mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[SLO] Add support for document count to custom metric indicator (#170913)
## 🍒 Summary
This PR fixes #170905 by adding the aggregation menu to the Custom
Metric indicator to allow the user to pick either `doc_count` or `sum`
for the aggregation.
<img width="1152" alt="image"
src="35aea8bd
-d21c-4780-bad6-1efe5fc8902b">
This commit is contained in:
parent
e0da2ae3da
commit
b9c08bac92
12 changed files with 274 additions and 73 deletions
|
@ -136,22 +136,29 @@ const timesliceMetricIndicatorSchema = t.type({
|
|||
]),
|
||||
});
|
||||
|
||||
const metricCustomValidAggregations = t.keyof({
|
||||
sum: true,
|
||||
});
|
||||
const metricCustomDocCountMetric = t.intersection([
|
||||
t.type({
|
||||
name: t.string,
|
||||
aggregation: t.literal('doc_count'),
|
||||
}),
|
||||
t.partial({
|
||||
filter: t.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
const metricCustomBasicMetric = t.intersection([
|
||||
t.type({
|
||||
name: t.string,
|
||||
aggregation: t.literal('sum'),
|
||||
field: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
filter: t.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
const metricCustomMetricDef = t.type({
|
||||
metrics: t.array(
|
||||
t.intersection([
|
||||
t.type({
|
||||
name: t.string,
|
||||
aggregation: metricCustomValidAggregations,
|
||||
field: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
filter: t.string,
|
||||
}),
|
||||
])
|
||||
),
|
||||
metrics: t.array(t.union([metricCustomBasicMetric, metricCustomDocCountMetric])),
|
||||
equation: t.string,
|
||||
});
|
||||
const metricCustomIndicatorTypeSchema = t.literal('sli.metric.custom');
|
||||
|
@ -267,6 +274,8 @@ export {
|
|||
kqlCustomIndicatorTypeSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
metricCustomIndicatorTypeSchema,
|
||||
metricCustomDocCountMetric,
|
||||
metricCustomBasicMetric,
|
||||
timesliceMetricComparatorMapping,
|
||||
timesliceMetricIndicatorSchema,
|
||||
timesliceMetricIndicatorTypeSchema,
|
||||
|
|
|
@ -22,6 +22,10 @@ import { first, range, xor } from 'lodash';
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
|
||||
import {
|
||||
aggValueToLabel,
|
||||
CUSTOM_METRIC_AGGREGATION_OPTIONS,
|
||||
} from '../../helpers/aggregation_options';
|
||||
import { createOptionsFromFields, Option } from '../../helpers/create_options';
|
||||
import { CreateSLOForm } from '../../types';
|
||||
import { QueryBuilder } from '../common/query_builder';
|
||||
|
@ -62,7 +66,8 @@ const metricTooltip = (
|
|||
content={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip',
|
||||
{
|
||||
defaultMessage: 'This data from this field will be aggregated with the "sum" aggregation.',
|
||||
defaultMessage:
|
||||
'This data from this field will be aggregated with the "sum" aggregation or document count.',
|
||||
}
|
||||
)}
|
||||
position="top"
|
||||
|
@ -89,6 +94,7 @@ const equationTooltip = (
|
|||
export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIndicatorProps) {
|
||||
const { control, watch, setValue, register, getFieldState } = useFormContext<CreateSLOForm>();
|
||||
const [options, setOptions] = useState<Option[]>(createOptionsFromFields(metricFields));
|
||||
const [aggregationOptions, setAggregationOptions] = useState(CUSTOM_METRIC_AGGREGATION_OPTIONS);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(createOptionsFromFields(metricFields));
|
||||
|
@ -131,20 +137,25 @@ export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIn
|
|||
{fields?.map((metric, index) => (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs" key={metric.id}>
|
||||
<input hidden {...register(`indicator.params.${type}.metrics.${index}.name`)} />
|
||||
<input hidden {...register(`indicator.params.${type}.metrics.${index}.aggregation`)} />
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
isInvalid={getFieldState(`indicator.params.${type}.metrics.${index}.field`).invalid}
|
||||
isInvalid={
|
||||
getFieldState(`indicator.params.${type}.metrics.${index}.aggregation`).invalid
|
||||
}
|
||||
label={
|
||||
<span>
|
||||
{metricLabel} {metric.name} {metricTooltip}
|
||||
{i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.customMetric.aggregationLabel',
|
||||
{ defaultMessage: 'Aggregation' }
|
||||
)}{' '}
|
||||
{metric.name}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name={`indicator.params.${type}.metrics.${index}.field`}
|
||||
defaultValue=""
|
||||
name={`indicator.params.${type}.metrics.${index}.aggregation`}
|
||||
defaultValue="sum"
|
||||
rules={{ required: true }}
|
||||
control={control}
|
||||
render={({ field: { ref, ...field }, fieldState }) => (
|
||||
|
@ -153,17 +164,13 @@ export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIn
|
|||
async
|
||||
fullWidth
|
||||
singleSelection={{ asPlainText: true }}
|
||||
prepend={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel',
|
||||
{ defaultMessage: 'Sum of' }
|
||||
)}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder',
|
||||
{ defaultMessage: 'Select a metric field' }
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.aggregation.placeholder',
|
||||
{ defaultMessage: 'Select an aggregation' }
|
||||
)}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder',
|
||||
{ defaultMessage: 'Select a metric field' }
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.aggregation.placeholder',
|
||||
{ defaultMessage: 'Select an aggregation' }
|
||||
)}
|
||||
isClearable
|
||||
isInvalid={fieldState.invalid}
|
||||
|
@ -178,40 +185,112 @@ export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIn
|
|||
selectedOptions={
|
||||
!!indexPattern &&
|
||||
!!field.value &&
|
||||
metricFields.some((metricField) => metricField.name === field.value)
|
||||
CUSTOM_METRIC_AGGREGATION_OPTIONS.some((agg) => agg.value === field.value)
|
||||
? [
|
||||
{
|
||||
value: field.value,
|
||||
label: field.value,
|
||||
label: aggValueToLabel(field.value),
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
onSearchChange={(searchValue: string) => {
|
||||
setOptions(
|
||||
createOptionsFromFields(metricFields, ({ value }) =>
|
||||
setAggregationOptions(
|
||||
CUSTOM_METRIC_AGGREGATION_OPTIONS.filter(({ value }) =>
|
||||
value.includes(searchValue)
|
||||
)
|
||||
);
|
||||
}}
|
||||
options={options}
|
||||
options={aggregationOptions}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
{watch(`indicator.params.${type}.metrics.${index}.aggregation`) !== 'doc_count' && (
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
isInvalid={
|
||||
getFieldState(`indicator.params.${type}.metrics.${index}.field`).invalid
|
||||
}
|
||||
label={
|
||||
<span>
|
||||
{metricLabel} {metric.name} {metricTooltip}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name={`indicator.params.${type}.metrics.${index}.field`}
|
||||
defaultValue=""
|
||||
rules={{ required: true }}
|
||||
shouldUnregister
|
||||
control={control}
|
||||
render={({ field: { ref, ...field }, fieldState }) => (
|
||||
<EuiComboBox
|
||||
{...field}
|
||||
async
|
||||
fullWidth
|
||||
singleSelection={{ asPlainText: true }}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder',
|
||||
{ defaultMessage: 'Select a metric field' }
|
||||
)}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder',
|
||||
{ defaultMessage: 'Select a metric field' }
|
||||
)}
|
||||
isClearable
|
||||
isInvalid={fieldState.invalid}
|
||||
isDisabled={isLoadingIndex || !indexPattern}
|
||||
isLoading={!!indexPattern && isLoadingIndex}
|
||||
onChange={(selected: EuiComboBoxOptionOption[]) => {
|
||||
if (selected.length) {
|
||||
return field.onChange(selected[0].value);
|
||||
}
|
||||
field.onChange('');
|
||||
}}
|
||||
selectedOptions={
|
||||
!!indexPattern &&
|
||||
!!field.value &&
|
||||
metricFields.some((metricField) => metricField.name === field.value)
|
||||
? [
|
||||
{
|
||||
value: field.value,
|
||||
label: field.value,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
onSearchChange={(searchValue: string) => {
|
||||
setOptions(
|
||||
createOptionsFromFields(metricFields, ({ value }) =>
|
||||
value.includes(searchValue)
|
||||
)
|
||||
);
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<QueryBuilder
|
||||
dataTestSubj="customKqlIndicatorFormGoodQueryInput"
|
||||
indexPatternString={watch('indicator.params.index')}
|
||||
label={`${filterLabel} ${metric.name}`}
|
||||
name={`indicator.params.${type}.metrics.${index}.filter`}
|
||||
placeholder=""
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.placeholder',
|
||||
{ defaultMessage: 'KQL filter' }
|
||||
)}
|
||||
required={false}
|
||||
tooltip={
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.goodQuery.tooltip',
|
||||
'xpack.observability.slo.sloEdit.sliType.customMetric.tooltip',
|
||||
{
|
||||
defaultMessage: 'This KQL query should return a subset of events.',
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ export function MetricInput({
|
|||
selectedOptions={
|
||||
!!indexPattern &&
|
||||
!!field.value &&
|
||||
AGGREGATION_OPTIONS.some((agg) => agg.value === agg.value)
|
||||
AGGREGATION_OPTIONS.some((agg) => agg.value === field.value)
|
||||
? [
|
||||
{
|
||||
value: field.value,
|
||||
|
|
|
@ -75,6 +75,10 @@ export const AGGREGATION_OPTIONS = [
|
|||
},
|
||||
];
|
||||
|
||||
export const CUSTOM_METRIC_AGGREGATION_OPTIONS = AGGREGATION_OPTIONS.filter((option) =>
|
||||
['doc_count', 'sum'].includes(option.value)
|
||||
);
|
||||
|
||||
export function aggValueToLabel(value: string) {
|
||||
const aggregation = AGGREGATION_OPTIONS.find((agg) => agg.value === value);
|
||||
if (aggregation) {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
metricCustomBasicMetric,
|
||||
metricCustomDocCountMetric,
|
||||
MetricCustomIndicator,
|
||||
timesliceMetricBasicMetricWithField,
|
||||
TimesliceMetricIndicator,
|
||||
|
@ -31,7 +33,16 @@ export function useSectionFormValidation({ getFieldState, getValues, formState,
|
|||
const data = getValues('indicator.params.good') as MetricCustomIndicator['params']['good'];
|
||||
const isEquationValid = !getFieldState('indicator.params.good.equation').invalid;
|
||||
const areMetricsValid =
|
||||
isObject(data) && (data.metrics ?? []).every((metric) => Boolean(metric.field));
|
||||
isObject(data) &&
|
||||
(data.metrics ?? []).every((metric) => {
|
||||
if (metricCustomDocCountMetric.is(metric)) {
|
||||
return true;
|
||||
}
|
||||
if (metricCustomBasicMetric.is(metric) && metric.field != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return isEquationValid && areMetricsValid;
|
||||
};
|
||||
|
||||
|
@ -41,7 +52,16 @@ export function useSectionFormValidation({ getFieldState, getValues, formState,
|
|||
) as MetricCustomIndicator['params']['total'];
|
||||
const isEquationValid = !getFieldState('indicator.params.total.equation').invalid;
|
||||
const areMetricsValid =
|
||||
isObject(data) && (data.metrics ?? []).every((metric) => Boolean(metric.field));
|
||||
isObject(data) &&
|
||||
(data.metrics ?? []).every((metric) => {
|
||||
if (metricCustomDocCountMetric.is(metric)) {
|
||||
return true;
|
||||
}
|
||||
if (metricCustomBasicMetric.is(metric) && metric.field != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return isEquationValid && areMetricsValid;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ exports[`GetHistogramIndicatorAggregation should generate a aggregation for good
|
|||
Object {
|
||||
"_good_A": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "total",
|
||||
},
|
||||
|
@ -16,7 +16,7 @@ Object {
|
|||
},
|
||||
"_good_B": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "processed",
|
||||
},
|
||||
|
@ -29,8 +29,8 @@ Object {
|
|||
"goodEvents": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_good_A>sum",
|
||||
"B": "_good_B>sum",
|
||||
"A": "_good_A>metric",
|
||||
"B": "_good_B>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -45,7 +45,7 @@ exports[`GetHistogramIndicatorAggregation should generate a aggregation for tota
|
|||
Object {
|
||||
"_total_A": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "total",
|
||||
},
|
||||
|
@ -58,7 +58,7 @@ Object {
|
|||
"totalEvents": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_total_A>sum",
|
||||
"A": "_total_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { MetricCustomIndicator } from '@kbn/slo-schema';
|
||||
import { metricCustomDocCountMetric, MetricCustomIndicator } from '@kbn/slo-schema';
|
||||
import { getElastichsearchQueryOrThrow } from '../transform_generators';
|
||||
|
||||
type MetricCustomMetricDef =
|
||||
|
@ -20,12 +20,22 @@ export class GetCustomMetricIndicatorAggregation {
|
|||
const filter = metric.filter
|
||||
? getElastichsearchQueryOrThrow(metric.filter)
|
||||
: { match_all: {} };
|
||||
|
||||
if (metricCustomDocCountMetric.is(metric)) {
|
||||
return {
|
||||
...acc,
|
||||
[`_${type}_${metric.name}`]: {
|
||||
filter,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[`_${type}_${metric.name}`]: {
|
||||
filter,
|
||||
aggs: {
|
||||
sum: {
|
||||
metric: {
|
||||
[metric.aggregation]: { field: metric.field },
|
||||
},
|
||||
},
|
||||
|
@ -42,10 +52,11 @@ export class GetCustomMetricIndicatorAggregation {
|
|||
}
|
||||
|
||||
private buildMetricEquation(type: 'good' | 'total', metricDef: MetricCustomMetricDef) {
|
||||
const bucketsPath = metricDef.metrics.reduce(
|
||||
(acc, metric) => ({ ...acc, [metric.name]: `_${type}_${metric.name}>sum` }),
|
||||
{}
|
||||
);
|
||||
const bucketsPath = metricDef.metrics.reduce((acc, metric) => {
|
||||
const path = metricCustomDocCountMetric.is(metric) ? '_count' : 'metric';
|
||||
return { ...acc, [metric.name]: `_${type}_${metric.name}>${path}` };
|
||||
}, {});
|
||||
|
||||
return {
|
||||
bucket_script: {
|
||||
buckets_path: bucketsPath,
|
||||
|
|
|
@ -1,10 +1,38 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Metric Custom Transform Generator aggregates using doc_count for the denominator equation with filter 1`] = `
|
||||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_total_A>_count",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "params.A / 100",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Metric Custom Transform Generator aggregates using doc_count the numerator equation with filter 1`] = `
|
||||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_good_A>_count",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "params.A * 100",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Metric Custom Transform Generator aggregates using the denominator equation 1`] = `
|
||||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_total_A>sum",
|
||||
"A": "_total_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -18,7 +46,7 @@ exports[`Metric Custom Transform Generator aggregates using the denominator equa
|
|||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_total_A>sum",
|
||||
"A": "_total_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -32,7 +60,7 @@ exports[`Metric Custom Transform Generator aggregates using the numerator equati
|
|||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_good_A>sum",
|
||||
"A": "_good_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -46,7 +74,7 @@ exports[`Metric Custom Transform Generator aggregates using the numerator equati
|
|||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_good_A>sum",
|
||||
"A": "_good_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -101,7 +129,7 @@ Object {
|
|||
"aggregations": Object {
|
||||
"_good_A": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "total",
|
||||
},
|
||||
|
@ -113,7 +141,7 @@ Object {
|
|||
},
|
||||
"_good_B": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "processed",
|
||||
},
|
||||
|
@ -125,7 +153,7 @@ Object {
|
|||
},
|
||||
"_total_A": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "total",
|
||||
},
|
||||
|
@ -138,7 +166,7 @@ Object {
|
|||
"slo.denominator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_total_A>sum",
|
||||
"A": "_total_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -158,8 +186,8 @@ Object {
|
|||
"slo.numerator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_good_A>sum",
|
||||
"B": "_good_B>sum",
|
||||
"A": "_good_A>metric",
|
||||
"B": "_good_B>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -384,7 +412,7 @@ Object {
|
|||
"aggregations": Object {
|
||||
"_good_A": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "total",
|
||||
},
|
||||
|
@ -396,7 +424,7 @@ Object {
|
|||
},
|
||||
"_good_B": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "processed",
|
||||
},
|
||||
|
@ -408,7 +436,7 @@ Object {
|
|||
},
|
||||
"_total_A": Object {
|
||||
"aggs": Object {
|
||||
"sum": Object {
|
||||
"metric": Object {
|
||||
"sum": Object {
|
||||
"field": "total",
|
||||
},
|
||||
|
@ -421,7 +449,7 @@ Object {
|
|||
"slo.denominator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_total_A>sum",
|
||||
"A": "_total_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -432,8 +460,8 @@ Object {
|
|||
"slo.numerator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_good_A>sum",
|
||||
"B": "_good_B>sum",
|
||||
"A": "_good_A>metric",
|
||||
"B": "_good_B>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
|
@ -629,3 +657,17 @@ Object {
|
|||
"transform_id": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Metric Custom Transform Generator support the same field used twice in the equation 1`] = `
|
||||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"A": "_good_A>metric",
|
||||
},
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "params.A + params.A * 100",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -142,6 +142,20 @@ describe('Metric Custom Transform Generator', () => {
|
|||
expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('support the same field used twice in the equation', async () => {
|
||||
const anSLO = createSLO({
|
||||
indicator: createMetricCustomIndicator({
|
||||
good: {
|
||||
metrics: [{ name: 'A', aggregation: 'sum', field: 'good' }],
|
||||
equation: 'A + A * 100',
|
||||
},
|
||||
}),
|
||||
});
|
||||
const transform = generator.getTransformParams(anSLO);
|
||||
|
||||
expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('aggregates using the numerator equation with filter', async () => {
|
||||
const anSLO = createSLO({
|
||||
indicator: createMetricCustomIndicator({
|
||||
|
@ -158,6 +172,20 @@ describe('Metric Custom Transform Generator', () => {
|
|||
expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('aggregates using doc_count the numerator equation with filter', async () => {
|
||||
const anSLO = createSLO({
|
||||
indicator: createMetricCustomIndicator({
|
||||
good: {
|
||||
metrics: [{ name: 'A', aggregation: 'doc_count', filter: 'outcome: "success" ' }],
|
||||
equation: 'A * 100',
|
||||
},
|
||||
}),
|
||||
});
|
||||
const transform = generator.getTransformParams(anSLO);
|
||||
|
||||
expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('aggregates using the denominator equation', async () => {
|
||||
const anSLO = createSLO({
|
||||
indicator: createMetricCustomIndicator({
|
||||
|
@ -185,4 +213,18 @@ describe('Metric Custom Transform Generator', () => {
|
|||
|
||||
expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('aggregates using doc_count for the denominator equation with filter', async () => {
|
||||
const anSLO = createSLO({
|
||||
indicator: createMetricCustomIndicator({
|
||||
total: {
|
||||
metrics: [{ name: 'A', aggregation: 'doc_count', filter: 'outcome: *' }],
|
||||
equation: 'A / 100',
|
||||
},
|
||||
}),
|
||||
});
|
||||
const transform = generator.getTransformParams(anSLO);
|
||||
|
||||
expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29682,12 +29682,10 @@
|
|||
"xpack.observability.slo.sloEdit.sliType.customMetric.equationHelpText": "Accepte les équations mathématiques de base, les caractères valides sont : A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel": "Équation",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel": "Filtre",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.goodQuery.tooltip": "Cette requête KQL doit renvoyer un sous-ensemble d'événements.",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.goodTitle": "Bons événements",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "Sélectionner un champ d’indicateur",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel": "Indicateur",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "Filtre de requête",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "Somme de",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip": "Ceci est compatible avec des calculs de base (A + B / C) et la logique booléenne (A < B ? A : B).",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip": "Les données de ce champ seront agrégées avec l’agréation de \"somme\".",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalTitle": "Total des événements",
|
||||
|
|
|
@ -29681,12 +29681,10 @@
|
|||
"xpack.observability.slo.sloEdit.sliType.customMetric.equationHelpText": "基本的な数式をサポートします。有効な文字:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel": "式",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel": "フィルター",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.goodQuery.tooltip": "このKQLクエリはイベントのサブセットを返します。",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.goodTitle": "良好なイベント数",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "メトリックフィールドを選択",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel": "メトリック",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "クエリのフィルター",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "の合計",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip": "これは基本的な数学ロジック(A + B / C)とブールロジック(A < B ?A :B)をサポートします。",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip": "このフィールドのデータは「sum」集計で集約されます。",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalTitle": "合計イベント数",
|
||||
|
|
|
@ -29679,12 +29679,10 @@
|
|||
"xpack.observability.slo.sloEdit.sliType.customMetric.equationHelpText": "支持基本数学方程,有效字符包括:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel": "方程",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel": "筛选",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.goodQuery.tooltip": "此 KQL 查询应返回一个事件子集。",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.goodTitle": "良好事件",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "选择指标字段",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel": "指标",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "查询筛选",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "求和",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip": "这支持基本数学 (A + B / C) 和布尔逻辑 (A < B ?A :B)。",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip": "来自该字段的此类数据将使用“求和”聚合进行汇总。",
|
||||
"xpack.observability.slo.sloEdit.sliType.customMetric.totalTitle": "事件合计",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue