[Metrics UI] Use Notify Every in Alert Preview (#74401)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Zacqary Adam Xeper 2020-09-18 14:42:23 -05:00 committed by GitHub
parent 060f0895cc
commit 9cf546f3d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 225 additions and 48 deletions

View file

@ -52,6 +52,8 @@ const baseAlertRequestParamsRT = rt.intersection([
]), ]),
criteria: rt.array(rt.any), criteria: rt.array(rt.any),
alertInterval: rt.string, alertInterval: rt.string,
alertThrottle: rt.string,
alertOnNoData: rt.boolean,
}), }),
]); ]);
@ -91,6 +93,7 @@ export const alertPreviewSuccessResponsePayloadRT = rt.type({
fired: rt.number, fired: rt.number,
noData: rt.number, noData: rt.number,
error: rt.number, error: rt.number,
notifications: rt.number,
}), }),
}); });
export type AlertPreviewSuccessResponsePayload = rt.TypeOf< export type AlertPreviewSuccessResponsePayload = rt.TypeOf<

View file

@ -33,6 +33,7 @@ import { getAlertPreview, PreviewableAlertTypes } from './get_alert_preview';
interface Props { interface Props {
alertInterval: string; alertInterval: string;
alertThrottle: string;
alertType: PreviewableAlertTypes; alertType: PreviewableAlertTypes;
fetch: HttpSetup['fetch']; fetch: HttpSetup['fetch'];
alertParams: { criteria: any[]; sourceId: string } & Record<string, any>; alertParams: { criteria: any[]; sourceId: string } & Record<string, any>;
@ -45,6 +46,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
const { const {
alertParams, alertParams,
alertInterval, alertInterval,
alertThrottle,
fetch, fetch,
alertType, alertType,
validate, validate,
@ -73,16 +75,27 @@ export const AlertPreview: React.FC<Props> = (props) => {
...alertParams, ...alertParams,
lookback: previewLookbackInterval as 'h' | 'd' | 'w' | 'M', lookback: previewLookbackInterval as 'h' | 'd' | 'w' | 'M',
alertInterval, alertInterval,
alertThrottle,
alertOnNoData: showNoDataResults ?? false,
} as AlertPreviewRequestParams, } as AlertPreviewRequestParams,
alertType, alertType,
}); });
setPreviewResult({ ...result, groupByDisplayName, previewLookbackInterval }); setPreviewResult({ ...result, groupByDisplayName, previewLookbackInterval, alertThrottle });
} catch (e) { } catch (e) {
setPreviewError(e); setPreviewError(e);
} finally { } finally {
setIsPreviewLoading(false); setIsPreviewLoading(false);
} }
}, [alertParams, alertInterval, fetch, alertType, groupByDisplayName, previewLookbackInterval]); }, [
alertParams,
alertInterval,
fetch,
alertType,
groupByDisplayName,
previewLookbackInterval,
alertThrottle,
showNoDataResults,
]);
const previewIntervalError = useMemo(() => { const previewIntervalError = useMemo(() => {
const intervalInSeconds = getIntervalInSeconds(alertInterval); const intervalInSeconds = getIntervalInSeconds(alertInterval);
@ -101,6 +114,13 @@ export const AlertPreview: React.FC<Props> = (props) => {
return hasValidationErrors || previewIntervalError; return hasValidationErrors || previewIntervalError;
}, [alertParams.criteria, previewIntervalError, validate]); }, [alertParams.criteria, previewIntervalError, validate]);
const showNumberOfNotifications = useMemo(() => {
if (!previewResult) return false;
const { notifications, fired, noData, error } = previewResult.resultTotals;
const unthrottledNotifications = fired + (showNoDataResults ? noData + error : 0);
return unthrottledNotifications > notifications;
}, [previewResult, showNoDataResults]);
return ( return (
<EuiFormRow <EuiFormRow
label={i18n.translate('xpack.infra.metrics.alertFlyout.previewLabel', { label={i18n.translate('xpack.infra.metrics.alertFlyout.previewLabel', {
@ -136,19 +156,22 @@ export const AlertPreview: React.FC<Props> = (props) => {
<> <>
<EuiSpacer size={'s'} /> <EuiSpacer size={'s'} />
<EuiCallOut <EuiCallOut
iconType="iInCircle" size="s"
title={ title={
<> <>
<FormattedMessage <FormattedMessage
id="xpack.infra.metrics.alertFlyout.alertPreviewResult" id="xpack.infra.metrics.alertFlyout.alertPreviewResult"
defaultMessage="This alert would have occurred {firedTimes}" defaultMessage="There were {firedTimes}"
values={{ values={{
firedTimes: ( firedTimes: (
<strong> <strong>
{previewResult.resultTotals.fired}{' '} <FormattedMessage
{previewResult.resultTotals.fired === 1 id="xpack.infra.metrics.alertFlyout.firedTimes"
? firedTimeLabel defaultMessage="{fired, plural, one {# instance} other {# instances}}"
: firedTimesLabel} values={{
fired: previewResult.resultTotals.fired,
}}
/>
</strong> </strong>
), ),
}} }}
@ -173,7 +196,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
) : null} ) : null}
<FormattedMessage <FormattedMessage
id="xpack.infra.metrics.alertFlyout.alertPreviewResultLookback" id="xpack.infra.metrics.alertFlyout.alertPreviewResultLookback"
defaultMessage="in the last {lookback}." defaultMessage="that satisfied the conditions of this alert in the last {lookback}."
values={{ values={{
lookback: previewOptions.find( lookback: previewOptions.find(
(e) => e.value === previewResult.previewLookbackInterval (e) => e.value === previewResult.previewLookbackInterval
@ -211,6 +234,32 @@ export const AlertPreview: React.FC<Props> = (props) => {
defaultMessage="An error occurred when trying to evaluate some of the data." defaultMessage="An error occurred when trying to evaluate some of the data."
/> />
) : null} ) : null}
{showNumberOfNotifications ? (
<>
<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}."'
values={{
alertThrottle: previewResult.alertThrottle,
notifications: (
<strong>
{i18n.translate(
'xpack.infra.metrics.alertFlyout.alertPreviewTotalNotificationsNumber',
{
defaultMessage:
'{notifs, plural, one {# notification} other {# notifications}}',
values: {
notifs: previewResult.resultTotals.notifications,
},
}
)}
</strong>
),
}}
/>
</>
) : null}{' '}
</EuiCallOut> </EuiCallOut>
</> </>
)} )}
@ -218,6 +267,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
<> <>
<EuiSpacer size={'s'} /> <EuiSpacer size={'s'} />
<EuiCallOut <EuiCallOut
size="s"
title={ title={
<FormattedMessage <FormattedMessage
id="xpack.infra.metrics.alertFlyout.previewIntervalTooShortTitle" id="xpack.infra.metrics.alertFlyout.previewIntervalTooShortTitle"
@ -242,6 +292,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
<EuiSpacer size={'s'} /> <EuiSpacer size={'s'} />
{previewError.body?.statusCode === 508 ? ( {previewError.body?.statusCode === 508 ? (
<EuiCallOut <EuiCallOut
size="s"
title={ title={
<FormattedMessage <FormattedMessage
id="xpack.infra.metrics.alertFlyout.tooManyBucketsErrorTitle" id="xpack.infra.metrics.alertFlyout.tooManyBucketsErrorTitle"
@ -264,6 +315,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
</EuiCallOut> </EuiCallOut>
) : ( ) : (
<EuiCallOut <EuiCallOut
size="s"
title={ title={
<FormattedMessage <FormattedMessage
id="xpack.infra.metrics.alertFlyout.alertPreviewError" id="xpack.infra.metrics.alertFlyout.alertPreviewError"
@ -349,10 +401,3 @@ const previewOptions = [
const previewDOMOptions: Array<{ text: string; value: string }> = previewOptions.map((o) => const previewDOMOptions: Array<{ text: string; value: string }> = previewOptions.map((o) =>
omit(o, 'shortText') 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',
});

View file

@ -45,10 +45,3 @@ export const previewOptions = [
}), }),
}, },
]; ];
export const firedTimeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTime', {
defaultMessage: 'time',
});
export const firedTimesLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTimes', {
defaultMessage: 'times',
});

View file

@ -69,6 +69,7 @@ describe('Expression', () => {
<Expressions <Expressions
alertsContext={context} alertsContext={context}
alertInterval="1m" alertInterval="1m"
alertThrottle="1m"
alertParams={alertParams as any} alertParams={alertParams as any}
errors={[]} errors={[]}
setAlertParams={(key, value) => Reflect.set(alertParams, key, value)} setAlertParams={(key, value) => Reflect.set(alertParams, key, value)}

View file

@ -89,6 +89,7 @@ interface Props {
alertOnNoData?: boolean; alertOnNoData?: boolean;
}; };
alertInterval: string; alertInterval: string;
alertThrottle: string;
alertsContext: AlertsContextValue<AlertContextMeta>; alertsContext: AlertsContextValue<AlertContextMeta>;
setAlertParams(key: string, value: any): void; setAlertParams(key: string, value: any): void;
setAlertProperty(key: string, value: any): void; setAlertProperty(key: string, value: any): void;
@ -104,7 +105,14 @@ const defaultExpression = {
} as InventoryMetricConditions; } as InventoryMetricConditions;
export const Expressions: React.FC<Props> = (props) => { export const Expressions: React.FC<Props> = (props) => {
const { setAlertParams, alertParams, errors, alertsContext, alertInterval } = props; const {
setAlertParams,
alertParams,
errors,
alertsContext,
alertInterval,
alertThrottle,
} = props;
const { source, createDerivedIndexPattern } = useSourceViaHttp({ const { source, createDerivedIndexPattern } = useSourceViaHttp({
sourceId: 'default', sourceId: 'default',
type: 'metrics', type: 'metrics',
@ -378,6 +386,7 @@ export const Expressions: React.FC<Props> = (props) => {
<EuiSpacer size={'m'} /> <EuiSpacer size={'m'} />
<AlertPreview <AlertPreview
alertInterval={alertInterval} alertInterval={alertInterval}
alertThrottle={alertThrottle}
alertType={METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID} alertType={METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID}
alertParams={pick(alertParams, 'criteria', 'nodeType', 'sourceId', 'filterQuery')} alertParams={pick(alertParams, 'criteria', 'nodeType', 'sourceId', 'filterQuery')}
validate={validateMetricThreshold} validate={validateMetricThreshold}

View file

@ -68,6 +68,7 @@ describe('Expression', () => {
<Expressions <Expressions
alertsContext={context} alertsContext={context}
alertInterval="1m" alertInterval="1m"
alertThrottle="1m"
alertParams={alertParams} alertParams={alertParams}
errors={[]} errors={[]}
setAlertParams={(key, value) => Reflect.set(alertParams, key, value)} setAlertParams={(key, value) => Reflect.set(alertParams, key, value)}

View file

@ -51,6 +51,7 @@ interface Props {
alertParams: AlertParams; alertParams: AlertParams;
alertsContext: AlertsContextValue<AlertContextMeta>; alertsContext: AlertsContextValue<AlertContextMeta>;
alertInterval: string; alertInterval: string;
alertThrottle: string;
setAlertParams(key: string, value: any): void; setAlertParams(key: string, value: any): void;
setAlertProperty(key: string, value: any): void; setAlertProperty(key: string, value: any): void;
} }
@ -65,7 +66,14 @@ const defaultExpression = {
export { defaultExpression }; export { defaultExpression };
export const Expressions: React.FC<Props> = (props) => { export const Expressions: React.FC<Props> = (props) => {
const { setAlertParams, alertParams, errors, alertsContext, alertInterval } = props; const {
setAlertParams,
alertParams,
errors,
alertsContext,
alertInterval,
alertThrottle,
} = props;
const { source, createDerivedIndexPattern } = useSourceViaHttp({ const { source, createDerivedIndexPattern } = useSourceViaHttp({
sourceId: 'default', sourceId: 'default',
type: 'metrics', type: 'metrics',
@ -399,6 +407,7 @@ export const Expressions: React.FC<Props> = (props) => {
<EuiSpacer size={'m'} /> <EuiSpacer size={'m'} />
<AlertPreview <AlertPreview
alertInterval={alertInterval} alertInterval={alertInterval}
alertThrottle={alertThrottle}
alertType={METRIC_THRESHOLD_ALERT_TYPE_ID} alertType={METRIC_THRESHOLD_ALERT_TYPE_ID}
alertParams={pick(alertParams, 'criteria', 'groupBy', 'filterQuery', 'sourceId')} alertParams={pick(alertParams, 'criteria', 'groupBy', 'filterQuery', 'sourceId')}
showNoDataResults={alertParams.alertOnNoData} showNoDataResults={alertParams.alertOnNoData}

View file

@ -29,6 +29,8 @@ interface PreviewInventoryMetricThresholdAlertParams {
source: InfraSource; source: InfraSource;
lookback: Unit; lookback: Unit;
alertInterval: string; alertInterval: string;
alertThrottle: string;
alertOnNoData: boolean;
} }
export const previewInventoryMetricThresholdAlert = async ({ export const previewInventoryMetricThresholdAlert = async ({
@ -37,6 +39,8 @@ export const previewInventoryMetricThresholdAlert = async ({
source, source,
lookback, lookback,
alertInterval, alertInterval,
alertThrottle,
alertOnNoData,
}: PreviewInventoryMetricThresholdAlertParams) => { }: PreviewInventoryMetricThresholdAlertParams) => {
const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams; const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams;
@ -52,6 +56,10 @@ export const previewInventoryMetricThresholdAlert = async ({
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval); const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds; const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds;
const throttleIntervalInSeconds = getIntervalInSeconds(alertThrottle);
const executionsPerThrottle = Math.floor(
(throttleIntervalInSeconds / alertIntervalInSeconds) * alertResultsPerExecution
);
try { try {
const results = await Promise.all( const results = await Promise.all(
criteria.map((c) => criteria.map((c) =>
@ -66,6 +74,12 @@ export const previewInventoryMetricThresholdAlert = async ({
let numberOfTimesFired = 0; let numberOfTimesFired = 0;
let numberOfNoDataResults = 0; let numberOfNoDataResults = 0;
let numberOfErrors = 0; let numberOfErrors = 0;
let numberOfNotifications = 0;
let throttleTracker = 0;
const notifyWithThrottle = () => {
if (throttleTracker === 0) numberOfNotifications++;
throttleTracker++;
};
for (let i = 0; i < numberOfExecutionBuckets; i++) { for (let i = 0; i < numberOfExecutionBuckets; i++) {
const mappedBucketIndex = Math.floor(i * alertResultsPerExecution); const mappedBucketIndex = Math.floor(i * alertResultsPerExecution);
const allConditionsFiredInMappedBucket = results.every((result) => { const allConditionsFiredInMappedBucket = results.every((result) => {
@ -79,11 +93,27 @@ export const previewInventoryMetricThresholdAlert = async ({
const someConditionsErrorInMappedBucket = results.some((result) => { const someConditionsErrorInMappedBucket = results.some((result) => {
return result[item].isError; return result[item].isError;
}); });
if (allConditionsFiredInMappedBucket) numberOfTimesFired++; if (someConditionsErrorInMappedBucket) {
if (someConditionsNoDataInMappedBucket) numberOfNoDataResults++; numberOfErrors++;
if (someConditionsErrorInMappedBucket) numberOfErrors++; if (alertOnNoData) {
notifyWithThrottle();
}
} else if (someConditionsNoDataInMappedBucket) {
numberOfNoDataResults++;
if (alertOnNoData) {
notifyWithThrottle();
}
} else if (allConditionsFiredInMappedBucket) {
numberOfTimesFired++;
notifyWithThrottle();
} else if (throttleTracker > 0) {
throttleTracker++;
}
if (throttleTracker === executionsPerThrottle) {
throttleTracker = 0;
}
} }
return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors]; return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors, numberOfNotifications];
}); });
return previewResults; return previewResults;

View file

@ -16,11 +16,14 @@ describe('Previewing the metric threshold alert type', () => {
...baseParams, ...baseParams,
lookback: 'h', lookback: 'h',
alertInterval: '1m', alertInterval: '1m',
alertThrottle: '1m',
alertOnNoData: true,
}); });
const [firedResults, noDataResults, errorResults] = ungroupedResult; const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult;
expect(firedResults).toBe(30); expect(firedResults).toBe(30);
expect(noDataResults).toBe(0); expect(noDataResults).toBe(0);
expect(errorResults).toBe(0); expect(errorResults).toBe(0);
expect(notifications).toBe(30);
}); });
test('returns the expected results using a bucket interval shorter than the alert interval', async () => { test('returns the expected results using a bucket interval shorter than the alert interval', async () => {
@ -28,22 +31,42 @@ describe('Previewing the metric threshold alert type', () => {
...baseParams, ...baseParams,
lookback: 'h', lookback: 'h',
alertInterval: '3m', alertInterval: '3m',
alertThrottle: '3m',
alertOnNoData: true,
}); });
const [firedResults, noDataResults, errorResults] = ungroupedResult; const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult;
expect(firedResults).toBe(10); expect(firedResults).toBe(10);
expect(noDataResults).toBe(0); expect(noDataResults).toBe(0);
expect(errorResults).toBe(0); expect(errorResults).toBe(0);
expect(notifications).toBe(10);
}); });
test('returns the expected results using a bucket interval longer than the alert interval', async () => { test('returns the expected results using a bucket interval longer than the alert interval', async () => {
const [ungroupedResult] = await previewMetricThresholdAlert({ const [ungroupedResult] = await previewMetricThresholdAlert({
...baseParams, ...baseParams,
lookback: 'h', lookback: 'h',
alertInterval: '30s', alertInterval: '30s',
alertThrottle: '30s',
alertOnNoData: true,
}); });
const [firedResults, noDataResults, errorResults] = ungroupedResult; const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult;
expect(firedResults).toBe(60); expect(firedResults).toBe(60);
expect(noDataResults).toBe(0); expect(noDataResults).toBe(0);
expect(errorResults).toBe(0); expect(errorResults).toBe(0);
expect(notifications).toBe(60);
});
test('returns the expected results using a throttle interval longer than the alert interval', async () => {
const [ungroupedResult] = await previewMetricThresholdAlert({
...baseParams,
lookback: 'h',
alertInterval: '1m',
alertThrottle: '3m',
alertOnNoData: true,
});
const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult;
expect(firedResults).toBe(30);
expect(noDataResults).toBe(0);
expect(errorResults).toBe(0);
expect(notifications).toBe(15);
}); });
}); });
describe('querying with a groupBy parameter', () => { describe('querying with a groupBy parameter', () => {
@ -56,15 +79,19 @@ describe('Previewing the metric threshold alert type', () => {
}, },
lookback: 'h', lookback: 'h',
alertInterval: '1m', alertInterval: '1m',
alertThrottle: '1m',
alertOnNoData: true,
}); });
const [firedResultsA, noDataResultsA, errorResultsA] = resultA; const [firedResultsA, noDataResultsA, errorResultsA, notificationsA] = resultA;
expect(firedResultsA).toBe(30); expect(firedResultsA).toBe(30);
expect(noDataResultsA).toBe(0); expect(noDataResultsA).toBe(0);
expect(errorResultsA).toBe(0); expect(errorResultsA).toBe(0);
const [firedResultsB, noDataResultsB, errorResultsB] = resultB; expect(notificationsA).toBe(30);
const [firedResultsB, noDataResultsB, errorResultsB, notificationsB] = resultB;
expect(firedResultsB).toBe(60); expect(firedResultsB).toBe(60);
expect(noDataResultsB).toBe(0); expect(noDataResultsB).toBe(0);
expect(errorResultsB).toBe(0); expect(errorResultsB).toBe(0);
expect(notificationsB).toBe(60);
}); });
}); });
describe('querying a data set with a period of No Data', () => { describe('querying a data set with a period of No Data', () => {
@ -82,11 +109,14 @@ describe('Previewing the metric threshold alert type', () => {
}, },
lookback: 'h', lookback: 'h',
alertInterval: '1m', alertInterval: '1m',
alertThrottle: '1m',
alertOnNoData: true,
}); });
const [firedResults, noDataResults, errorResults] = ungroupedResult; const [firedResults, noDataResults, errorResults, notifications] = ungroupedResult;
expect(firedResults).toBe(25); expect(firedResults).toBe(25);
expect(noDataResults).toBe(10); expect(noDataResults).toBe(10);
expect(errorResults).toBe(0); expect(errorResults).toBe(0);
expect(notifications).toBe(35);
}); });
}); });
}); });

View file

@ -28,6 +28,8 @@ interface PreviewMetricThresholdAlertParams {
config: InfraSource['configuration']; config: InfraSource['configuration'];
lookback: Unit; lookback: Unit;
alertInterval: string; alertInterval: string;
alertThrottle: string;
alertOnNoData: boolean;
end?: number; end?: number;
overrideLookbackIntervalInSeconds?: number; overrideLookbackIntervalInSeconds?: number;
} }
@ -43,6 +45,8 @@ export const previewMetricThresholdAlert: (
config, config,
lookback, lookback,
alertInterval, alertInterval,
alertThrottle,
alertOnNoData,
end = Date.now(), end = Date.now(),
overrideLookbackIntervalInSeconds, overrideLookbackIntervalInSeconds,
}, },
@ -77,6 +81,11 @@ export const previewMetricThresholdAlert: (
// Now determine how to interpolate this histogram based on the alert interval // Now determine how to interpolate this histogram based on the alert interval
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval); const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds; const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds;
const throttleIntervalInSeconds = Math.max(
getIntervalInSeconds(alertThrottle),
alertIntervalInSeconds
);
const previewResults = await Promise.all( const previewResults = await Promise.all(
groups.map(async (group) => { groups.map(async (group) => {
// Interpolate the buckets returned by evaluateAlert and return a count of how many of these // Interpolate the buckets returned by evaluateAlert and return a count of how many of these
@ -90,6 +99,12 @@ export const previewMetricThresholdAlert: (
let numberOfTimesFired = 0; let numberOfTimesFired = 0;
let numberOfNoDataResults = 0; let numberOfNoDataResults = 0;
let numberOfErrors = 0; let numberOfErrors = 0;
let numberOfNotifications = 0;
let throttleTracker = 0;
const notifyWithThrottle = () => {
if (throttleTracker === 0) numberOfNotifications++;
throttleTracker += alertIntervalInSeconds;
};
for (let i = 0; i < numberOfExecutionBuckets; i++) { for (let i = 0; i < numberOfExecutionBuckets; i++) {
const mappedBucketIndex = Math.floor(i * alertResultsPerExecution); const mappedBucketIndex = Math.floor(i * alertResultsPerExecution);
const allConditionsFiredInMappedBucket = alertResults.every( const allConditionsFiredInMappedBucket = alertResults.every(
@ -102,11 +117,27 @@ export const previewMetricThresholdAlert: (
const someConditionsErrorInMappedBucket = alertResults.some((alertResult) => { const someConditionsErrorInMappedBucket = alertResults.some((alertResult) => {
return alertResult[group].isError; return alertResult[group].isError;
}); });
if (allConditionsFiredInMappedBucket) numberOfTimesFired++; if (someConditionsErrorInMappedBucket) {
if (someConditionsNoDataInMappedBucket) numberOfNoDataResults++; numberOfErrors++;
if (someConditionsErrorInMappedBucket) numberOfErrors++; if (alertOnNoData) {
notifyWithThrottle();
}
} else if (someConditionsNoDataInMappedBucket) {
numberOfNoDataResults++;
if (alertOnNoData) {
notifyWithThrottle();
}
} else if (allConditionsFiredInMappedBucket) {
numberOfTimesFired++;
notifyWithThrottle();
} else if (throttleTracker > 0) {
throttleTracker += alertIntervalInSeconds;
}
if (throttleTracker >= throttleIntervalInSeconds) {
throttleTracker = 0;
}
} }
return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors]; return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors, numberOfNotifications];
}) })
); );
return previewResults; return previewResults;
@ -114,7 +145,15 @@ export const previewMetricThresholdAlert: (
if (isTooManyBucketsPreviewException(e)) { if (isTooManyBucketsPreviewException(e)) {
// If there's too much data on the first request, recursively slice the lookback interval // If there's too much data on the first request, recursively slice the lookback interval
// until all the data can be retrieved // until all the data can be retrieved
const basePreviewParams = { callCluster, params, config, lookback, alertInterval }; const basePreviewParams = {
callCluster,
params,
config,
lookback,
alertInterval,
alertThrottle,
alertOnNoData,
};
const { maxBuckets } = e; const { maxBuckets } = e;
// If this is still the first iteration, try to get the number of groups in order to // If this is still the first iteration, try to get the number of groups in order to
// calculate max buckets. If this fails, just estimate based on 1 group // calculate max buckets. If this fails, just estimate based on 1 group
@ -159,7 +198,7 @@ export const previewMetricThresholdAlert: (
.reduce((a, b) => { .reduce((a, b) => {
if (!a) return b; if (!a) return b;
if (!b) return a; if (!b) return a;
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];
}) })
); );
return zippedResult; return zippedResult;

View file

@ -30,7 +30,16 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
}, },
}, },
framework.router.handleLegacyErrors(async (requestContext, request, response) => { framework.router.handleLegacyErrors(async (requestContext, request, response) => {
const { criteria, filterQuery, lookback, sourceId, alertType, alertInterval } = request.body; const {
criteria,
filterQuery,
lookback,
sourceId,
alertType,
alertInterval,
alertThrottle,
alertOnNoData,
} = request.body;
const callCluster = (endpoint: string, opts: Record<string, any>) => { const callCluster = (endpoint: string, opts: Record<string, any>) => {
return callWithRequest(requestContext, endpoint, opts); return callWithRequest(requestContext, endpoint, opts);
@ -51,22 +60,26 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
lookback, lookback,
config: source.configuration, config: source.configuration,
alertInterval, alertInterval,
alertThrottle,
alertOnNoData,
}); });
const numberOfGroups = previewResult.length; const numberOfGroups = previewResult.length;
const resultTotals = previewResult.reduce( const resultTotals = previewResult.reduce(
(totals, [firedResult, noDataResult, errorResult]) => { (totals, [firedResult, noDataResult, errorResult, notifications]) => {
return { return {
...totals, ...totals,
fired: totals.fired + firedResult, fired: totals.fired + firedResult,
noData: totals.noData + noDataResult, noData: totals.noData + noDataResult,
error: totals.error + errorResult, error: totals.error + errorResult,
notifications: totals.notifications + notifications,
}; };
}, },
{ {
fired: 0, fired: 0,
noData: 0, noData: 0,
error: 0, error: 0,
notifications: 0,
} }
); );
return response.ok({ return response.ok({
@ -84,22 +97,26 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
lookback, lookback,
source, source,
alertInterval, alertInterval,
alertThrottle,
alertOnNoData,
}); });
const numberOfGroups = previewResult.length; const numberOfGroups = previewResult.length;
const resultTotals = previewResult.reduce( const resultTotals = previewResult.reduce(
(totals, [firedResult, noDataResult, errorResult]) => { (totals, [firedResult, noDataResult, errorResult, notifications]) => {
return { return {
...totals, ...totals,
fired: totals.fired + firedResult, fired: totals.fired + firedResult,
noData: totals.noData + noDataResult, noData: totals.noData + noDataResult,
error: totals.error + errorResult, error: totals.error + errorResult,
notifications: totals.notifications + notifications,
}; };
}, },
{ {
fired: 0, fired: 0,
noData: 0, noData: 0,
error: 0, error: 0,
notifications: 0,
} }
); );

View file

@ -8677,8 +8677,7 @@
"xpack.infra.metrics.alertFlyout.expression.metric.whenLabel": "タイミング", "xpack.infra.metrics.alertFlyout.expression.metric.whenLabel": "タイミング",
"xpack.infra.metrics.alertFlyout.filterHelpText": "KQL式を使用して、アラートトリガーの範囲を制限します。", "xpack.infra.metrics.alertFlyout.filterHelpText": "KQL式を使用して、アラートトリガーの範囲を制限します。",
"xpack.infra.metrics.alertFlyout.filterLabel": "フィルター(任意)", "xpack.infra.metrics.alertFlyout.filterLabel": "フィルター(任意)",
"xpack.infra.metrics.alertFlyout.firedTime": "時間", "xpack.infra.metrics.alertFlyout.firedTimes": "{fired, plural, one {# 時間} other {# 回数}}",
"xpack.infra.metrics.alertFlyout.firedTimes": "回数",
"xpack.infra.metrics.alertFlyout.hourLabel": "時間", "xpack.infra.metrics.alertFlyout.hourLabel": "時間",
"xpack.infra.metrics.alertFlyout.lastDayLabel": "昨日", "xpack.infra.metrics.alertFlyout.lastDayLabel": "昨日",
"xpack.infra.metrics.alertFlyout.lastHourLabel": "過去1時間", "xpack.infra.metrics.alertFlyout.lastHourLabel": "過去1時間",

View file

@ -8683,8 +8683,7 @@
"xpack.infra.metrics.alertFlyout.expression.metric.whenLabel": "当", "xpack.infra.metrics.alertFlyout.expression.metric.whenLabel": "当",
"xpack.infra.metrics.alertFlyout.filterHelpText": "使用 KQL 表达式限制告警触发器的范围。", "xpack.infra.metrics.alertFlyout.filterHelpText": "使用 KQL 表达式限制告警触发器的范围。",
"xpack.infra.metrics.alertFlyout.filterLabel": "筛选(可选)", "xpack.infra.metrics.alertFlyout.filterLabel": "筛选(可选)",
"xpack.infra.metrics.alertFlyout.firedTime": "次", "xpack.infra.metrics.alertFlyout.firedTimes": "{fired, plural, one {# 次} other {# 次}}",
"xpack.infra.metrics.alertFlyout.firedTimes": "次",
"xpack.infra.metrics.alertFlyout.hourLabel": "小时", "xpack.infra.metrics.alertFlyout.hourLabel": "小时",
"xpack.infra.metrics.alertFlyout.lastDayLabel": "昨天", "xpack.infra.metrics.alertFlyout.lastDayLabel": "昨天",
"xpack.infra.metrics.alertFlyout.lastHourLabel": "上一小时", "xpack.infra.metrics.alertFlyout.lastHourLabel": "上一小时",

View file

@ -248,6 +248,7 @@ export const AlertForm = ({
<AlertParamsExpressionComponent <AlertParamsExpressionComponent
alertParams={alert.params} alertParams={alert.params}
alertInterval={`${alertInterval ?? 1}${alertIntervalUnit}`} alertInterval={`${alertInterval ?? 1}${alertIntervalUnit}`}
alertThrottle={`${alertThrottle ?? 1}${alertThrottleUnit}`}
errors={errors} errors={errors}
setAlertParams={setAlertParams} setAlertParams={setAlertParams}
setAlertProperty={setAlertProperty} setAlertProperty={setAlertProperty}

View file

@ -134,6 +134,7 @@ export interface AlertTypeParamsExpressionProps<
> { > {
alertParams: AlertParamsType; alertParams: AlertParamsType;
alertInterval: string; alertInterval: string;
alertThrottle: string;
setAlertParams: (property: string, value: any) => void; setAlertParams: (property: string, value: any) => void;
setAlertProperty: (key: string, value: any) => void; setAlertProperty: (key: string, value: any) => void;
errors: IErrorObject; errors: IErrorObject;