Fix(alert): reason for non percentage threshold metrics (#138138)

* Fix typo

* Fix metric threshold reason format

* Add test for percentage metric

* Fix formatter usage

* Invert if condition

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Kevin Delemme 2022-08-15 10:15:17 -04:00 committed by GitHub
parent e86cc0c84c
commit 67b6f85f6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 20 deletions

View file

@ -5,7 +5,7 @@
* 2.0.
*/
export const formatHighPercision = (val: number) => {
export const formatHighPrecision = (val: number) => {
return Number(val).toLocaleString('en', {
maximumFractionDigits: 5,
});

View file

@ -9,7 +9,7 @@ import { createBytesFormatter } from './bytes';
import { formatNumber } from './number';
import { formatPercent } from './percent';
import { InventoryFormatterType } from '../inventory_models/types';
import { formatHighPercision } from './high_precision';
import { formatHighPrecision } from './high_precision';
import { InfraWaffleMapDataFormat } from './types';
export const FORMATTERS = {
@ -22,7 +22,7 @@ export const FORMATTERS = {
// bytes in bits formatted string out
bits: createBytesFormatter(InfraWaffleMapDataFormat.bitsDecimal),
percent: formatPercent,
highPercision: formatHighPercision,
highPrecision: formatHighPrecision,
};
export const createFormatter =

View file

@ -143,7 +143,7 @@ export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPr
<ChartSectionVis
type="bar"
stacked={true}
formatter="highPercision"
formatter="highPrecision"
formatterTemplate={'{{value}} ms'}
seriesOverrides={{
read: {

View file

@ -31,7 +31,7 @@ export const InventoryFormatterTypeRT = rt.keyof({
bytes: null,
number: null,
percent: null,
highPercision: null,
highPrecision: null,
});
export type InventoryFormatterType = rt.TypeOf<typeof InventoryFormatterTypeRT>;
export type InventoryItemType = rt.TypeOf<typeof ItemTypeRT>;

View file

@ -29,6 +29,7 @@ import {
createMetricThresholdExecutor,
FIRED_ACTIONS,
NO_DATA_ACTIONS,
WARNING_ACTIONS,
} from './metric_threshold_executor';
import { Evaluation } from './lib/evaluate_rule';
import type { LogMeta, Logger } from '@kbn/logging';
@ -1504,6 +1505,91 @@ describe('The metric threshold alert type', () => {
expect(mostRecentAction(instanceID)).toBeErrorAction();
});
});
describe('querying the entire infrastructure with warning threshold', () => {
afterAll(() => clearInstances());
const instanceID = '*';
const execute = () =>
executor({
...mockOptions,
services,
params: {
sourceId: 'default',
criteria: [
{
...baseNonCountCriterion,
comparator: Comparator.GT,
threshold: [9999],
},
],
},
});
const setResults = ({
comparator = Comparator.GT,
threshold = [9999],
warningComparator = Comparator.GT,
warningThreshold = [2.49],
metric = 'test.metric.1',
currentValue = 7.59,
shouldWarn = false,
}) =>
setEvaluationResults([
{
'*': {
...baseNonCountCriterion,
comparator,
threshold,
warningComparator,
warningThreshold,
metric,
currentValue,
timestamp: new Date().toISOString(),
shouldFire: false,
shouldWarn,
isNoData: false,
},
},
]);
test('warns as expected with the > comparator', async () => {
setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true });
await execute();
expect(mostRecentAction(instanceID)).toBeWarnAction();
setResults({ warningThreshold: [2.49], currentValue: 1.23, shouldWarn: false });
await execute();
expect(mostRecentAction(instanceID)).toBe(undefined);
});
test('reports expected warning values to the action context', async () => {
setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true });
await execute();
const { action } = mostRecentAction(instanceID);
expect(action.group).toBe('*');
expect(action.reason).toBe(
'test.metric.1 is 2.5 in the last 1 min for all hosts. Alert when > 2.49.'
);
});
test('reports expected warning values to the action context for percentage metric', async () => {
setResults({
warningThreshold: [0.81],
currentValue: 0.82,
shouldWarn: true,
metric: 'system.cpu.user.pct',
});
await execute();
const { action } = mostRecentAction(instanceID);
expect(action.group).toBe('*');
expect(action.reason).toBe(
'system.cpu.user.pct is 82% in the last 1 min for all hosts. Alert when > 81%.'
);
});
});
});
const createMockStaticConfiguration = (sources: any) => ({
@ -1622,6 +1708,14 @@ expect.extend({
pass,
};
},
toBeWarnAction(action?: Action) {
const pass = action?.id === WARNING_ACTIONS.id && action?.action.alertState === 'WARNING';
const message = () => `expected ${JSON.stringify(action)} to be an WARNING action`;
return {
message,
pass,
};
},
toBeNoDataAction(action?: Action) {
const pass = action?.id === NO_DATA_ACTIONS.id && action?.action.alertState === 'NO DATA';
const message = () => `expected ${action} to be a NO DATA action`;
@ -1645,9 +1739,8 @@ declare global {
namespace jest {
interface Matchers<R> {
toBeAlertAction(action?: Action): R;
toBeWarnAction(action?: Action): R;
toBeNoDataAction(action?: Action): R;
toBeErrorAction(action?: Action): R;
}
}

View file

@ -322,28 +322,32 @@ const formatAlertResult = <AlertResult>(
alertResult;
const noDataValue = i18n.translate(
'xpack.infra.metrics.alerting.threshold.noDataFormattedValue',
{
defaultMessage: '[NO DATA]',
}
{ defaultMessage: '[NO DATA]' }
);
if (!metric.endsWith('.pct'))
const thresholdToFormat = useWarningThreshold ? warningThreshold! : threshold;
const comparatorToUse = useWarningThreshold ? warningComparator! : comparator;
if (metric.endsWith('.pct')) {
const formatter = createFormatter('percent');
return {
...alertResult,
currentValue: currentValue ?? noDataValue,
currentValue:
currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue,
threshold: Array.isArray(thresholdToFormat)
? thresholdToFormat.map((v: number) => formatter(v))
: formatter(thresholdToFormat),
comparator: comparatorToUse,
};
const formatter = createFormatter('percent');
const thresholdToFormat = useWarningThreshold ? warningThreshold! : threshold;
const comparatorToFormat = useWarningThreshold ? warningComparator! : comparator;
}
const formatter = createFormatter('highPrecision');
return {
...alertResult,
currentValue:
currentValue !== null && typeof currentValue !== 'undefined'
? formatter(currentValue)
: noDataValue,
currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue,
threshold: Array.isArray(thresholdToFormat)
? thresholdToFormat.map((v: number) => formatter(v))
: thresholdToFormat,
comparator: comparatorToFormat,
: formatter(thresholdToFormat),
comparator: comparatorToUse,
};
};