[Metrics UI] Display Too Many Buckets error when previewing Inventory Alerts (#70508)

* [Metrics UI] Display Too Many Buckets error when previewing Inventory Alerts

* Fix typecheck
This commit is contained in:
Zacqary Adam Xeper 2020-07-07 10:05:50 -05:00 committed by GitHub
parent e7c54d3684
commit 5e869b0e77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 103 additions and 74 deletions

View file

@ -5,6 +5,10 @@
*/
import { mapValues, last, first } from 'lodash';
import moment from 'moment';
import {
isTooManyBucketsPreviewException,
TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
} from '../../../../common/alerting/metrics';
import {
InfraDatabaseSearchResponse,
CallWithRequestParams,
@ -57,18 +61,23 @@ export const evaluateCondition = async (
const comparisonFunction = comparatorMap[comparator];
return mapValues(currentValues, (value) => ({
...condition,
shouldFire:
value !== undefined &&
value !== null &&
(Array.isArray(value)
? value.map((v) => comparisonFunction(Number(v), threshold))
: comparisonFunction(value, threshold)),
isNoData: value === null,
isError: value === undefined,
currentValue: getCurrentValue(value),
}));
const result = mapValues(currentValues, (value) => {
if (isTooManyBucketsPreviewException(value)) throw value;
return {
...condition,
shouldFire:
value !== undefined &&
value !== null &&
(Array.isArray(value)
? value.map((v) => comparisonFunction(Number(v), threshold))
: comparisonFunction(value as number, threshold)),
isNoData: value === null,
isError: value === undefined,
currentValue: getCurrentValue(value),
};
}) as unknown; // Typescript doesn't seem to know what `throw` is doing
return result as Record<string, ConditionResult>;
};
const getCurrentValue: (value: any) => number = (value) => {
@ -99,21 +108,36 @@ const getData = async (
timerange,
includeTimeseries: Boolean(timerange.lookbackSize),
};
try {
const { nodes } = await snapshot.getNodes(esClient, options);
const { nodes } = await snapshot.getNodes(esClient, options);
return nodes.reduce((acc, n) => {
const nodePathItem = last(n.path) as any;
const m = first(n.metrics);
if (m && m.value && m.timeseries) {
const { timeseries } = m;
const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>;
acc[nodePathItem.label] = values;
} else {
acc[nodePathItem.label] = m && m.value;
return nodes.reduce((acc, n) => {
const nodePathItem = last(n.path) as any;
const m = first(n.metrics);
if (m && m.value && m.timeseries) {
const { timeseries } = m;
const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>;
acc[nodePathItem.label] = values;
} else {
acc[nodePathItem.label] = m && m.value;
}
return acc;
}, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>);
} catch (e) {
if (timerange.lookbackSize) {
// This code should only ever be reached when previewing the alert, not executing it
const causedByType = e.body?.error?.caused_by?.type;
if (causedByType === 'too_many_buckets_exception') {
return {
'*': {
[TOO_MANY_BUCKETS_PREVIEW_EXCEPTION]: true,
maxBuckets: e.body.error.caused_by.max_buckets,
},
};
}
}
return acc;
}, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>);
return { '*': undefined };
}
};
const comparatorMap = {

View file

@ -6,6 +6,10 @@
import { Unit } from '@elastic/datemath';
import { first } from 'lodash';
import { InventoryMetricConditions } from './types';
import {
TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
isTooManyBucketsPreviewException,
} from '../../../../common/alerting/metrics';
import { ILegacyScopedClusterClient } from '../../../../../../../src/core/server';
import { InfraSource } from '../../../../common/http_api/source_api';
import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds';
@ -46,38 +50,43 @@ export const previewInventoryMetricThresholdAlert = async ({
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) 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) => {
const shouldFire = result[item].shouldFire as boolean[];
return shouldFire[Math.floor(i * alertResultsPerExecution)];
})
? 1
: 0),
0
try {
const results = await Promise.all(
criteria.map((c) =>
evaluateCondition(c, nodeType, config, callCluster, filterQuery, lookbackSize)
)
);
});
return previewResults;
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) => {
const shouldFire = result[item].shouldFire as boolean[];
return shouldFire[Math.floor(i * alertResultsPerExecution)];
})
? 1
: 0),
0
);
});
return previewResults;
} catch (e) {
if (!isTooManyBucketsPreviewException(e)) throw e;
const { maxBuckets } = e;
throw new Error(`${TOO_MANY_BUCKETS_PREVIEW_EXCEPTION}:${maxBuckets}`);
}
};

View file

@ -6,7 +6,6 @@
import { mapValues, first, last, isNaN } from 'lodash';
import {
TooManyBucketsPreviewExceptionMetadata,
isTooManyBucketsPreviewException,
TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
} from '../../../../../common/alerting/metrics';
@ -58,22 +57,19 @@ export const evaluateAlert = (
);
const { threshold, comparator } = criterion;
const comparisonFunction = comparatorMap[comparator];
return mapValues(
currentValues,
(values: number | number[] | null | TooManyBucketsPreviewExceptionMetadata) => {
if (isTooManyBucketsPreviewException(values)) throw values;
return {
...criterion,
metric: criterion.metric ?? DOCUMENT_COUNT_I18N,
currentValue: Array.isArray(values) ? last(values) : NaN,
shouldFire: Array.isArray(values)
? values.map((value) => comparisonFunction(value, threshold))
: [false],
isNoData: values === null,
isError: isNaN(values),
};
}
);
return mapValues(currentValues, (values: number | number[] | null) => {
if (isTooManyBucketsPreviewException(values)) throw values;
return {
...criterion,
metric: criterion.metric ?? DOCUMENT_COUNT_I18N,
currentValue: Array.isArray(values) ? last(values) : NaN,
shouldFire: Array.isArray(values)
? values.map((value) => comparisonFunction(value, threshold))
: [false],
isNoData: values === null,
isError: isNaN(values),
};
});
})
);
};