mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[SLOs] synthetics availability - add cardinality count for group by (#178454)
## Summary Adds group by cardinality count to the synthetics availability SLO indicator Resolves https://github.com/elastic/kibana/issues/178409 Resolves https://github.com/elastic/kibana/issues/178140 Also, it's come to my attention that https://github.com/elastic/kibana/issues/178341 was not fixed by a previous PR. This PR now also resolves https://github.com/elastic/kibana/issues/178341 ### Testing 1. Create an cluster with oblt-cli and add the config to your `kibana.dev.yml` 2. Navigate to the Synthetics app. Create at least two synthetic monitors 3. Navigate to SLO create. Select the synthetic availability indicator 4. Check the group by cardinality callout. The cardinality should reflect the number of monitor/location combinations <img width="730" alt="Screenshot 2024-04-12 at 1 04 57 PM" src="a05ffaff
-c01b-4107-8f8d-2ea8362fe72e"> 5. Now filter by monitor name or tag. The group by cardinality should reflect the number of monitors that match the filters <img width="733" alt="Screenshot 2024-04-12 at 1 05 11 PM" src="079c74ea
-dd1c-45f2-bf0e-2dbefea30f96"> ### Testing https://github.com/elastic/kibana/issues/178341 To test the fix for https://github.com/elastic/kibana/issues/178341, create a simple custom kql SLO with a group by. Add a overall filter that would impact the overall group by count. Verify that the group by count accurately reflects the overall filter.
This commit is contained in:
parent
b67ab78a65
commit
672bb5a54b
7 changed files with 618 additions and 38 deletions
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALL_VALUE, QuerySchema } from '@kbn/slo-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCallOut, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useFetchGroupByCardinality } from '../../../../hooks/use_fetch_group_by_cardinality';
|
||||
import { CreateSLOForm } from '../../types';
|
||||
import { getGroupKeysProse } from '../../../../utils/slo/groupings';
|
||||
|
||||
export function GroupByCardinality({
|
||||
titleAppend,
|
||||
customFilters,
|
||||
}: {
|
||||
titleAppend?: React.ReactNode;
|
||||
customFilters?: QuerySchema;
|
||||
}) {
|
||||
const { watch } = useFormContext<CreateSLOForm>();
|
||||
|
||||
const index = watch('indicator.params.index');
|
||||
const filters = watch('indicator.params.filter');
|
||||
const timestampField = watch('indicator.params.timestampField');
|
||||
const groupByField = watch('groupBy');
|
||||
|
||||
const { isLoading: isGroupByCardinalityLoading, data: groupByCardinality } =
|
||||
useFetchGroupByCardinality(index, timestampField, groupByField, customFilters || filters);
|
||||
const groupBy = [groupByField].flat().filter((value) => !!value);
|
||||
const hasGroupBy = !groupBy.includes(ALL_VALUE) && groupBy.length;
|
||||
|
||||
if (!hasGroupBy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isGroupByCardinalityLoading && !groupByCardinality) {
|
||||
return <EuiCallOut size="s" title={<EuiLoadingSpinner />} />;
|
||||
}
|
||||
|
||||
if (!groupByCardinality) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cardinalityMessage = i18n.translate('xpack.slo.sloEdit.groupBy.cardinalityInfo', {
|
||||
defaultMessage:
|
||||
'Selected group by field {groupBy} will generate at least {card} SLO instances based on the last 24h sample data.',
|
||||
values: {
|
||||
card: groupByCardinality.cardinality,
|
||||
groupBy: getGroupKeysProse(groupByField),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
iconType={groupByCardinality.isHighCardinality ? 'warning' : ''}
|
||||
color={groupByCardinality.isHighCardinality ? 'warning' : 'primary'}
|
||||
title={
|
||||
titleAppend ? (
|
||||
<>
|
||||
{titleAppend} {cardinalityMessage}
|
||||
</>
|
||||
) : (
|
||||
cardinalityMessage
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { canGroupBy } from './group_by_field';
|
||||
|
||||
describe('canGroupBy', () => {
|
||||
it('handles multi fields where there are multi es types', () => {
|
||||
const field = {
|
||||
name: 'event.action.keyword',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
metadata_field: false,
|
||||
subType: {
|
||||
multi: {
|
||||
parent: 'event.action',
|
||||
},
|
||||
},
|
||||
isMapped: true,
|
||||
shortDotsEnable: false,
|
||||
};
|
||||
expect(canGroupBy(field)).toBe(true);
|
||||
const field2 = {
|
||||
name: 'event.action',
|
||||
type: 'string',
|
||||
esTypes: ['keyword', 'text'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
metadata_field: false,
|
||||
isMapped: true,
|
||||
shortDotsEnable: false,
|
||||
};
|
||||
expect(canGroupBy(field2)).toBe(false);
|
||||
});
|
||||
|
||||
it('handles date fields', () => {
|
||||
const field = {
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
esTypes: ['date'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
metadata_field: false,
|
||||
isMapped: true,
|
||||
shortDotsEnable: false,
|
||||
};
|
||||
expect(canGroupBy(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('handles non aggregatable fields', () => {
|
||||
const field = {
|
||||
name: 'event.action',
|
||||
type: 'string',
|
||||
esTypes: ['text'],
|
||||
searchable: true,
|
||||
aggregatable: false,
|
||||
readFromDocValues: true,
|
||||
metadata_field: false,
|
||||
isMapped: true,
|
||||
shortDotsEnable: false,
|
||||
};
|
||||
|
||||
expect(canGroupBy(field)).toBe(false);
|
||||
});
|
||||
});
|
|
@ -7,29 +7,20 @@
|
|||
|
||||
import { ALL_VALUE } from '@kbn/slo-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCallOut, EuiIconTip } from '@elastic/eui';
|
||||
import { EuiIconTip } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { DataView, FieldSpec } from '@kbn/data-views-plugin/common';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { OptionalText } from './optional_text';
|
||||
import { useFetchGroupByCardinality } from '../../../../hooks/use_fetch_group_by_cardinality';
|
||||
import { CreateSLOForm } from '../../types';
|
||||
import { IndexFieldSelector } from './index_field_selector';
|
||||
import { getGroupKeysProse } from '../../../../utils/slo/groupings';
|
||||
import { GroupByCardinality } from './group_by_cardinality';
|
||||
|
||||
export function GroupByField({ dataView, isLoading }: { dataView?: DataView; isLoading: boolean }) {
|
||||
const { watch } = useFormContext<CreateSLOForm>();
|
||||
|
||||
const groupByFields =
|
||||
dataView?.fields?.filter((field) => field.aggregatable && field.type !== 'date') ?? [];
|
||||
const groupByFields = dataView?.fields?.filter((field) => canGroupBy(field)) ?? [];
|
||||
const index = watch('indicator.params.index');
|
||||
const timestampField = watch('indicator.params.timestampField');
|
||||
const groupByField = watch('groupBy');
|
||||
|
||||
const { isLoading: isGroupByCardinalityLoading, data: groupByCardinality } =
|
||||
useFetchGroupByCardinality(index, timestampField, groupByField);
|
||||
const groupBy = [groupByField].flat().filter((value) => !!value);
|
||||
const hasGroupBy = !groupBy.includes(ALL_VALUE) && groupBy.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -57,21 +48,17 @@ export function GroupByField({ dataView, isLoading }: { dataView?: DataView; isL
|
|||
isLoading={!!index && isLoading}
|
||||
isDisabled={!index}
|
||||
/>
|
||||
{!isGroupByCardinalityLoading && !!groupByCardinality && hasGroupBy && (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
iconType={groupByCardinality.isHighCardinality ? 'warning' : ''}
|
||||
color={groupByCardinality.isHighCardinality ? 'warning' : 'primary'}
|
||||
title={i18n.translate('xpack.slo.sloEdit.groupBy.cardinalityInfo', {
|
||||
defaultMessage:
|
||||
'Selected group by field {groupBy} will generate at least {card} SLO instances based on the last 24h sample data.',
|
||||
values: {
|
||||
card: groupByCardinality.cardinality,
|
||||
groupBy: getGroupKeysProse(groupByField),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<GroupByCardinality />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const canGroupBy = (field: FieldSpec) => {
|
||||
const isAggregatable = field.aggregatable;
|
||||
const isNotDate = field.type !== 'date';
|
||||
// handles multi fields where there are multi es types, which could include 'text'
|
||||
// text fields break the transforms so we must ensure that the field is only a keyword
|
||||
const isOnlyKeyword = field.esTypes?.length === 1 && field.esTypes[0] === 'keyword';
|
||||
|
||||
return isAggregatable && isNotDate && isOnlyKeyword;
|
||||
};
|
||||
|
|
|
@ -186,7 +186,7 @@ export function SloEditFormObjectiveSection() {
|
|||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.slo.sloEdit.sliType.syntheticAvailability.objectiveMessage"
|
||||
defaultMessage="The Synthetics availability indicator requires the budgeting method to be set to 'Occurances'."
|
||||
defaultMessage="The Synthetics availability indicator requires the budgeting method to be set to 'Occurrences'."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
formatAllFilters,
|
||||
getGroupByCardinalityFilters,
|
||||
} from './synthetics_availability_indicator_type_form';
|
||||
|
||||
describe('get group by cardinality filters', () => {
|
||||
it('formats filters correctly', () => {
|
||||
const monitorIds = ['1234'];
|
||||
const tags = ['tag1', 'tag2'];
|
||||
const projects = ['project1', 'project2'];
|
||||
expect(getGroupByCardinalityFilters(monitorIds, projects, tags)).toEqual([
|
||||
{
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.id',
|
||||
negate: false,
|
||||
params: ['1234'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: { minimum_should_match: 1, should: [{ match_phrase: { 'monitor.id': '1234' } }] },
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.project.id',
|
||||
negate: false,
|
||||
params: ['project1', 'project2'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{ match_phrase: { 'monitor.project.id': 'project1' } },
|
||||
{ match_phrase: { 'monitor.project.id': 'project2' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'tags',
|
||||
negate: false,
|
||||
params: ['tag1', 'tag2'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [{ match_phrase: { tags: 'tag1' } }, { match_phrase: { tags: 'tag2' } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not include filters when arrays are empty', () => {
|
||||
// @ts-ignore
|
||||
const monitorIds = [];
|
||||
// @ts-ignore
|
||||
const tags = [];
|
||||
// @ts-ignore
|
||||
const projects = [];
|
||||
// @ts-ignore
|
||||
expect(getGroupByCardinalityFilters(monitorIds, projects, tags)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatAllFilters', () => {
|
||||
const monitorIds = ['1234'];
|
||||
const tags = ['tag1', 'tag2'];
|
||||
const projects = ['project1', 'project2'];
|
||||
const cardinalityFilters = getGroupByCardinalityFilters(monitorIds, projects, tags);
|
||||
|
||||
it('handles global kql filter', () => {
|
||||
const kqlFilter = 'monitor.id: "1234"';
|
||||
expect(formatAllFilters(kqlFilter, cardinalityFilters)).toEqual({
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.id',
|
||||
negate: false,
|
||||
params: ['1234'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.id': '1234',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.project.id',
|
||||
negate: false,
|
||||
params: ['project1', 'project2'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.project.id': 'project1',
|
||||
},
|
||||
},
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.project.id': 'project2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'tags',
|
||||
negate: false,
|
||||
params: ['tag1', 'tag2'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
tags: 'tag1',
|
||||
},
|
||||
},
|
||||
{
|
||||
match_phrase: {
|
||||
tags: 'tag2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
kqlQuery: 'monitor.id: "1234"',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles global filters meta ', () => {
|
||||
const globalFilters = {
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.name',
|
||||
negate: false,
|
||||
params: ['test name'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.name': 'test name',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
kqlQuery: 'monitor.id: "1234"',
|
||||
};
|
||||
expect(formatAllFilters(globalFilters, cardinalityFilters)).toEqual({
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.name',
|
||||
negate: false,
|
||||
params: ['test name'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.name': 'test name',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.id',
|
||||
negate: false,
|
||||
params: ['1234'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.id': '1234',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'monitor.project.id',
|
||||
negate: false,
|
||||
params: ['project1', 'project2'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.project.id': 'project1',
|
||||
},
|
||||
},
|
||||
{
|
||||
match_phrase: {
|
||||
'monitor.project.id': 'project2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'tags',
|
||||
negate: false,
|
||||
params: ['tag1', 'tag2'],
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
tags: 'tag1',
|
||||
},
|
||||
},
|
||||
{
|
||||
match_phrase: {
|
||||
tags: 'tag2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
kqlQuery: 'monitor.id: "1234"',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,25 +5,34 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiCallOut } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALL_VALUE, SyntheticsAvailabilityIndicator } from '@kbn/slo-schema';
|
||||
import {
|
||||
ALL_VALUE,
|
||||
SyntheticsAvailabilityIndicator,
|
||||
QuerySchema,
|
||||
kqlQuerySchema,
|
||||
kqlWithFiltersSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import { Filter, FilterStateStore } from '@kbn/es-query';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { FieldSelector } from '../synthetics_common/field_selector';
|
||||
import { CreateSLOForm } from '../../types';
|
||||
import { DataPreviewChart } from '../common/data_preview_chart';
|
||||
import { QueryBuilder } from '../common/query_builder';
|
||||
import { GroupByCardinality } from '../common/group_by_cardinality';
|
||||
|
||||
const ONE_DAY_IN_MILLISECONDS = 1 * 60 * 60 * 1000 * 24;
|
||||
|
||||
export function SyntheticsAvailabilityIndicatorTypeForm() {
|
||||
const { watch } = useFormContext<CreateSLOForm<SyntheticsAvailabilityIndicator>>();
|
||||
|
||||
const [monitorIds = [], projects = [], tags = [], index] = watch([
|
||||
const [monitorIds = [], projects = [], tags = [], index, globalFilters] = watch([
|
||||
'indicator.params.monitorIds',
|
||||
'indicator.params.projects',
|
||||
'indicator.params.tags',
|
||||
'indicator.params.index',
|
||||
'indicator.params.filter',
|
||||
]);
|
||||
|
||||
const [range, _] = useState({
|
||||
|
@ -36,6 +45,12 @@ export function SyntheticsAvailabilityIndicatorTypeForm() {
|
|||
projects: projects.map((project) => project.value).filter((id) => id !== ALL_VALUE),
|
||||
tags: tags.map((tag) => tag.value).filter((id) => id !== ALL_VALUE),
|
||||
};
|
||||
const groupByCardinalityFilters: Filter[] = getGroupByCardinalityFilters(
|
||||
filters.monitorIds,
|
||||
filters.projects,
|
||||
filters.tags
|
||||
);
|
||||
const allFilters = formatAllFilters(globalFilters, groupByCardinalityFilters);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="l">
|
||||
|
@ -121,11 +136,12 @@ export function SyntheticsAvailabilityIndicatorTypeForm() {
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title="Synthetics availability SLIs are automatically grouped by monitor and location"
|
||||
iconType="iInCircle"
|
||||
<GroupByCardinality
|
||||
titleAppend={i18n.translate('xpack.slo.sloEdit.syntheticsAvailability.warning', {
|
||||
defaultMessage:
|
||||
'Synthetics availability SLIs are automatically grouped by monitor and location.',
|
||||
})}
|
||||
customFilters={allFilters as QuerySchema}
|
||||
/>
|
||||
<DataPreviewChart range={range} label={LABEL} useGoodBadEventsChart />
|
||||
</EuiFlexGroup>
|
||||
|
@ -135,3 +151,103 @@ export function SyntheticsAvailabilityIndicatorTypeForm() {
|
|||
const LABEL = i18n.translate('xpack.slo.sloEdit.dataPreviewChart.syntheticsAvailability.xTitle', {
|
||||
defaultMessage: 'Last 24 hours',
|
||||
});
|
||||
|
||||
export const getGroupByCardinalityFilters = (
|
||||
monitorIds: string[],
|
||||
projects: string[],
|
||||
tags: string[]
|
||||
): Filter[] => {
|
||||
const monitorIdFilters = monitorIds.length
|
||||
? {
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'monitor.id',
|
||||
params: monitorIds,
|
||||
type: 'phrases',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: monitorIds.map((id) => ({
|
||||
match_phrase: {
|
||||
'monitor.id': id,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
const projectFilters = projects.length
|
||||
? {
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'monitor.project.id',
|
||||
params: projects,
|
||||
type: 'phrases',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: projects.map((id) => ({
|
||||
match_phrase: {
|
||||
'monitor.project.id': id,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
const tagFilters = tags.length
|
||||
? {
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'tags',
|
||||
params: tags,
|
||||
type: 'phrases',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: tags.map((tag) => ({
|
||||
match_phrase: {
|
||||
tags: tag,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
return [monitorIdFilters, projectFilters, tagFilters].filter((value) => !!value) as Filter[];
|
||||
};
|
||||
|
||||
export const formatAllFilters = (
|
||||
globalFilters: QuerySchema = '',
|
||||
groupByCardinalityFilters: Filter[]
|
||||
) => {
|
||||
if (kqlQuerySchema.is(globalFilters)) {
|
||||
return { kqlQuery: globalFilters, filters: groupByCardinalityFilters };
|
||||
} else if (kqlWithFiltersSchema) {
|
||||
return {
|
||||
kqlQuery: globalFilters.kqlQuery,
|
||||
filters: [...globalFilters.filters, ...groupByCardinalityFilters],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -167,7 +167,7 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator
|
|||
private buildAggregations(slo: SLO) {
|
||||
if (!occurrencesBudgetingMethodSchema.is(slo.budgetingMethod)) {
|
||||
throw new Error(
|
||||
'The sli.synthetics.availability indicator MUST have an occurances budgeting method.'
|
||||
"The sli.synthetics.availability indicator MUST have an 'Occurrences' budgeting method."
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue