Skip to content

Commit

Permalink
[Metrics UI] Display Too Many Buckets error when previewing Inventory…
Browse files Browse the repository at this point in the history
… Alerts (#70508)

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

* Fix typecheck
  • Loading branch information
Zacqary authored Jul 7, 2020
1 parent e7c54d3 commit 5e869b0
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -46,38 +50,43 @@ export const previewInventoryMetricThresholdAlert = async ({

const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds;
try {
const results = await Promise.all(
criteria.map((c) =>
evaluateCondition(c, nodeType, config, callCluster, filterQuery, lookbackSize)
)
);

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 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
);
});
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;
return previewResults;
} catch (e) {
if (!isTooManyBucketsPreviewException(e)) throw e;
const { maxBuckets } = e;
throw new Error(`${TOO_MANY_BUCKETS_PREVIEW_EXCEPTION}:${maxBuckets}`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import { mapValues, first, last, isNaN } from 'lodash';
import {
TooManyBucketsPreviewExceptionMetadata,
isTooManyBucketsPreviewException,
TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
} from '../../../../../common/alerting/metrics';
Expand Down Expand Up @@ -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),
};
});
})
);
};
Expand Down

0 comments on commit 5e869b0

Please sign in to comment.