mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[SLOS] fix APM group by cardinality count (#183171)
## Summary
Resolves https://github.com/elastic/kibana/issues/179046
Summarize your PR. If it involves visual changes include a screenshot or
gif.
Applies filters based on the selected APM indicator params to the
cardinality count query.
<img width="845" alt="Screenshot 2024-05-10 at 1 07 27 PM"
src="7283f7cc
-b141-47e2-883e-e463556e4aec">
### Testing.
1. Create mock api data via `node scripts/synthtrace continuous_rollups
--live`
2. Navigate to create SLI and choose APM latency
3. Select a service
4. Select group by as `service.name`. Cardinality count should be 1
5. Select environment all and change the group by to
`service.environment`. Cardinality count should be 2.
6. Now select a specific environment. Notice cardinality count changes
to 1.
7. Repeat this testing strategy with transaction name and transaction
type, changing the group by field to `transaction.name` or
`transaction.type`, respectively and playing around with the ALL_VALUE
versus a specific value.
8. Also make sure to test the global filter for regressions
### Release note
The cardinality count for SLOs generated from a single SLI definition
was previously incorrect for APM latency and APM availability SLIs. The
cardinality count is now accurate.
This commit is contained in:
parent
3ecd73c2e5
commit
d3945a3e50
11 changed files with 625 additions and 301 deletions
|
@ -15,6 +15,7 @@ import {
|
|||
kqlWithFiltersSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
querySchema,
|
||||
filtersSchema,
|
||||
groupingsSchema,
|
||||
syntheticsAvailabilityIndicatorSchema,
|
||||
timesliceMetricBasicMetricWithField,
|
||||
|
@ -42,6 +43,7 @@ type HistogramIndicator = t.OutputOf<typeof histogramIndicatorSchema>;
|
|||
type KQLCustomIndicator = t.OutputOf<typeof kqlCustomIndicatorSchema>;
|
||||
type KqlWithFiltersSchema = t.TypeOf<typeof kqlWithFiltersSchema>;
|
||||
type QuerySchema = t.TypeOf<typeof querySchema>;
|
||||
type FiltersSchema = t.TypeOf<typeof filtersSchema>;
|
||||
type GroupingsSchema = t.TypeOf<typeof groupingsSchema>;
|
||||
|
||||
export type {
|
||||
|
@ -59,5 +61,6 @@ export type {
|
|||
KQLCustomIndicator,
|
||||
KqlWithFiltersSchema,
|
||||
QuerySchema,
|
||||
FiltersSchema,
|
||||
GroupingsSchema,
|
||||
};
|
||||
|
|
|
@ -10,29 +10,31 @@ import { allOrAnyString } from './common';
|
|||
|
||||
const kqlQuerySchema = t.string;
|
||||
|
||||
const filtersSchema = t.array(
|
||||
t.type({
|
||||
meta: t.partial({
|
||||
alias: t.union([t.string, t.null]),
|
||||
disabled: t.boolean,
|
||||
negate: t.boolean,
|
||||
// controlledBy is there to identify who owns the filter
|
||||
controlledBy: t.string,
|
||||
// allows grouping of filters
|
||||
group: t.string,
|
||||
// index and type are optional only because when you create a new filter, there are no defaults
|
||||
index: t.string,
|
||||
isMultiIndex: t.boolean,
|
||||
type: t.string,
|
||||
key: t.string,
|
||||
params: t.any,
|
||||
value: t.string,
|
||||
}),
|
||||
query: t.record(t.string, t.any),
|
||||
})
|
||||
);
|
||||
|
||||
const kqlWithFiltersSchema = t.type({
|
||||
kqlQuery: t.string,
|
||||
filters: t.array(
|
||||
t.type({
|
||||
meta: t.partial({
|
||||
alias: t.union([t.string, t.null]),
|
||||
disabled: t.boolean,
|
||||
negate: t.boolean,
|
||||
// controlledBy is there to identify who owns the filter
|
||||
controlledBy: t.string,
|
||||
// allows grouping of filters
|
||||
group: t.string,
|
||||
// index and type are optional only because when you create a new filter, there are no defaults
|
||||
index: t.string,
|
||||
isMultiIndex: t.boolean,
|
||||
type: t.string,
|
||||
key: t.string,
|
||||
params: t.any,
|
||||
value: t.string,
|
||||
}),
|
||||
query: t.record(t.string, t.any),
|
||||
})
|
||||
),
|
||||
filters: filtersSchema,
|
||||
});
|
||||
|
||||
const querySchema = t.union([kqlQuerySchema, kqlWithFiltersSchema]);
|
||||
|
@ -314,6 +316,7 @@ export {
|
|||
kqlQuerySchema,
|
||||
kqlWithFiltersSchema,
|
||||
querySchema,
|
||||
filtersSchema,
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
apmTransactionDurationIndicatorTypeSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
|
||||
import { APMTransactionErrorRateIndicator } from '@kbn/slo-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
@ -16,11 +17,34 @@ import { CreateSLOForm } from '../../types';
|
|||
import { FieldSelector } from '../apm_common/field_selector';
|
||||
import { DataPreviewChart } from '../common/data_preview_chart';
|
||||
import { QueryBuilder } from '../common/query_builder';
|
||||
import { formatAllFilters } from '../../helpers/format_filters';
|
||||
import { getGroupByCardinalityFilters } from '../apm_common/get_group_by_cardinality_filters';
|
||||
|
||||
export function ApmAvailabilityIndicatorTypeForm() {
|
||||
const { watch, setValue } = useFormContext<CreateSLOForm>();
|
||||
const { watch, setValue } = useFormContext<CreateSLOForm<APMTransactionErrorRateIndicator>>();
|
||||
const { data: apmIndex } = useFetchApmIndex();
|
||||
|
||||
const [
|
||||
serviceName = '',
|
||||
environment = '',
|
||||
transactionType = '',
|
||||
transactionName = '',
|
||||
globalFilters,
|
||||
] = watch([
|
||||
'indicator.params.service',
|
||||
'indicator.params.environment',
|
||||
'indicator.params.transactionType',
|
||||
'indicator.params.transactionName',
|
||||
'indicator.params.filter',
|
||||
]);
|
||||
const indicatorParamsFilters = getGroupByCardinalityFilters({
|
||||
serviceName,
|
||||
environment,
|
||||
transactionType,
|
||||
transactionName,
|
||||
});
|
||||
const allFilters = formatAllFilters(globalFilters, indicatorParamsFilters);
|
||||
|
||||
useEffect(() => {
|
||||
if (apmIndex !== '') {
|
||||
setValue('indicator.params.index', apmIndex);
|
||||
|
@ -126,7 +150,7 @@ export function ApmAvailabilityIndicatorTypeForm() {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<GroupByField dataView={dataView} isLoading={isIndexFieldsLoading} />
|
||||
<GroupByField dataView={dataView} isLoading={isIndexFieldsLoading} filters={allFilters} />
|
||||
|
||||
<DataPreviewChart />
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 { getGroupByCardinalityFilters } from './get_group_by_cardinality_filters';
|
||||
import { ALL_VALUE } from '@kbn/slo-schema';
|
||||
|
||||
describe('get group by cardinality filters', () => {
|
||||
it('formats filters correctly', () => {
|
||||
const serviceName = 'testService';
|
||||
const environment = 'testEnvironment';
|
||||
const transactionName = 'testTransactionName';
|
||||
const transactionType = 'testTransactionType';
|
||||
expect(
|
||||
getGroupByCardinalityFilters({ serviceName, environment, transactionName, transactionType })
|
||||
).toEqual([
|
||||
{
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'service.name',
|
||||
negate: false,
|
||||
params: serviceName,
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: { match_phrase: { 'service.name': serviceName } },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'service.environment',
|
||||
negate: false,
|
||||
params: environment,
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: { match_phrase: { 'service.environment': environment } },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'transaction.type',
|
||||
negate: false,
|
||||
params: transactionType,
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: { match_phrase: { 'transaction.type': transactionType } },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'transaction.name',
|
||||
negate: false,
|
||||
params: transactionName,
|
||||
type: 'phrases',
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: { match_phrase: { 'transaction.name': transactionName } },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not include filters when values are undefined', () => {
|
||||
expect(
|
||||
getGroupByCardinalityFilters({
|
||||
// @ts-ignore
|
||||
serviceName: undefined,
|
||||
environment: undefined,
|
||||
transactionName: undefined,
|
||||
transactionType: undefined,
|
||||
})
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not include filters when values are ALL_VALUE', () => {
|
||||
expect(
|
||||
getGroupByCardinalityFilters({
|
||||
serviceName: ALL_VALUE,
|
||||
environment: ALL_VALUE,
|
||||
transactionName: ALL_VALUE,
|
||||
transactionType: ALL_VALUE,
|
||||
})
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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, FiltersSchema } from '@kbn/slo-schema';
|
||||
import { FilterStateStore } from '@kbn/es-query';
|
||||
|
||||
export const getGroupByCardinalityFilters = ({
|
||||
serviceName,
|
||||
environment,
|
||||
transactionType,
|
||||
transactionName,
|
||||
}: {
|
||||
serviceName: string;
|
||||
environment?: string;
|
||||
transactionType?: string;
|
||||
transactionName?: string;
|
||||
}): FiltersSchema => {
|
||||
const serviceNameFilter =
|
||||
serviceName && serviceName !== ALL_VALUE
|
||||
? {
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'service.name',
|
||||
params: serviceName,
|
||||
type: 'phrases',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: {
|
||||
match_phrase: {
|
||||
'service.name': serviceName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
const environmentFilter =
|
||||
environment && environment !== ALL_VALUE
|
||||
? {
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'service.environment',
|
||||
params: environment,
|
||||
type: 'phrases',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: {
|
||||
match_phrase: {
|
||||
'service.environment': environment,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
const transactionTypeFilter =
|
||||
transactionType && transactionType !== ALL_VALUE
|
||||
? {
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'transaction.type',
|
||||
params: transactionType,
|
||||
type: 'phrases',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: {
|
||||
match_phrase: {
|
||||
'transaction.type': transactionType,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
const transactionNameFilter =
|
||||
transactionName && transactionName !== ALL_VALUE
|
||||
? {
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'transaction.name',
|
||||
params: transactionName,
|
||||
type: 'phrases',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: {
|
||||
match_phrase: {
|
||||
'transaction.name': transactionName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
return [
|
||||
serviceNameFilter,
|
||||
environmentFilter,
|
||||
transactionTypeFilter,
|
||||
transactionNameFilter,
|
||||
].filter((value) => !!value) as FiltersSchema;
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip } from '@elastic/eui';
|
||||
import { APMTransactionDurationIndicator } from '@kbn/slo-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
@ -16,11 +17,35 @@ import { CreateSLOForm } from '../../types';
|
|||
import { FieldSelector } from '../apm_common/field_selector';
|
||||
import { DataPreviewChart } from '../common/data_preview_chart';
|
||||
import { QueryBuilder } from '../common/query_builder';
|
||||
import { formatAllFilters } from '../../helpers/format_filters';
|
||||
import { getGroupByCardinalityFilters } from '../apm_common/get_group_by_cardinality_filters';
|
||||
|
||||
export function ApmLatencyIndicatorTypeForm() {
|
||||
const { control, watch, getFieldState, setValue } = useFormContext<CreateSLOForm>();
|
||||
const { control, watch, getFieldState, setValue } =
|
||||
useFormContext<CreateSLOForm<APMTransactionDurationIndicator>>();
|
||||
const { data: apmIndex } = useFetchApmIndex();
|
||||
|
||||
const [
|
||||
serviceName = '',
|
||||
environment = '',
|
||||
transactionType = '',
|
||||
transactionName = '',
|
||||
globalFilters,
|
||||
] = watch([
|
||||
'indicator.params.service',
|
||||
'indicator.params.environment',
|
||||
'indicator.params.transactionType',
|
||||
'indicator.params.transactionName',
|
||||
'indicator.params.filter',
|
||||
]);
|
||||
const indicatorParamsFilters = getGroupByCardinalityFilters({
|
||||
serviceName,
|
||||
environment,
|
||||
transactionType,
|
||||
transactionName,
|
||||
});
|
||||
const allFilters = formatAllFilters(globalFilters, indicatorParamsFilters);
|
||||
|
||||
useEffect(() => {
|
||||
if (apmIndex !== '') {
|
||||
setValue('indicator.params.index', apmIndex);
|
||||
|
@ -160,7 +185,7 @@ export function ApmLatencyIndicatorTypeForm() {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<GroupByField dataView={dataView} isLoading={isIndexFieldsLoading} />
|
||||
<GroupByField dataView={dataView} isLoading={isIndexFieldsLoading} filters={allFilters} />
|
||||
|
||||
<DataPreviewChart />
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALL_VALUE } from '@kbn/slo-schema';
|
||||
import { ALL_VALUE, QuerySchema } from '@kbn/slo-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiIconTip } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
@ -16,7 +16,15 @@ import { CreateSLOForm } from '../../types';
|
|||
import { IndexFieldSelector } from './index_field_selector';
|
||||
import { GroupByCardinality } from './group_by_cardinality';
|
||||
|
||||
export function GroupByField({ dataView, isLoading }: { dataView?: DataView; isLoading: boolean }) {
|
||||
export function GroupByField({
|
||||
dataView,
|
||||
isLoading,
|
||||
filters,
|
||||
}: {
|
||||
dataView?: DataView;
|
||||
isLoading: boolean;
|
||||
filters?: QuerySchema;
|
||||
}) {
|
||||
const { watch } = useFormContext<CreateSLOForm>();
|
||||
|
||||
const groupByFields = dataView?.fields?.filter((field) => canGroupBy(field)) ?? [];
|
||||
|
@ -48,7 +56,7 @@ export function GroupByField({ dataView, isLoading }: { dataView?: DataView; isL
|
|||
isLoading={!!index && isLoading}
|
||||
isDisabled={!index}
|
||||
/>
|
||||
<GroupByCardinality />
|
||||
<GroupByCardinality customFilters={filters} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
* 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';
|
||||
import { getGroupByCardinalityFilters } from './synthetics_availability_indicator_type_form';
|
||||
|
||||
describe('get group by cardinality filters', () => {
|
||||
it('formats filters correctly', () => {
|
||||
|
@ -80,252 +77,3 @@ describe('get group by cardinality filters', () => {
|
|||
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"',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,16 +11,16 @@ import {
|
|||
ALL_VALUE,
|
||||
SyntheticsAvailabilityIndicator,
|
||||
QuerySchema,
|
||||
kqlQuerySchema,
|
||||
kqlWithFiltersSchema,
|
||||
FiltersSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import { Filter, FilterStateStore } from '@kbn/es-query';
|
||||
import { 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';
|
||||
import { formatAllFilters } from '../../helpers/format_filters';
|
||||
|
||||
const ONE_DAY_IN_MILLISECONDS = 1 * 60 * 60 * 1000 * 24;
|
||||
|
||||
|
@ -45,7 +45,7 @@ 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(
|
||||
const groupByCardinalityFilters = getGroupByCardinalityFilters(
|
||||
filters.monitorIds,
|
||||
filters.projects,
|
||||
filters.tags
|
||||
|
@ -156,7 +156,7 @@ export const getGroupByCardinalityFilters = (
|
|||
monitorIds: string[],
|
||||
projects: string[],
|
||||
tags: string[]
|
||||
): Filter[] => {
|
||||
): FiltersSchema => {
|
||||
const monitorIdFilters = monitorIds.length
|
||||
? {
|
||||
meta: {
|
||||
|
@ -235,19 +235,5 @@ export const getGroupByCardinalityFilters = (
|
|||
}
|
||||
: 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],
|
||||
};
|
||||
}
|
||||
return [monitorIdFilters, projectFilters, tagFilters].filter((value) => !!value) as FiltersSchema;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* 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 { getGroupByCardinalityFilters } from '../components/synthetics_availability/synthetics_availability_indicator_type_form';
|
||||
import { formatAllFilters } from './format_filters';
|
||||
|
||||
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"',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { QuerySchema, FiltersSchema, kqlQuerySchema, kqlWithFiltersSchema } from '@kbn/slo-schema';
|
||||
|
||||
export const formatAllFilters = (
|
||||
globalFilters: QuerySchema = '',
|
||||
groupByCardinalityFilters: FiltersSchema
|
||||
): QuerySchema => {
|
||||
if (kqlQuerySchema.is(globalFilters)) {
|
||||
return { kqlQuery: globalFilters, filters: groupByCardinalityFilters };
|
||||
} else if (kqlWithFiltersSchema.is(globalFilters)) {
|
||||
return {
|
||||
kqlQuery: globalFilters.kqlQuery,
|
||||
filters: [...globalFilters.filters, ...groupByCardinalityFilters],
|
||||
};
|
||||
} else {
|
||||
return { kqlQuery: '', filters: groupByCardinalityFilters };
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue