[Metrics UI] Fix alert preview accuracy with new Notify settings (#89939)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
1fbea8cd78
commit
15277e187c
|
@ -75,6 +75,7 @@ const baseAlertRequestParamsRT = rt.intersection([
|
|||
alertInterval: rt.string,
|
||||
alertThrottle: rt.string,
|
||||
alertOnNoData: rt.boolean,
|
||||
alertNotifyWhen: rt.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertNotifyWhenType } from '../../../../../alerts/common';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { FORMATTERS } from '../../../../common/formatters';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
|
@ -36,6 +37,7 @@ import { getAlertPreview, PreviewableAlertTypes } from './get_alert_preview';
|
|||
interface Props {
|
||||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
alertNotifyWhen: AlertNotifyWhenType;
|
||||
alertType: PreviewableAlertTypes;
|
||||
alertParams: { criteria?: any[]; sourceId: string } & Record<string, any>;
|
||||
validate: (params: any) => ValidationResult;
|
||||
|
@ -48,6 +50,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
|
|||
alertParams,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
alertNotifyWhen,
|
||||
alertType,
|
||||
validate,
|
||||
showNoDataResults,
|
||||
|
@ -78,6 +81,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
|
|||
lookback: previewLookbackInterval as 'h' | 'd' | 'w' | 'M',
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
alertNotifyWhen,
|
||||
alertOnNoData: showNoDataResults ?? false,
|
||||
} as AlertPreviewRequestParams,
|
||||
alertType,
|
||||
|
@ -92,6 +96,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
|
|||
alertParams,
|
||||
alertInterval,
|
||||
alertType,
|
||||
alertNotifyWhen,
|
||||
groupByDisplayName,
|
||||
previewLookbackInterval,
|
||||
alertThrottle,
|
||||
|
@ -119,10 +124,11 @@ export const AlertPreview: React.FC<Props> = (props) => {
|
|||
|
||||
const showNumberOfNotifications = useMemo(() => {
|
||||
if (!previewResult) return false;
|
||||
if (alertNotifyWhen === 'onActiveAlert') return false;
|
||||
const { notifications, fired, noData, error } = previewResult.resultTotals;
|
||||
const unthrottledNotifications = fired + (showNoDataResults ? noData + error : 0);
|
||||
return unthrottledNotifications > notifications;
|
||||
}, [previewResult, showNoDataResults]);
|
||||
}, [previewResult, showNoDataResults, alertNotifyWhen]);
|
||||
|
||||
const hasWarningThreshold = useMemo(
|
||||
() => alertParams.criteria?.some((c) => Reflect.has(c, 'warningThreshold')) ?? false,
|
||||
|
@ -213,9 +219,17 @@ export const AlertPreview: React.FC<Props> = (props) => {
|
|||
<EuiSpacer size={'s'} />
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.alertPreviewTotalNotifications"
|
||||
defaultMessage='As a result, this alert would have sent {notifications} based on the selected "notify every" setting of "{alertThrottle}."'
|
||||
defaultMessage='As a result, this alert would have sent {notifications} based on the selected "notify" setting of "{alertThrottle}."'
|
||||
values={{
|
||||
alertThrottle: previewResult.alertThrottle,
|
||||
alertThrottle:
|
||||
alertNotifyWhen === 'onThrottleInterval'
|
||||
? previewResult.alertThrottle
|
||||
: i18n.translate(
|
||||
'xpack.infra.metrics.alertFlyout.alertPreviewOnlyOnStatusChange',
|
||||
{
|
||||
defaultMessage: 'Only on status change',
|
||||
}
|
||||
),
|
||||
notifications: (
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
|
|
|
@ -48,8 +48,9 @@ describe('Expression', () => {
|
|||
<Expressions
|
||||
alertInterval="1m"
|
||||
alertThrottle="1m"
|
||||
alertNotifyWhen="onThrottleInterval"
|
||||
alertParams={alertParams as any}
|
||||
errors={[]}
|
||||
errors={{}}
|
||||
setAlertParams={(key, value) => Reflect.set(alertParams, key, value)}
|
||||
setAlertProperty={() => {}}
|
||||
metadata={currentOptions}
|
||||
|
|
|
@ -38,8 +38,10 @@ import {
|
|||
ForLastExpression,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../triggers_actions_ui/public/common';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { IErrorObject } from '../../../../../triggers_actions_ui/public/types';
|
||||
import {
|
||||
IErrorObject,
|
||||
AlertTypeParamsExpressionProps,
|
||||
} from '../../../../../triggers_actions_ui/public';
|
||||
import { MetricsExplorerKueryBar } from '../../../pages/metrics/metrics_explorer/components/kuery_bar';
|
||||
import { useSourceViaHttp } from '../../../containers/source/use_source_via_http';
|
||||
import { sqsMetricTypes } from '../../../../common/inventory_models/aws_sqs/toolbar_items';
|
||||
|
@ -78,22 +80,21 @@ export interface AlertContextMeta {
|
|||
customMetrics?: SnapshotCustomMetricInput[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
errors: IErrorObject[];
|
||||
alertParams: {
|
||||
criteria: InventoryMetricConditions[];
|
||||
nodeType: InventoryItemType;
|
||||
filterQuery?: string;
|
||||
filterQueryText?: string;
|
||||
sourceId: string;
|
||||
alertOnNoData?: boolean;
|
||||
};
|
||||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
setAlertParams(key: string, value: any): void;
|
||||
setAlertProperty(key: string, value: any): void;
|
||||
metadata: AlertContextMeta;
|
||||
}
|
||||
type Criteria = InventoryMetricConditions[];
|
||||
type Props = Omit<
|
||||
AlertTypeParamsExpressionProps<
|
||||
{
|
||||
criteria: Criteria;
|
||||
nodeType: InventoryItemType;
|
||||
filterQuery?: string;
|
||||
filterQueryText?: string;
|
||||
sourceId: string;
|
||||
alertOnNoData?: boolean;
|
||||
},
|
||||
AlertContextMeta
|
||||
>,
|
||||
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data'
|
||||
>;
|
||||
|
||||
export const defaultExpression = {
|
||||
metric: 'cpu' as SnapshotMetricType,
|
||||
|
@ -111,7 +112,15 @@ export const defaultExpression = {
|
|||
|
||||
export const Expressions: React.FC<Props> = (props) => {
|
||||
const { http, notifications } = useKibanaContextForPlugin().services;
|
||||
const { setAlertParams, alertParams, errors, alertInterval, alertThrottle, metadata } = props;
|
||||
const {
|
||||
setAlertParams,
|
||||
alertParams,
|
||||
errors,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
metadata,
|
||||
alertNotifyWhen,
|
||||
} = props;
|
||||
const { source, createDerivedIndexPattern } = useSourceViaHttp({
|
||||
sourceId: 'default',
|
||||
type: 'metrics',
|
||||
|
@ -186,7 +195,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
timeSize: ts,
|
||||
}));
|
||||
setTimeSize(ts || undefined);
|
||||
setAlertParams('criteria', criteria);
|
||||
setAlertParams('criteria', criteria as Criteria);
|
||||
},
|
||||
[alertParams.criteria, setAlertParams]
|
||||
);
|
||||
|
@ -198,7 +207,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
timeUnit: tu,
|
||||
}));
|
||||
setTimeUnit(tu as Unit);
|
||||
setAlertParams('criteria', criteria);
|
||||
setAlertParams('criteria', criteria as Criteria);
|
||||
},
|
||||
[alertParams.criteria, setAlertParams]
|
||||
);
|
||||
|
@ -301,7 +310,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
key={idx} // idx's don't usually make good key's but here the index has semantic meaning
|
||||
expressionId={idx}
|
||||
setAlertParams={updateParams}
|
||||
errors={errors[idx] || emptyError}
|
||||
errors={(errors[idx] as IErrorObject) || emptyError}
|
||||
expression={e || {}}
|
||||
fields={derivedIndexPattern.fields}
|
||||
/>
|
||||
|
@ -385,6 +394,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
<AlertPreview
|
||||
alertInterval={alertInterval}
|
||||
alertThrottle={alertThrottle}
|
||||
alertNotifyWhen={alertNotifyWhen}
|
||||
alertType={METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID}
|
||||
alertParams={pick(alertParams, 'criteria', 'nodeType', 'sourceId', 'filterQuery')}
|
||||
validate={validateMetricThreshold}
|
||||
|
@ -406,7 +416,7 @@ interface ExpressionRowProps {
|
|||
expression: Omit<InventoryMetricConditions, 'metric'> & {
|
||||
metric?: SnapshotMetricType;
|
||||
};
|
||||
errors: IErrorObject;
|
||||
errors: AlertTypeParamsExpressionProps['errors'];
|
||||
canDelete: boolean;
|
||||
addExpression(): void;
|
||||
remove(id: number): void;
|
||||
|
|
|
@ -43,8 +43,9 @@ describe('Expression', () => {
|
|||
<Expression
|
||||
alertInterval="1m"
|
||||
alertThrottle="1m"
|
||||
alertNotifyWhen="onThrottleInterval"
|
||||
alertParams={alertParams as any}
|
||||
errors={[]}
|
||||
errors={{}}
|
||||
setAlertParams={(key, value) => Reflect.set(alertParams, key, value)}
|
||||
setAlertProperty={() => {}}
|
||||
metadata={currentOptions}
|
||||
|
|
|
@ -22,8 +22,11 @@ import {
|
|||
WhenExpression,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../triggers_actions_ui/public/common';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { IErrorObject } from '../../../../../triggers_actions_ui/public/types';
|
||||
import {
|
||||
AlertTypeParams,
|
||||
AlertTypeParamsExpressionProps,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../triggers_actions_ui/public/types';
|
||||
import { useSourceViaHttp } from '../../../containers/source/use_source_via_http';
|
||||
import { findInventoryModel } from '../../../../common/inventory_models';
|
||||
import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
|
@ -41,29 +44,32 @@ export interface AlertContextMeta {
|
|||
nodeType?: InventoryItemType;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
errors: IErrorObject[];
|
||||
alertParams: MetricAnomalyParams & {
|
||||
sourceId: string;
|
||||
};
|
||||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
setAlertParams(key: string, value: any): void;
|
||||
setAlertProperty(key: string, value: any): void;
|
||||
metadata: AlertContextMeta;
|
||||
}
|
||||
type AlertParams = AlertTypeParams &
|
||||
MetricAnomalyParams & { sourceId: string; hasInfraMLCapabilities: boolean };
|
||||
|
||||
type Props = Omit<
|
||||
AlertTypeParamsExpressionProps<AlertParams, AlertContextMeta>,
|
||||
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data'
|
||||
>;
|
||||
|
||||
export const defaultExpression = {
|
||||
metric: 'memory_usage' as MetricAnomalyParams['metric'],
|
||||
threshold: ANOMALY_THRESHOLD.MAJOR,
|
||||
nodeType: 'hosts',
|
||||
threshold: ANOMALY_THRESHOLD.MAJOR as MetricAnomalyParams['threshold'],
|
||||
nodeType: 'hosts' as MetricAnomalyParams['nodeType'],
|
||||
influencerFilter: undefined,
|
||||
};
|
||||
|
||||
export const Expression: React.FC<Props> = (props) => {
|
||||
const { hasInfraMLCapabilities, isLoading: isLoadingMLCapabilities } = useInfraMLCapabilities();
|
||||
const { http, notifications } = useKibanaContextForPlugin().services;
|
||||
const { setAlertParams, alertParams, alertInterval, alertThrottle, metadata } = props;
|
||||
const {
|
||||
setAlertParams,
|
||||
alertParams,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
alertNotifyWhen,
|
||||
metadata,
|
||||
} = props;
|
||||
const { source, createDerivedIndexPattern } = useSourceViaHttp({
|
||||
sourceId: 'default',
|
||||
type: 'metrics',
|
||||
|
@ -97,7 +103,7 @@ export const Expression: React.FC<Props> = (props) => {
|
|||
setAlertParams('influencerFilter', {
|
||||
...alertParams.influencerFilter,
|
||||
fieldValue: value,
|
||||
});
|
||||
} as MetricAnomalyParams['influencerFilter']);
|
||||
} else {
|
||||
setAlertParams('influencerFilter', undefined);
|
||||
}
|
||||
|
@ -118,7 +124,7 @@ export const Expression: React.FC<Props> = (props) => {
|
|||
|
||||
const updateMetric = useCallback(
|
||||
(metric: string) => {
|
||||
setAlertParams('metric', metric);
|
||||
setAlertParams('metric', metric as MetricAnomalyParams['metric']);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
@ -249,6 +255,7 @@ export const Expression: React.FC<Props> = (props) => {
|
|||
<AlertPreview
|
||||
alertInterval={alertInterval}
|
||||
alertThrottle={alertThrottle}
|
||||
alertNotifyWhen={alertNotifyWhen}
|
||||
alertType={METRIC_ANOMALY_ALERT_TYPE_ID}
|
||||
alertParams={pick(
|
||||
alertParams,
|
||||
|
@ -295,7 +302,9 @@ export const nodeTypes: { [key: string]: any } = {
|
|||
},
|
||||
};
|
||||
|
||||
const getMLMetricFromInventoryMetric = (metric: SnapshotMetricType) => {
|
||||
const getMLMetricFromInventoryMetric: (
|
||||
metric: SnapshotMetricType
|
||||
) => MetricAnomalyParams['metric'] | null = (metric) => {
|
||||
switch (metric) {
|
||||
case 'memory':
|
||||
return 'memory_usage';
|
||||
|
@ -308,7 +317,9 @@ const getMLMetricFromInventoryMetric = (metric: SnapshotMetricType) => {
|
|||
}
|
||||
};
|
||||
|
||||
const getMLNodeTypeFromInventoryNodeType = (nodeType: InventoryItemType) => {
|
||||
const getMLNodeTypeFromInventoryNodeType: (
|
||||
nodeType: InventoryItemType
|
||||
) => MetricAnomalyParams['nodeType'] | null = (nodeType) => {
|
||||
switch (nodeType) {
|
||||
case 'host':
|
||||
return 'hosts';
|
||||
|
|
|
@ -44,8 +44,9 @@ describe('Expression', () => {
|
|||
<Expressions
|
||||
alertInterval="1m"
|
||||
alertThrottle="1m"
|
||||
alertNotifyWhen="onThrottleInterval"
|
||||
alertParams={alertParams}
|
||||
errors={[]}
|
||||
errors={{}}
|
||||
setAlertParams={(key, value) => Reflect.set(alertParams, key, value)}
|
||||
setAlertProperty={() => {}}
|
||||
metadata={{
|
||||
|
|
|
@ -30,8 +30,12 @@ import {
|
|||
ForLastExpression,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../triggers_actions_ui/public/common';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { IErrorObject } from '../../../../../triggers_actions_ui/public/types';
|
||||
import {
|
||||
IErrorObject,
|
||||
AlertTypeParams,
|
||||
AlertTypeParamsExpressionProps,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../triggers_actions_ui/public/types';
|
||||
import { MetricsExplorerKueryBar } from '../../../pages/metrics/metrics_explorer/components/kuery_bar';
|
||||
import { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options';
|
||||
import { MetricsExplorerGroupBy } from '../../../pages/metrics/metrics_explorer/components/group_by';
|
||||
|
@ -46,15 +50,10 @@ import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
|||
|
||||
const FILTER_TYPING_DEBOUNCE_MS = 500;
|
||||
|
||||
interface Props {
|
||||
errors: IErrorObject[];
|
||||
alertParams: AlertParams;
|
||||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
setAlertParams(key: string, value: any): void;
|
||||
setAlertProperty(key: string, value: any): void;
|
||||
metadata: AlertContextMeta;
|
||||
}
|
||||
type Props = Omit<
|
||||
AlertTypeParamsExpressionProps<AlertTypeParams & AlertParams, AlertContextMeta>,
|
||||
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data'
|
||||
>;
|
||||
|
||||
const defaultExpression = {
|
||||
aggType: Aggregators.AVERAGE,
|
||||
|
@ -66,7 +65,15 @@ const defaultExpression = {
|
|||
export { defaultExpression };
|
||||
|
||||
export const Expressions: React.FC<Props> = (props) => {
|
||||
const { setAlertParams, alertParams, errors, alertInterval, alertThrottle, metadata } = props;
|
||||
const {
|
||||
setAlertParams,
|
||||
alertParams,
|
||||
errors,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
metadata,
|
||||
alertNotifyWhen,
|
||||
} = props;
|
||||
const { http, notifications } = useKibanaContextForPlugin().services;
|
||||
const { source, createDerivedIndexPattern } = useSourceViaHttp({
|
||||
sourceId: 'default',
|
||||
|
@ -76,7 +83,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
});
|
||||
|
||||
const [timeSize, setTimeSize] = useState<number | undefined>(1);
|
||||
const [timeUnit, setTimeUnit] = useState<Unit>('m');
|
||||
const [timeUnit, setTimeUnit] = useState<Unit | undefined>('m');
|
||||
const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [
|
||||
createDerivedIndexPattern,
|
||||
]);
|
||||
|
@ -174,7 +181,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
timeUnit: tu,
|
||||
})) || [];
|
||||
setTimeUnit(tu as Unit);
|
||||
setAlertParams('criteria', criteria);
|
||||
setAlertParams('criteria', criteria as AlertParams['criteria']);
|
||||
},
|
||||
[alertParams.criteria, setAlertParams]
|
||||
);
|
||||
|
@ -191,7 +198,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
timeSize,
|
||||
timeUnit,
|
||||
aggType: metric.aggregation,
|
||||
}))
|
||||
})) as AlertParams['criteria']
|
||||
);
|
||||
} else {
|
||||
setAlertParams('criteria', [defaultExpression]);
|
||||
|
@ -280,7 +287,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
key={idx} // idx's don't usually make good key's but here the index has semantic meaning
|
||||
expressionId={idx}
|
||||
setAlertParams={updateParams}
|
||||
errors={errors[idx] || emptyError}
|
||||
errors={(errors[idx] as IErrorObject) || emptyError}
|
||||
expression={e || {}}
|
||||
>
|
||||
<ExpressionChart
|
||||
|
@ -396,6 +403,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
<AlertPreview
|
||||
alertInterval={alertInterval}
|
||||
alertThrottle={alertThrottle}
|
||||
alertNotifyWhen={alertNotifyWhen}
|
||||
alertType={METRIC_THRESHOLD_ALERT_TYPE_ID}
|
||||
alertParams={pick(alertParams, 'criteria', 'groupBy', 'filterQuery', 'sourceId')}
|
||||
showNoDataResults={alertParams.alertOnNoData}
|
||||
|
|
|
@ -328,7 +328,7 @@ export const ExpressionChart: React.FC<Props> = ({
|
|||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alerts.dataTimeRangeLabelWithGrouping"
|
||||
defaultMessage="Last {lookback} {timeLabel} of data for {id}"
|
||||
values={{ id: series.id, timeLabel, lookback: timeSize * 20 }}
|
||||
values={{ id: series.id, timeLabel, lookback: timeSize! * 20 }}
|
||||
/>
|
||||
</EuiText>
|
||||
) : (
|
||||
|
@ -336,7 +336,7 @@ export const ExpressionChart: React.FC<Props> = ({
|
|||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alerts.dataTimeRangeLabel"
|
||||
defaultMessage="Last {lookback} {timeLabel}"
|
||||
values={{ timeLabel, lookback: timeSize * 20 }}
|
||||
values={{ timeLabel, lookback: timeSize! * 20 }}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
|
|
|
@ -17,8 +17,10 @@ export interface AlertContextMeta {
|
|||
series?: MetricsExplorerSeries;
|
||||
}
|
||||
|
||||
export type MetricExpression = Omit<MetricExpressionParams, 'metric'> & {
|
||||
metric?: string;
|
||||
export type MetricExpression = Omit<MetricExpressionParams, 'metric' | 'timeSize' | 'timeUnit'> & {
|
||||
metric?: MetricExpressionParams['metric'];
|
||||
timeSize?: MetricExpressionParams['timeSize'];
|
||||
timeUnit?: MetricExpressionParams['timeUnit'];
|
||||
};
|
||||
|
||||
export enum AGGREGATION_TYPES {
|
||||
|
@ -54,7 +56,7 @@ export interface ExpressionChartData {
|
|||
|
||||
export interface AlertParams {
|
||||
criteria: MetricExpression[];
|
||||
groupBy?: string[];
|
||||
groupBy?: string | string[];
|
||||
filterQuery?: string;
|
||||
sourceId: string;
|
||||
filterQueryText?: string;
|
||||
|
|
|
@ -34,6 +34,7 @@ interface PreviewInventoryMetricThresholdAlertParams {
|
|||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
alertOnNoData: boolean;
|
||||
alertNotifyWhen: string;
|
||||
}
|
||||
|
||||
export const previewInventoryMetricThresholdAlert: (
|
||||
|
@ -46,7 +47,8 @@ export const previewInventoryMetricThresholdAlert: (
|
|||
alertInterval,
|
||||
alertThrottle,
|
||||
alertOnNoData,
|
||||
}) => {
|
||||
alertNotifyWhen,
|
||||
}: PreviewInventoryMetricThresholdAlertParams) => {
|
||||
const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams;
|
||||
|
||||
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
|
||||
|
@ -62,9 +64,7 @@ export const previewInventoryMetricThresholdAlert: (
|
|||
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
|
||||
const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds;
|
||||
const throttleIntervalInSeconds = getIntervalInSeconds(alertThrottle);
|
||||
const executionsPerThrottle = Math.floor(
|
||||
(throttleIntervalInSeconds / alertIntervalInSeconds) * alertResultsPerExecution
|
||||
);
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
criteria.map((c) =>
|
||||
|
@ -82,9 +82,17 @@ export const previewInventoryMetricThresholdAlert: (
|
|||
let numberOfErrors = 0;
|
||||
let numberOfNotifications = 0;
|
||||
let throttleTracker = 0;
|
||||
const notifyWithThrottle = () => {
|
||||
if (throttleTracker === 0) numberOfNotifications++;
|
||||
throttleTracker++;
|
||||
let previousActionGroup: string | null = null;
|
||||
const notifyWithThrottle = (actionGroup: string) => {
|
||||
if (alertNotifyWhen === 'onActionGroupChange') {
|
||||
if (previousActionGroup !== actionGroup) numberOfNotifications++;
|
||||
} else if (alertNotifyWhen === 'onThrottleInterval') {
|
||||
if (throttleTracker === 0) numberOfNotifications++;
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
} else {
|
||||
numberOfNotifications++;
|
||||
}
|
||||
previousActionGroup = actionGroup;
|
||||
};
|
||||
for (let i = 0; i < numberOfExecutionBuckets; i++) {
|
||||
const mappedBucketIndex = Math.floor(i * alertResultsPerExecution);
|
||||
|
@ -105,23 +113,26 @@ export const previewInventoryMetricThresholdAlert: (
|
|||
if (someConditionsErrorInMappedBucket) {
|
||||
numberOfErrors++;
|
||||
if (alertOnNoData) {
|
||||
notifyWithThrottle();
|
||||
notifyWithThrottle('fired'); // TODO: Update this when No Data alerts move to an action group
|
||||
}
|
||||
} else if (someConditionsNoDataInMappedBucket) {
|
||||
numberOfNoDataResults++;
|
||||
if (alertOnNoData) {
|
||||
notifyWithThrottle();
|
||||
notifyWithThrottle('fired'); // TODO: Update this when No Data alerts move to an action group
|
||||
}
|
||||
} else if (allConditionsFiredInMappedBucket) {
|
||||
numberOfTimesFired++;
|
||||
notifyWithThrottle();
|
||||
notifyWithThrottle('fired');
|
||||
} else if (allConditionsWarnInMappedBucket) {
|
||||
numberOfTimesWarned++;
|
||||
notifyWithThrottle();
|
||||
} else if (throttleTracker > 0) {
|
||||
throttleTracker++;
|
||||
notifyWithThrottle('warning');
|
||||
} else {
|
||||
previousActionGroup = 'recovered';
|
||||
if (throttleTracker > 0) {
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
}
|
||||
}
|
||||
if (throttleTracker === executionsPerThrottle) {
|
||||
if (throttleTracker >= throttleIntervalInSeconds) {
|
||||
throttleTracker = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ interface PreviewMetricAnomalyAlertParams {
|
|||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
alertOnNoData: boolean;
|
||||
alertNotifyWhen: string;
|
||||
}
|
||||
|
||||
export const previewMetricAnomalyAlert = async ({
|
||||
|
@ -38,12 +39,12 @@ export const previewMetricAnomalyAlert = async ({
|
|||
lookback,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
alertNotifyWhen,
|
||||
}: PreviewMetricAnomalyAlertParams) => {
|
||||
const { metric, threshold, influencerFilter, nodeType } = params as MetricAnomalyParams;
|
||||
|
||||
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
|
||||
const throttleIntervalInSeconds = getIntervalInSeconds(alertThrottle);
|
||||
const executionsPerThrottle = Math.floor(throttleIntervalInSeconds / alertIntervalInSeconds);
|
||||
|
||||
const lookbackInterval = `1${lookback}`;
|
||||
const lookbackIntervalInSeconds = getIntervalInSeconds(lookbackInterval);
|
||||
|
@ -78,9 +79,17 @@ export const previewMetricAnomalyAlert = async ({
|
|||
let numberOfTimesFired = 0;
|
||||
let numberOfNotifications = 0;
|
||||
let throttleTracker = 0;
|
||||
const notifyWithThrottle = () => {
|
||||
if (throttleTracker === 0) numberOfNotifications++;
|
||||
throttleTracker++;
|
||||
let previousActionGroup: string | null = null;
|
||||
const notifyWithThrottle = (actionGroup: string) => {
|
||||
if (alertNotifyWhen === 'onActionGroupChange') {
|
||||
if (previousActionGroup !== actionGroup) numberOfNotifications++;
|
||||
} else if (alertNotifyWhen === 'onThrottleInterval') {
|
||||
if (throttleTracker === 0) numberOfNotifications++;
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
} else {
|
||||
numberOfNotifications++;
|
||||
}
|
||||
previousActionGroup = actionGroup;
|
||||
};
|
||||
// Mock each alert evaluation
|
||||
for (let i = 0; i < numberOfExecutions; i++) {
|
||||
|
@ -102,11 +111,14 @@ export const previewMetricAnomalyAlert = async ({
|
|||
|
||||
if (anomaliesDetectedInBuckets) {
|
||||
numberOfTimesFired++;
|
||||
notifyWithThrottle();
|
||||
} else if (throttleTracker > 0) {
|
||||
throttleTracker++;
|
||||
notifyWithThrottle('fired');
|
||||
} else {
|
||||
previousActionGroup = 'recovered';
|
||||
if (throttleTracker > 0) {
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
}
|
||||
}
|
||||
if (throttleTracker === executionsPerThrottle) {
|
||||
if (throttleTracker >= throttleIntervalInSeconds) {
|
||||
throttleTracker = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ describe('Previewing the metric threshold alert type', () => {
|
|||
alertInterval: '1m',
|
||||
alertThrottle: '1m',
|
||||
alertOnNoData: true,
|
||||
alertNotifyWhen: 'onThrottleInterval',
|
||||
});
|
||||
const { fired, noData, error, notifications } = ungroupedResult;
|
||||
expect(fired).toBe(30);
|
||||
|
@ -34,6 +35,7 @@ describe('Previewing the metric threshold alert type', () => {
|
|||
alertInterval: '3m',
|
||||
alertThrottle: '3m',
|
||||
alertOnNoData: true,
|
||||
alertNotifyWhen: 'onThrottleInterval',
|
||||
});
|
||||
const { fired, noData, error, notifications } = ungroupedResult;
|
||||
expect(fired).toBe(10);
|
||||
|
@ -48,6 +50,7 @@ describe('Previewing the metric threshold alert type', () => {
|
|||
alertInterval: '30s',
|
||||
alertThrottle: '30s',
|
||||
alertOnNoData: true,
|
||||
alertNotifyWhen: 'onThrottleInterval',
|
||||
});
|
||||
const { fired, noData, error, notifications } = ungroupedResult;
|
||||
expect(fired).toBe(60);
|
||||
|
@ -62,6 +65,7 @@ describe('Previewing the metric threshold alert type', () => {
|
|||
alertInterval: '1m',
|
||||
alertThrottle: '3m',
|
||||
alertOnNoData: true,
|
||||
alertNotifyWhen: 'onThrottleInterval',
|
||||
});
|
||||
const { fired, noData, error, notifications } = ungroupedResult;
|
||||
expect(fired).toBe(30);
|
||||
|
@ -69,6 +73,30 @@ describe('Previewing the metric threshold alert type', () => {
|
|||
expect(error).toBe(0);
|
||||
expect(notifications).toBe(15);
|
||||
});
|
||||
test('returns the expected results using a notify setting of Only on Status Change', async () => {
|
||||
const [ungroupedResult] = await previewMetricThresholdAlert({
|
||||
...baseParams,
|
||||
params: {
|
||||
...baseParams.params,
|
||||
criteria: [
|
||||
{
|
||||
...baseCriterion,
|
||||
metric: 'test.metric.3',
|
||||
} as MetricExpressionParams,
|
||||
],
|
||||
},
|
||||
lookback: 'h',
|
||||
alertInterval: '1m',
|
||||
alertThrottle: '1m',
|
||||
alertOnNoData: true,
|
||||
alertNotifyWhen: 'onActionGroupChange',
|
||||
});
|
||||
const { fired, noData, error, notifications } = ungroupedResult;
|
||||
expect(fired).toBe(20);
|
||||
expect(noData).toBe(0);
|
||||
expect(error).toBe(0);
|
||||
expect(notifications).toBe(20);
|
||||
});
|
||||
});
|
||||
describe('querying with a groupBy parameter', () => {
|
||||
test('returns the expected results', async () => {
|
||||
|
@ -82,6 +110,7 @@ describe('Previewing the metric threshold alert type', () => {
|
|||
alertInterval: '1m',
|
||||
alertThrottle: '1m',
|
||||
alertOnNoData: true,
|
||||
alertNotifyWhen: 'onThrottleInterval',
|
||||
});
|
||||
const {
|
||||
fired: firedA,
|
||||
|
@ -122,6 +151,7 @@ describe('Previewing the metric threshold alert type', () => {
|
|||
alertInterval: '1m',
|
||||
alertThrottle: '1m',
|
||||
alertOnNoData: true,
|
||||
alertNotifyWhen: 'onThrottleInterval',
|
||||
});
|
||||
const { fired, noData, error, notifications } = ungroupedResult;
|
||||
expect(fired).toBe(25);
|
||||
|
@ -144,6 +174,9 @@ services.callCluster.mockImplementation(async (_: string, { body, index }: any)
|
|||
if (metric === 'test.metric.2') {
|
||||
return mocks.alternateMetricPreviewResponse;
|
||||
}
|
||||
if (metric === 'test.metric.3') {
|
||||
return mocks.repeatingMetricPreviewResponse;
|
||||
}
|
||||
return mocks.basicMetricPreviewResponse;
|
||||
});
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ interface PreviewMetricThresholdAlertParams {
|
|||
lookback: Unit;
|
||||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
alertNotifyWhen: string;
|
||||
alertOnNoData: boolean;
|
||||
end?: number;
|
||||
overrideLookbackIntervalInSeconds?: number;
|
||||
|
@ -48,6 +49,7 @@ export const previewMetricThresholdAlert: (
|
|||
lookback,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
alertNotifyWhen,
|
||||
alertOnNoData,
|
||||
end = Date.now(),
|
||||
overrideLookbackIntervalInSeconds,
|
||||
|
@ -104,9 +106,17 @@ export const previewMetricThresholdAlert: (
|
|||
let numberOfErrors = 0;
|
||||
let numberOfNotifications = 0;
|
||||
let throttleTracker = 0;
|
||||
const notifyWithThrottle = () => {
|
||||
if (throttleTracker === 0) numberOfNotifications++;
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
let previousActionGroup: string | null = null;
|
||||
const notifyWithThrottle = (actionGroup: string) => {
|
||||
if (alertNotifyWhen === 'onActionGroupChange') {
|
||||
if (previousActionGroup !== actionGroup) numberOfNotifications++;
|
||||
previousActionGroup = actionGroup;
|
||||
} else if (alertNotifyWhen === 'onThrottleInterval') {
|
||||
if (throttleTracker === 0) numberOfNotifications++;
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
} else {
|
||||
numberOfNotifications++;
|
||||
}
|
||||
};
|
||||
for (let i = 0; i < numberOfExecutionBuckets; i++) {
|
||||
const mappedBucketIndex = Math.floor(i * alertResultsPerExecution);
|
||||
|
@ -126,21 +136,24 @@ export const previewMetricThresholdAlert: (
|
|||
if (someConditionsErrorInMappedBucket) {
|
||||
numberOfErrors++;
|
||||
if (alertOnNoData) {
|
||||
notifyWithThrottle();
|
||||
notifyWithThrottle('fired'); // TODO: Update this when No Data alerts move to an action group
|
||||
}
|
||||
} else if (someConditionsNoDataInMappedBucket) {
|
||||
numberOfNoDataResults++;
|
||||
if (alertOnNoData) {
|
||||
notifyWithThrottle();
|
||||
notifyWithThrottle('fired'); // TODO: Update this when No Data alerts move to an action group
|
||||
}
|
||||
} else if (allConditionsFiredInMappedBucket) {
|
||||
numberOfTimesFired++;
|
||||
notifyWithThrottle();
|
||||
notifyWithThrottle('fired');
|
||||
} else if (allConditionsWarnInMappedBucket) {
|
||||
numberOfTimesWarned++;
|
||||
notifyWithThrottle();
|
||||
} else if (throttleTracker > 0) {
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
notifyWithThrottle('warning');
|
||||
} else {
|
||||
previousActionGroup = 'recovered';
|
||||
if (throttleTracker > 0) {
|
||||
throttleTracker += alertIntervalInSeconds;
|
||||
}
|
||||
}
|
||||
if (throttleTracker >= throttleIntervalInSeconds) {
|
||||
throttleTracker = 0;
|
||||
|
@ -168,6 +181,7 @@ export const previewMetricThresholdAlert: (
|
|||
alertInterval,
|
||||
alertThrottle,
|
||||
alertOnNoData,
|
||||
alertNotifyWhen,
|
||||
};
|
||||
const { maxBuckets } = e;
|
||||
// If this is still the first iteration, try to get the number of groups in order to
|
||||
|
|
|
@ -45,6 +45,7 @@ const previewBucketsWithNulls = [
|
|||
...Array.from(Array(10), (_, i) => ({ aggregatedValue: { value: null } })),
|
||||
...previewBucketsA.slice(10),
|
||||
];
|
||||
const previewBucketsRepeat = Array.from(Array(60), (_, i) => bucketsA[Math.max(0, (i % 3) - 1)]);
|
||||
|
||||
export const basicMetricResponse = {
|
||||
aggregations: {
|
||||
|
@ -175,6 +176,14 @@ export const alternateMetricPreviewResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
export const repeatingMetricPreviewResponse = {
|
||||
aggregations: {
|
||||
aggregatedIntervals: {
|
||||
buckets: previewBucketsRepeat,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const basicCompositePreviewResponse = {
|
||||
aggregations: {
|
||||
groupings: {
|
||||
|
|
|
@ -43,6 +43,7 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
alertInterval,
|
||||
alertThrottle,
|
||||
alertOnNoData,
|
||||
alertNotifyWhen,
|
||||
} = request.body;
|
||||
|
||||
const callCluster = (endpoint: string, opts: Record<string, any>) => {
|
||||
|
@ -69,6 +70,7 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
config: source.configuration,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
alertNotifyWhen,
|
||||
alertOnNoData,
|
||||
});
|
||||
|
||||
|
@ -90,6 +92,7 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
source,
|
||||
alertInterval,
|
||||
alertThrottle,
|
||||
alertNotifyWhen,
|
||||
alertOnNoData,
|
||||
});
|
||||
|
||||
|
@ -119,6 +122,7 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
alertInterval,
|
||||
alertThrottle,
|
||||
alertOnNoData,
|
||||
alertNotifyWhen,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
|
|
|
@ -147,6 +147,7 @@ describe('EsQueryAlertTypeExpression', () => {
|
|||
<EsQueryAlertTypeExpression
|
||||
alertInterval="1m"
|
||||
alertThrottle="1m"
|
||||
alertNotifyWhen="onThrottleInterval"
|
||||
alertParams={alertParams}
|
||||
setAlertParams={() => {}}
|
||||
setAlertProperty={() => {}}
|
||||
|
|
|
@ -89,6 +89,7 @@ describe('IndexThresholdAlertTypeExpression', () => {
|
|||
<IndexThresholdAlertTypeExpression
|
||||
alertInterval="1m"
|
||||
alertThrottle="1m"
|
||||
alertNotifyWhen="onThrottleInterval"
|
||||
alertParams={alertParams}
|
||||
setAlertParams={() => {}}
|
||||
setAlertProperty={() => {}}
|
||||
|
|
|
@ -591,6 +591,7 @@ export const AlertForm = ({
|
|||
alertParams={alert.params}
|
||||
alertInterval={`${alertInterval ?? 1}${alertIntervalUnit}`}
|
||||
alertThrottle={`${alertThrottle ?? 1}${alertThrottleUnit}`}
|
||||
alertNotifyWhen={alert.notifyWhen ?? 'onActionGroupChange'}
|
||||
errors={errors}
|
||||
setAlertParams={setAlertParams}
|
||||
setAlertProperty={setAlertProperty}
|
||||
|
|
|
@ -198,6 +198,7 @@ export interface AlertTypeParamsExpressionProps<
|
|||
alertParams: Params;
|
||||
alertInterval: string;
|
||||
alertThrottle: string;
|
||||
alertNotifyWhen: AlertNotifyWhenType;
|
||||
setAlertParams: <Key extends keyof Params>(property: Key, value: Params[Key] | undefined) => void;
|
||||
setAlertProperty: <Prop extends keyof Alert>(
|
||||
key: Prop,
|
||||
|
|
Loading…
Reference in a new issue