mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
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:
parent
e86cc0c84c
commit
67b6f85f6b
6 changed files with 117 additions and 20 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const formatHighPercision = (val: number) => {
|
||||
export const formatHighPrecision = (val: number) => {
|
||||
return Number(val).toLocaleString('en', {
|
||||
maximumFractionDigits: 5,
|
||||
});
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue