[Metrics UI] Fix previewing of No Data results (#73753)
This commit is contained in:
parent
5e86d2f848
commit
c2d8869cca
|
@ -194,7 +194,7 @@ export const AlertPreview: React.FC<Props> = (props) => {
|
|||
plural: previewResult.resultTotals.noData !== 1 ? 's' : '',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
) : null}{' '}
|
||||
{previewResult.resultTotals.error ? (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.alertPreviewErrorResult"
|
||||
|
|
|
@ -368,6 +368,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
validate={validateMetricThreshold}
|
||||
fetch={alertsContext.http.fetch}
|
||||
groupByDisplayName={alertParams.nodeType}
|
||||
showNoDataResults={alertParams.alertOnNoData}
|
||||
/>
|
||||
<EuiSpacer size={'m'} />
|
||||
</>
|
||||
|
|
|
@ -23,9 +23,9 @@ import { InfraSourceConfiguration } from '../../sources';
|
|||
import { UNGROUPED_FACTORY_KEY } from '../common/utils';
|
||||
|
||||
type ConditionResult = InventoryMetricConditions & {
|
||||
shouldFire: boolean | boolean[];
|
||||
shouldFire: boolean[];
|
||||
currentValue: number;
|
||||
isNoData: boolean;
|
||||
isNoData: boolean[];
|
||||
isError: boolean;
|
||||
};
|
||||
|
||||
|
@ -71,8 +71,8 @@ export const evaluateCondition = async (
|
|||
value !== null &&
|
||||
(Array.isArray(value)
|
||||
? value.map((v) => comparisonFunction(Number(v), threshold))
|
||||
: comparisonFunction(value as number, threshold)),
|
||||
isNoData: value === null,
|
||||
: [comparisonFunction(value as number, threshold)]),
|
||||
isNoData: Array.isArray(value) ? value.map((v) => v === null) : [value === null],
|
||||
isError: value === undefined,
|
||||
currentValue: getCurrentValue(value),
|
||||
};
|
||||
|
|
|
@ -3,7 +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 { first, get } from 'lodash';
|
||||
import { first, get, last } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { toMetricOpt } from '../../../../common/snapshot_metric_i18n';
|
||||
|
@ -56,11 +56,14 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
|
|||
for (const item of inventoryItems) {
|
||||
const alertInstance = services.alertInstanceFactory(`${item}`);
|
||||
// AND logic; all criteria must be across the threshold
|
||||
const shouldAlertFire = results.every((result) => result[item].shouldFire);
|
||||
const shouldAlertFire = results.every((result) =>
|
||||
// Grab the result of the most recent bucket
|
||||
last(result[item].shouldFire)
|
||||
);
|
||||
|
||||
// AND logic; because we need to evaluate all criteria, if one of them reports no data then the
|
||||
// whole alert is in a No Data/Error state
|
||||
const isNoData = results.some((result) => result[item].isNoData);
|
||||
const isNoData = results.some((result) => last(result[item].isNoData));
|
||||
const isError = results.some((result) => result[item].isError);
|
||||
|
||||
const nextState = isError
|
||||
|
|
|
@ -59,28 +59,29 @@ export const previewInventoryMetricThresholdAlert = async ({
|
|||
|
||||
const inventoryItems = Object.keys(first(results) as any);
|
||||
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) => {
|
||||
let numberOfTimesFired = 0;
|
||||
let numberOfNoDataResults = 0;
|
||||
let numberOfErrors = 0;
|
||||
for (let i = 0; i < numberOfExecutionBuckets; i++) {
|
||||
const mappedBucketIndex = Math.floor(i * alertResultsPerExecution);
|
||||
const allConditionsFiredInMappedBucket = results.every((result) => {
|
||||
const shouldFire = result[item].shouldFire as boolean[];
|
||||
return shouldFire[Math.floor(i * alertResultsPerExecution)];
|
||||
})
|
||||
? 1
|
||||
: 0),
|
||||
0
|
||||
);
|
||||
return shouldFire[mappedBucketIndex];
|
||||
});
|
||||
const someConditionsNoDataInMappedBucket = results.some((result) => {
|
||||
const hasNoData = result[item].isNoData as boolean[];
|
||||
return hasNoData[mappedBucketIndex];
|
||||
});
|
||||
const someConditionsErrorInMappedBucket = results.some((result) => {
|
||||
return result[item].isError;
|
||||
});
|
||||
if (allConditionsFiredInMappedBucket) numberOfTimesFired++;
|
||||
if (someConditionsNoDataInMappedBucket) numberOfNoDataResults++;
|
||||
if (someConditionsErrorInMappedBucket) numberOfErrors++;
|
||||
}
|
||||
return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors];
|
||||
});
|
||||
|
||||
return previewResults;
|
||||
|
|
|
@ -72,7 +72,9 @@ export const evaluateAlert = (
|
|||
typeof point.value === 'number' && comparisonFunction(point.value, threshold)
|
||||
)
|
||||
: [false],
|
||||
isNoData: (Array.isArray(points) ? last(points)?.value : points) === null,
|
||||
isNoData: Array.isArray(points)
|
||||
? points.map((point) => point?.value === null || point === null)
|
||||
: [points === null],
|
||||
isError: isNaN(Array.isArray(points) ? last(points)?.value : points),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -45,7 +45,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
|
|||
);
|
||||
// AND logic; because we need to evaluate all criteria, if one of them reports no data then the
|
||||
// whole alert is in a No Data/Error state
|
||||
const isNoData = alertResults.some((result) => result[group].isNoData);
|
||||
const isNoData = alertResults.some((result) => last(result[group].isNoData));
|
||||
const isError = alertResults.some((result) => result[group].isError);
|
||||
|
||||
const nextState = isError
|
||||
|
|
|
@ -36,7 +36,7 @@ export const previewMetricThresholdAlert: (
|
|||
params: PreviewMetricThresholdAlertParams,
|
||||
iterations?: number,
|
||||
precalculatedNumberOfGroups?: number
|
||||
) => Promise<Array<number | null>> = async (
|
||||
) => Promise<number[][]> = async (
|
||||
{
|
||||
callCluster,
|
||||
params,
|
||||
|
@ -77,15 +77,6 @@ export const previewMetricThresholdAlert: (
|
|||
const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds;
|
||||
const previewResults = await Promise.all(
|
||||
groups.map(async (group) => {
|
||||
const isNoData = alertResults.some((alertResult) => alertResult[group].isNoData);
|
||||
if (isNoData) {
|
||||
return null;
|
||||
}
|
||||
const isError = alertResults.some((alertResult) => alertResult[group].isError);
|
||||
if (isError) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Interpolate the buckets returned by evaluateAlert and return a count of how many of these
|
||||
// buckets would have fired the alert. If the alert interval and bucket interval are the same,
|
||||
// this will be a 1:1 evaluation of the alert results. If these are different, the interpolation
|
||||
|
@ -95,14 +86,25 @@ export const previewMetricThresholdAlert: (
|
|||
numberOfResultBuckets / alertResultsPerExecution
|
||||
);
|
||||
let numberOfTimesFired = 0;
|
||||
let numberOfNoDataResults = 0;
|
||||
let numberOfErrors = 0;
|
||||
for (let i = 0; i < numberOfExecutionBuckets; i++) {
|
||||
const mappedBucketIndex = Math.floor(i * alertResultsPerExecution);
|
||||
const allConditionsFiredInMappedBucket = alertResults.every(
|
||||
(alertResult) => alertResult[group].shouldFire[mappedBucketIndex]
|
||||
);
|
||||
const someConditionsNoDataInMappedBucket = alertResults.some((alertResult) => {
|
||||
const hasNoData = alertResult[group].isNoData as boolean[];
|
||||
return hasNoData[mappedBucketIndex];
|
||||
});
|
||||
const someConditionsErrorInMappedBucket = alertResults.some((alertResult) => {
|
||||
return alertResult[group].isError;
|
||||
});
|
||||
if (allConditionsFiredInMappedBucket) numberOfTimesFired++;
|
||||
if (someConditionsNoDataInMappedBucket) numberOfNoDataResults++;
|
||||
if (someConditionsErrorInMappedBucket) numberOfErrors++;
|
||||
}
|
||||
return numberOfTimesFired;
|
||||
return [numberOfTimesFired, numberOfNoDataResults, numberOfErrors];
|
||||
})
|
||||
);
|
||||
return previewResults;
|
||||
|
@ -152,9 +154,9 @@ export const previewMetricThresholdAlert: (
|
|||
// so filter these results out entirely and only regard the resultA portion
|
||||
.filter((value) => typeof value !== 'undefined')
|
||||
.reduce((a, b) => {
|
||||
if (typeof a !== 'number') return a;
|
||||
if (typeof b !== 'number') return b;
|
||||
return a + b;
|
||||
if (!a) return b;
|
||||
if (!b) return a;
|
||||
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
||||
})
|
||||
);
|
||||
return zippedResult as any;
|
||||
|
|
|
@ -55,10 +55,13 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
|
||||
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 };
|
||||
(totals, [firedResult, noDataResult, errorResult]) => {
|
||||
return {
|
||||
...totals,
|
||||
fired: totals.fired + firedResult,
|
||||
noData: totals.noData + noDataResult,
|
||||
error: totals.error + errorResult,
|
||||
};
|
||||
},
|
||||
{
|
||||
fired: 0,
|
||||
|
@ -66,7 +69,6 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
error: 0,
|
||||
}
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: alertPreviewSuccessResponsePayloadRT.encode({
|
||||
numberOfGroups,
|
||||
|
@ -86,10 +88,13 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
|
||||
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 };
|
||||
(totals, [firedResult, noDataResult, errorResult]) => {
|
||||
return {
|
||||
...totals,
|
||||
fired: totals.fired + firedResult,
|
||||
noData: totals.noData + noDataResult,
|
||||
error: totals.error + errorResult,
|
||||
};
|
||||
},
|
||||
{
|
||||
fired: 0,
|
||||
|
|
Loading…
Reference in a new issue