[SLOs] Added unified search into form (#176496)

## Summary

Fixes https://github.com/elastic/kibana/issues/176276

Added unified search into form !!

Update schema to reflect new changes !!


<img width="1189" alt="image"
src="b1ba4da4-f23e-435c-8778-b819bc2ff7be">
This commit is contained in:
Shahzad 2024-02-13 21:08:10 +01:00 committed by GitHub
parent 0481436d0e
commit 2d36a14de9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 505 additions and 122 deletions

View file

@ -34,6 +34,8 @@ import {
timesliceMetricBasicMetricWithField,
timesliceMetricDocCountMetric,
timesliceMetricPercentileMetric,
kqlWithFiltersSchema,
querySchema,
} from '../schema';
const createSLOParamsSchema = t.type({
@ -71,6 +73,8 @@ const getPreviewDataParamsSchema = t.type({
}),
t.partial({
objective: objectiveSchema,
instanceId: t.string,
groupBy: t.string,
}),
]),
});
@ -334,6 +338,8 @@ type TimesclieMetricPercentileMetric = t.OutputOf<typeof timesliceMetricPercenti
type HistogramIndicator = t.OutputOf<typeof histogramIndicatorSchema>;
type KQLCustomIndicator = t.OutputOf<typeof kqlCustomIndicatorSchema>;
type GroupSummary = t.TypeOf<typeof groupSummarySchema>;
type KqlWithFiltersSchema = t.TypeOf<typeof kqlWithFiltersSchema>;
type QuerySchema = t.TypeOf<typeof querySchema>;
export {
createSLOParamsSchema,
@ -409,4 +415,6 @@ export type {
KQLCustomIndicator,
TimeWindow,
GroupSummary,
KqlWithFiltersSchema,
QuerySchema,
};

View file

@ -83,6 +83,35 @@ const previewDataSchema = t.intersection([
const dateRangeSchema = t.type({ from: dateType, to: dateType });
const kqlQuerySchema = t.string;
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),
})
),
});
const querySchema = t.union([kqlQuerySchema, kqlWithFiltersSchema]);
export {
ALL_VALUE,
allOrAnyString,
@ -94,4 +123,7 @@ export {
statusSchema,
summarySchema,
groupSummarySchema,
kqlWithFiltersSchema,
querySchema,
kqlQuerySchema,
};

View file

@ -6,7 +6,7 @@
*/
import * as t from 'io-ts';
import { allOrAnyString, dateRangeSchema } from './common';
import { allOrAnyString, dateRangeSchema, querySchema } from './common';
const apmTransactionDurationIndicatorTypeSchema = t.literal('sli.apm.transactionDuration');
const apmTransactionDurationIndicatorSchema = t.type({
@ -21,7 +21,7 @@ const apmTransactionDurationIndicatorSchema = t.type({
index: t.string,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]),
});
@ -38,7 +38,7 @@ const apmTransactionErrorRateIndicatorSchema = t.type({
index: t.string,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]),
});
@ -49,12 +49,12 @@ const kqlCustomIndicatorSchema = t.type({
params: t.intersection([
t.type({
index: t.string,
good: t.string,
total: t.string,
good: querySchema,
total: querySchema,
timestampField: t.string,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]),
});
@ -83,7 +83,7 @@ const timesliceMetricBasicMetricWithField = t.intersection([
field: t.string,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]);
@ -93,7 +93,7 @@ const timesliceMetricDocCountMetric = t.intersection([
aggregation: t.literal('doc_count'),
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]);
@ -105,7 +105,7 @@ const timesliceMetricPercentileMetric = t.intersection([
percentile: t.number,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]);
@ -131,7 +131,7 @@ const timesliceMetricIndicatorSchema = t.type({
timestampField: t.string,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]),
});
@ -142,7 +142,7 @@ const metricCustomDocCountMetric = t.intersection([
aggregation: t.literal('doc_count'),
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]);
@ -153,7 +153,7 @@ const metricCustomBasicMetric = t.intersection([
field: t.string,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]);
@ -172,7 +172,7 @@ const metricCustomIndicatorSchema = t.type({
timestampField: t.string,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]),
});
@ -186,7 +186,7 @@ const rangeBasedHistogramMetricDef = t.intersection([
to: t.number,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]);
@ -197,7 +197,7 @@ const valueCountBasedHistogramMetricDef = t.intersection([
aggregation: valueCountHistogramMetricType,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]);
@ -217,7 +217,7 @@ const histogramIndicatorSchema = t.type({
total: histogramMetricDef,
}),
t.partial({
filter: t.string,
filter: querySchema,
}),
]),
});

View file

@ -1020,6 +1020,85 @@
}
}
},
"filter_meta": {
"title": "FilterMeta",
"description": "Defines properties for a filter",
"type": "object",
"properties": {
"alias": {
"type": "string",
"nullable": true
},
"disabled": {
"type": "boolean"
},
"negate": {
"type": "boolean"
},
"controlledBy": {
"type": "string"
},
"group": {
"type": "string"
},
"index": {
"type": "string"
},
"isMultiIndex": {
"type": "boolean"
},
"type": {
"type": "string"
},
"key": {
"type": "string"
},
"params": {
"type": "object"
},
"value": {
"type": "string"
}
}
},
"filter": {
"title": "Filter",
"description": "Defines properties for a filter",
"type": "object",
"properties": {
"query": {
"type": "object"
},
"meta": {
"$ref": "#/components/schemas/filter_meta"
}
}
},
"kql_with_filters": {
"title": "KQL with filters",
"description": "Defines properties for a filter",
"oneOf": [
{
"description": "the KQL query to filter the documents with.",
"type": "string",
"example": "field.environment : \"production\" and service.name : \"my-service\""
},
{
"type": "object",
"properties": {
"kqlQuery": {
"type": "string"
},
"filters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/filter"
}
}
}
}
]
},
"indicator_properties_custom_kql": {
"title": "Custom KQL",
"required": [
@ -1047,17 +1126,16 @@
},
"filter": {
"description": "the KQL query to filter the documents with.",
"type": "string",
"example": "field.environment : \"production\" and service.name : \"my-service\""
"$ref": "#/components/schemas/kql_with_filters"
},
"good": {
"description": "the KQL query used to define the good events.",
"type": "string",
"$ref": "#/components/schemas/kql_with_filters",
"example": "request.latency <= 150 and request.status_code : \"2xx\""
},
"total": {
"description": "the KQL query used to define all events.",
"type": "string",
"$ref": "#/components/schemas/kql_with_filters",
"example": ""
},
"timestampField": {

View file

@ -634,6 +634,58 @@ components:
description: The type of indicator.
type: string
example: sli.apm.transactionDuration
filter_meta:
title: FilterMeta
description: Defines properties for a filter
type: object
properties:
alias:
type: string
nullable: true
disabled:
type: boolean
negate:
type: boolean
controlledBy:
type: string
group:
type: string
index:
type: string
isMultiIndex:
type: boolean
type:
type: string
key:
type: string
params:
type: object
value:
type: string
filter:
title: Filter
description: Defines properties for a filter
type: object
properties:
query:
type: object
meta:
$ref: '#/components/schemas/filter_meta'
kql_with_filters:
title: KQL with filters
description: Defines properties for a filter
oneOf:
- description: the KQL query to filter the documents with.
type: string
example: 'field.environment : "production" and service.name : "my-service"'
- type: object
properties:
kqlQuery:
type: string
filters:
type: array
items:
$ref: '#/components/schemas/filter'
indicator_properties_custom_kql:
title: Custom KQL
required:
@ -658,15 +710,14 @@ components:
example: my-service-*
filter:
description: the KQL query to filter the documents with.
type: string
example: 'field.environment : "production" and service.name : "my-service"'
$ref: '#/components/schemas/kql_with_filters'
good:
description: the KQL query used to define the good events.
type: string
$ref: '#/components/schemas/kql_with_filters'
example: 'request.latency <= 150 and request.status_code : "2xx"'
total:
description: the KQL query used to define all events.
type: string
$ref: '#/components/schemas/kql_with_filters'
example: ''
timestampField:
description: |

View file

@ -0,0 +1,8 @@
title: Filter
description: Defines properties for a filter
type: object
properties:
query:
type: object
meta:
$ref: "filter_meta.yaml"

View file

@ -0,0 +1,27 @@
title: FilterMeta
description: Defines properties for a filter
type: object
properties:
alias:
type: string
nullable: true
disabled:
type: boolean
negate:
type: boolean
controlledBy:
type: string
group:
type: string
index:
type: string
isMultiIndex:
type: boolean
type:
type: string
key:
type: string
params:
type: object
value:
type: string

View file

@ -21,15 +21,14 @@ properties:
example: my-service-*
filter:
description: the KQL query to filter the documents with.
type: string
example: 'field.environment : "production" and service.name : "my-service"'
$ref: "kql_with_filters.yaml"
good:
description: the KQL query used to define the good events.
type: string
$ref: "kql_with_filters.yaml"
example: 'request.latency <= 150 and request.status_code : "2xx"'
total:
description: the KQL query used to define all events.
type: string
$ref: "kql_with_filters.yaml"
example: ''
timestampField:
description: >

View file

@ -0,0 +1,15 @@
title: KQL with filters
description: Defines properties for a filter
oneOf:
- description: the KQL query to filter the documents with.
type: string
example: 'field.environment : "production" and service.name : "my-service"'
- type: object
properties:
kqlQuery:
type: string
filters:
type: array
items:
$ref: "filter.yaml"

View file

@ -18,12 +18,21 @@ export interface UseGetPreviewData {
isError: boolean;
}
export function useGetPreviewData(
isValid: boolean,
indicator: Indicator,
range: { start: number; end: number },
objective?: Objective
): UseGetPreviewData {
export function useGetPreviewData({
isValid,
range,
indicator,
objective,
groupBy,
instanceId,
}: {
isValid: boolean;
groupBy?: string;
instanceId?: string;
objective?: Objective;
indicator: Indicator;
range: { start: number; end: number };
}): UseGetPreviewData {
const { http } = useKibana().services;
const { isInitialLoading, isLoading, isError, isSuccess, data } = useQuery({
@ -35,6 +44,8 @@ export function useGetPreviewData(
body: JSON.stringify({
indicator,
range,
groupBy,
instanceId,
...(objective ? { objective } : null),
}),
signal,

View file

@ -32,8 +32,8 @@ import {
import numeral from '@elastic/numeral';
import { useActiveCursor } from '@kbn/charts-plugin/public';
import { i18n } from '@kbn/i18n';
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
import { cloneDeep, max, min } from 'lodash';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import { max, min } from 'lodash';
import moment from 'moment';
import React, { useRef } from 'react';
import { useGetPreviewData } from '../../../hooks/slo/use_get_preview_data';
@ -57,16 +57,13 @@ export function EventsChartPanel({ slo, range }: Props) {
isDateHistogram: true,
});
const instanceIdFilter =
slo.instanceId !== ALL_VALUE ? `${slo.groupBy}: "${slo.instanceId}"` : null;
const sloIndicator = cloneDeep(slo.indicator);
if (instanceIdFilter) {
sloIndicator.params.filter =
!!sloIndicator.params.filter && sloIndicator.params.filter.length > 0
? `${sloIndicator.params.filter} and ${instanceIdFilter}`
: instanceIdFilter;
}
const { isLoading, data } = useGetPreviewData(true, sloIndicator, range, slo.objective);
const { isLoading, data } = useGetPreviewData({
range,
isValid: true,
indicator: slo.indicator,
groupBy: slo.groupBy,
instanceId: slo.instanceId,
});
const dateFormat = uiSettings.get('dateFormat');

View file

@ -8,6 +8,8 @@
import { EuiFormRow } from '@elastic/eui';
import React, { ReactNode } from 'react';
import { Controller, FieldPath, useFormContext } from 'react-hook-form';
import styled from 'styled-components';
import { kqlQuerySchema } from '@kbn/slo-schema';
import { useCreateDataView } from '../../../../hooks/use_create_data_view';
import { useKibana } from '../../../../utils/kibana_react';
import { CreateSLOForm } from '../../types';
@ -34,7 +36,7 @@ export function QueryBuilder({
}: Props) {
const {
unifiedSearch: {
ui: { QueryStringInput },
ui: { SearchBar },
},
} = useKibana().services;
@ -67,23 +69,56 @@ export function QueryBuilder({
required: Boolean(required) && Boolean(dataView),
}}
render={({ field, fieldState }) => (
<QueryStringInput
appName="Observability"
dataTestSubj={dataTestSubj}
disableLanguageSwitcher
indexPatterns={dataView ? [dataView] : []}
isDisabled={!dataView}
isInvalid={fieldState.invalid}
languageSwitcherPopoverAnchorPosition="rightDown"
placeholder={placeholder}
query={{ query: String(field.value), language: 'kuery' }}
size="s"
onChange={(value) => {
field.onChange(value.query);
}}
/>
<Container>
<SearchBar
appName="Observability"
dataTestSubj={dataTestSubj}
indexPatterns={dataView ? [dataView] : []}
isDisabled={!dataView}
placeholder={placeholder}
query={{
query: kqlQuerySchema.is(field.value) ? String(field.value) : field.value.kqlQuery,
language: 'kuery',
}}
onQuerySubmit={(value) => {
if (kqlQuerySchema.is(field.value)) {
field.onChange(String(value.query?.query));
} else {
field.onChange({
...(field.value ?? {}),
kqlQuery: String(value.query?.query),
});
}
}}
onFiltersUpdated={(filters) => {
if (kqlQuerySchema.is(field.value)) {
field.onChange({
filters,
kqlQuery: field.value,
});
} else {
field.onChange({
...(field.value ?? {}),
filters,
});
}
}}
showDatePicker={false}
showSubmitButton={false}
showQueryInput={true}
disableQueryLanguageSwitcher={true}
onClearSavedQuery={() => {}}
filters={kqlQuerySchema.is(field.value) ? [] : field.value?.filters ?? []}
/>
</Container>
)}
/>
</EuiFormRow>
);
}
const Container = styled.div`
.uniSearchBar {
padding: 0;
}
`;

View file

@ -105,6 +105,45 @@ Object {
}
`;
exports[`Transform partial URL state into form state handles the 'filters' URL state 1`] = `
Object {
"budgetingMethod": "occurrences",
"description": "",
"groupBy": "*",
"indicator": Object {
"params": Object {
"filter": "",
"good": Object {
"filters": Array [
Object {
"meta": Object {
"alias": "override-alias",
"disabled": true,
"key": "override",
"negate": true,
},
},
],
"kqlQuery": "some.override.filter:'foo'",
},
"index": "override-index",
"timestampField": "",
"total": "",
},
"type": "sli.kql.custom",
},
"name": "",
"objective": Object {
"target": 99,
},
"tags": Array [],
"timeWindow": Object {
"duration": "30d",
"type": "rolling",
},
}
`;
exports[`Transform partial URL state into form state handles the 'objective' URL state 1`] = `
Object {
"budgetingMethod": "occurrences",

View file

@ -95,4 +95,30 @@ describe('Transform partial URL state into form state', () => {
transform({ objective: { target: 0.945, timesliceTarget: 0.95, timesliceWindow: '2m' } })
).toMatchSnapshot();
});
it("handles the 'filters' URL state", () => {
expect(
transform({
indicator: {
type: 'sli.kql.custom',
params: {
good: {
kqlQuery: "some.override.filter:'foo'",
filters: [
{
meta: {
alias: 'override-alias',
negate: true,
disabled: true,
key: 'override',
},
},
],
},
index: 'override-index',
},
},
})
).toMatchSnapshot();
});
});

View file

@ -29,5 +29,9 @@ export function useDebouncedGetPreviewData(
}
}, [indicatorState, serializedIndicator, store]);
return useGetPreviewData(isIndicatorValid, JSON.parse(indicatorState), range);
return useGetPreviewData({
isValid: isIndicatorValid,
indicator: JSON.parse(indicatorState),
range,
});
}

View file

@ -121,6 +121,7 @@ const mockKibana = (license: ILicense | null = licenseMock) => {
unifiedSearch: {
ui: {
QueryStringInput: () => <div>Query String Input</div>,
SearchBar: () => <div>Search Bar</div>,
},
autocomplete: {
hasQuerySuggestions: () => {},

View file

@ -9,6 +9,7 @@ import {
ALL_VALUE,
apmTransactionDurationIndicatorSchema,
apmTransactionErrorRateIndicatorSchema,
kqlQuerySchema,
SLOResponse,
} from '@kbn/slo-schema';
@ -48,7 +49,7 @@ export function convertSliApmParamsToApmAppDeeplinkUrl(slo: SLOResponse): string
if (transactionName && transactionName !== ALL_VALUE) {
kueryParams.push(`transaction.name : "${transactionName}"`);
}
if (filter && filter.length > 0) {
if (filter && kqlQuerySchema.is(filter) && filter.length > 0) {
kueryParams.push(filter);
}
if (groupBy !== ALL_VALUE && instanceId !== ALL_VALUE) {

View file

@ -6,7 +6,7 @@
*/
import { metricCustomDocCountMetric, MetricCustomIndicator } from '@kbn/slo-schema';
import { getElastichsearchQueryOrThrow } from '../transform_generators';
import { getElasticsearchQueryOrThrow } from '../transform_generators';
type MetricCustomMetricDef =
| MetricCustomIndicator['params']['good']
@ -18,7 +18,7 @@ export class GetCustomMetricIndicatorAggregation {
private buildMetricAggregations(type: 'good' | 'total', metricDef: MetricCustomMetricDef) {
return metricDef.metrics.reduce((acc, metric) => {
const filter = metric.filter
? getElastichsearchQueryOrThrow(metric.filter)
? getElasticsearchQueryOrThrow(metric.filter)
: { match_all: {} };
if (metricCustomDocCountMetric.is(metric)) {

View file

@ -7,7 +7,7 @@
import { HistogramIndicator } from '@kbn/slo-schema';
import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { getElastichsearchQueryOrThrow } from '../transform_generators/common';
import { getElasticsearchQueryOrThrow } from '../transform_generators/common';
type HistogramIndicatorDef =
| HistogramIndicator['params']['good']
@ -18,7 +18,7 @@ export class GetHistogramIndicatorAggregation {
private buildAggregation(indicator: HistogramIndicatorDef): AggregationsAggregationContainer {
const filter = indicator.filter
? getElastichsearchQueryOrThrow(indicator.filter)
? getElasticsearchQueryOrThrow(indicator.filter)
: { match_all: {} };
if (indicator.aggregation === 'value_count') {
return {

View file

@ -9,7 +9,7 @@ import { TimesliceMetricIndicator, timesliceMetricMetricDef } from '@kbn/slo-sch
import * as t from 'io-ts';
import { assertNever } from '@kbn/std';
import { getElastichsearchQueryOrThrow } from '../transform_generators';
import { getElasticsearchQueryOrThrow } from '../transform_generators';
type TimesliceMetricDef = TimesliceMetricIndicator['params']['metric'];
type TimesliceMetricMetricDef = t.TypeOf<typeof timesliceMetricMetricDef>;
@ -85,7 +85,7 @@ export class GetTimesliceMetricIndicatorAggregation {
private buildMetricAggregations(metricDef: TimesliceMetricDef) {
return metricDef.metrics.reduce((acc, metric) => {
const filter = metric.filter
? getElastichsearchQueryOrThrow(metric.filter)
? getElasticsearchQueryOrThrow(metric.filter)
: { match_all: {} };
const aggs = { metric: this.buildAggregation(metric) };
return {

View file

@ -15,7 +15,7 @@ import {
DEFAULT_SLO_GROUPS_PAGE_SIZE,
} from '../../../common/slo/constants';
import { Status } from '../../domain/models';
import { getElastichsearchQueryOrThrow } from './transform_generators';
import { getElasticsearchQueryOrThrow } from './transform_generators';
const DEFAULT_PAGE = 1;
const MAX_PER_PAGE = 5000;
@ -66,7 +66,7 @@ export class FindSLOGroups {
bool: {
filter: [
{ term: { spaceId: this.spaceId } },
getElastichsearchQueryOrThrow(kqlQuery),
getElasticsearchQueryOrThrow(kqlQuery),
...(parsedFilters.filter ?? []),
],
},

View file

@ -6,7 +6,6 @@
*/
import { calculateAuto } from '@kbn/calculate-auto';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import {
ALL_VALUE,
APMTransactionErrorRateIndicator,
@ -21,10 +20,10 @@ import { assertNever } from '@kbn/std';
import moment from 'moment';
import { ElasticsearchClient } from '@kbn/core/server';
import { estypes } from '@elastic/elasticsearch';
import { getElasticsearchQueryOrThrow } from './transform_generators';
import { typedSearch } from '../../utils/queries';
import { APMTransactionDurationIndicator } from '../../domain/models';
import { computeSLI } from '../../domain/services';
import { InvalidQueryError } from '../../errors';
import {
GetCustomMetricIndicatorAggregation,
GetHistogramIndicatorAggregation,
@ -37,6 +36,8 @@ interface Options {
end: number;
};
interval: string;
instanceId?: string;
groupBy?: string;
}
export class GetPreviewData {
constructor(private esClient: ElasticsearchClient) {}
@ -46,6 +47,11 @@ export class GetPreviewData {
options: Options
): Promise<GetPreviewDataResponse> {
const filter: estypes.QueryDslQueryContainer[] = [];
if (options.instanceId !== ALL_VALUE && options.groupBy) {
filter.push({
term: { [options.groupBy]: options.instanceId },
});
}
if (indicator.params.service !== ALL_VALUE)
filter.push({
match: { 'service.name': indicator.params.service },
@ -138,7 +144,12 @@ export class GetPreviewData {
indicator: APMTransactionErrorRateIndicator,
options: Options
): Promise<GetPreviewDataResponse> {
const filter = [];
const filter: estypes.QueryDslQueryContainer[] = [];
if (options.instanceId !== ALL_VALUE && options.groupBy) {
filter.push({
term: { [options.groupBy]: options.instanceId },
});
}
if (indicator.params.service !== ALL_VALUE)
filter.push({
match: { 'service.name': indicator.params.service },
@ -225,15 +236,24 @@ export class GetPreviewData {
const getHistogramIndicatorAggregations = new GetHistogramIndicatorAggregation(indicator);
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
const timestampField = indicator.params.timestampField;
const filter: estypes.QueryDslQueryContainer[] = [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
];
if (options.instanceId !== ALL_VALUE && options.groupBy) {
filter.push({
term: { [options.groupBy]: options.instanceId },
});
}
const result = await this.esClient.search({
index: indicator.params.index,
size: 0,
query: {
bool: {
filter: [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
],
filter,
},
},
aggs: {
@ -280,15 +300,23 @@ export class GetPreviewData {
const timestampField = indicator.params.timestampField;
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
const getCustomMetricIndicatorAggregation = new GetCustomMetricIndicatorAggregation(indicator);
const filter: estypes.QueryDslQueryContainer[] = [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
];
if (options.instanceId !== ALL_VALUE && options.groupBy) {
filter.push({
term: { [options.groupBy]: options.instanceId },
});
}
const result = await this.esClient.search({
index: indicator.params.index,
size: 0,
query: {
bool: {
filter: [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
],
filter,
},
},
aggs: {
@ -337,15 +365,24 @@ export class GetPreviewData {
const getCustomMetricIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(
indicator
);
const filter: estypes.QueryDslQueryContainer[] = [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
];
if (options.instanceId !== ALL_VALUE && options.groupBy) {
filter.push({
term: { [options.groupBy]: options.instanceId },
});
}
const result = await this.esClient.search({
index: indicator.params.index,
size: 0,
query: {
bool: {
filter: [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
],
filter,
},
},
aggs: {
@ -380,15 +417,23 @@ export class GetPreviewData {
const goodQuery = getElasticsearchQueryOrThrow(indicator.params.good);
const totalQuery = getElasticsearchQueryOrThrow(indicator.params.total);
const timestampField = indicator.params.timestampField;
const filter: estypes.QueryDslQueryContainer[] = [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
];
if (options.instanceId !== ALL_VALUE && options.groupBy) {
filter.push({
term: { [options.groupBy]: options.instanceId },
});
}
const result = await this.esClient.search({
index: indicator.params.index,
size: 0,
query: {
bool: {
filter: [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
filterQuery,
],
filter,
},
},
aggs: {
@ -442,7 +487,9 @@ export class GetPreviewData {
1
);
const options: Options = {
instanceId: params.instanceId,
range: params.range,
groupBy: params.groupBy,
interval: `${bucketSize}m`,
};
@ -468,11 +515,3 @@ export class GetPreviewData {
}
}
}
function getElasticsearchQueryOrThrow(kuery: string | undefined = '') {
try {
return toElasticsearchQuery(fromKueryExpression(kuery));
} catch (err) {
throw new InvalidQueryError(`Invalid kuery: ${kuery}`);
}
}

View file

@ -13,7 +13,7 @@ import { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types';
import { SLO_SUMMARY_DESTINATION_INDEX_PATTERN } from '../../../common/slo/constants';
import { SLOId, Status, Summary } from '../../domain/models';
import { toHighPrecision } from '../../utils/number';
import { getElastichsearchQueryOrThrow } from './transform_generators';
import { getElasticsearchQueryOrThrow } from './transform_generators';
interface EsSummaryDocument {
slo: {
@ -81,7 +81,7 @@ export class DefaultSummarySearchClient implements SummarySearchClient {
bool: {
filter: [
{ term: { spaceId: this.spaceId } },
getElastichsearchQueryOrThrow(kqlQuery),
getElasticsearchQueryOrThrow(kqlQuery),
...(parsedFilters.filter ?? []),
],
must_not: [...(parsedFilters.must_not ?? [])],

View file

@ -12,7 +12,8 @@ import {
timeslicesBudgetingMethodSchema,
} from '@kbn/slo-schema';
import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { getElastichsearchQueryOrThrow, TransformGenerator } from '.';
import { estypes } from '@elastic/elasticsearch';
import { getElasticsearchQueryOrThrow, TransformGenerator } from '.';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_NAME,
@ -22,7 +23,6 @@ import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo
import { APMTransactionDurationIndicator, SLO } from '../../../domain/models';
import { InvalidTransformError } from '../../../errors';
import { parseIndex } from './common';
import { Query } from './types';
export class ApmTransactionDurationTransformGenerator extends TransformGenerator {
public getTransformParams(slo: SLO): TransformPutTransformRequest {
@ -69,7 +69,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
}
private buildSource(slo: SLO, indicator: APMTransactionDurationIndicator) {
const queryFilter: Query[] = [
const queryFilter: estypes.QueryDslQueryContainer[] = [
{
range: {
'@timestamp': {
@ -112,7 +112,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
}
if (!!indicator.params.filter) {
queryFilter.push(getElastichsearchQueryOrThrow(indicator.params.filter));
queryFilter.push(getElasticsearchQueryOrThrow(indicator.params.filter));
}
return {

View file

@ -11,7 +11,8 @@ import {
apmTransactionErrorRateIndicatorSchema,
timeslicesBudgetingMethodSchema,
} from '@kbn/slo-schema';
import { getElastichsearchQueryOrThrow, TransformGenerator } from '.';
import { estypes } from '@elastic/elasticsearch';
import { getElasticsearchQueryOrThrow, TransformGenerator } from '.';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_NAME,
@ -21,7 +22,6 @@ import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo
import { APMTransactionErrorRateIndicator, SLO } from '../../../domain/models';
import { InvalidTransformError } from '../../../errors';
import { parseIndex } from './common';
import { Query } from './types';
export class ApmTransactionErrorRateTransformGenerator extends TransformGenerator {
public getTransformParams(slo: SLO): TransformPutTransformRequest {
@ -68,7 +68,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
}
private buildSource(slo: SLO, indicator: APMTransactionErrorRateIndicator) {
const queryFilter: Query[] = [
const queryFilter: estypes.QueryDslQueryContainer[] = [
{
range: {
'@timestamp': {
@ -111,7 +111,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
}
if (indicator.params.filter) {
queryFilter.push(getElastichsearchQueryOrThrow(indicator.params.filter));
queryFilter.push(getElasticsearchQueryOrThrow(indicator.params.filter));
}
return {

View file

@ -5,12 +5,24 @@
* 2.0.
*/
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { buildEsQuery, fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { QuerySchema, kqlQuerySchema } from '@kbn/slo-schema';
import { InvalidTransformError } from '../../../errors';
export function getElastichsearchQueryOrThrow(kuery: string | undefined = '') {
export function getElasticsearchQueryOrThrow(kuery: QuerySchema = '') {
try {
return toElasticsearchQuery(fromKueryExpression(kuery));
if (kqlQuerySchema.is(kuery)) {
return toElasticsearchQuery(fromKueryExpression(kuery));
} else {
return buildEsQuery(
undefined,
{
query: kuery?.kqlQuery,
language: 'kuery',
},
kuery?.filters
);
}
} catch (err) {
throw new InvalidTransformError(`Invalid KQL: ${kuery}`);
}

View file

@ -14,7 +14,7 @@ import {
import { InvalidTransformError } from '../../../errors';
import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template';
import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import { getElasticsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import {
SLO_DESTINATION_INDEX_NAME,
SLO_INGEST_PIPELINE_NAME,
@ -58,7 +58,7 @@ export class HistogramTransformGenerator extends TransformGenerator {
},
},
},
getElastichsearchQueryOrThrow(indicator.params.filter),
getElasticsearchQueryOrThrow(indicator.params.filter),
],
},
},

View file

@ -10,7 +10,7 @@ import { kqlCustomIndicatorSchema, timeslicesBudgetingMethodSchema } from '@kbn/
import { InvalidTransformError } from '../../../errors';
import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template';
import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import { getElasticsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import {
SLO_DESTINATION_INDEX_NAME,
SLO_INGEST_PIPELINE_NAME,
@ -53,7 +53,7 @@ export class KQLCustomTransformGenerator extends TransformGenerator {
},
},
},
getElastichsearchQueryOrThrow(indicator.params.filter),
getElasticsearchQueryOrThrow(indicator.params.filter),
],
},
},
@ -68,8 +68,8 @@ export class KQLCustomTransformGenerator extends TransformGenerator {
}
private buildAggregations(slo: SLO, indicator: KQLCustomIndicator) {
const numerator = getElastichsearchQueryOrThrow(indicator.params.good);
const denominator = getElastichsearchQueryOrThrow(indicator.params.total);
const numerator = getElasticsearchQueryOrThrow(indicator.params.good);
const denominator = getElasticsearchQueryOrThrow(indicator.params.total);
return {
'slo.numerator': {

View file

@ -10,7 +10,7 @@ import { metricCustomIndicatorSchema, timeslicesBudgetingMethodSchema } from '@k
import { InvalidTransformError } from '../../../errors';
import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template';
import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import { getElasticsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import {
SLO_DESTINATION_INDEX_NAME,
SLO_INGEST_PIPELINE_NAME,
@ -56,7 +56,7 @@ export class MetricCustomTransformGenerator extends TransformGenerator {
},
},
},
getElastichsearchQueryOrThrow(indicator.params.filter),
getElasticsearchQueryOrThrow(indicator.params.filter),
],
},
},

View file

@ -15,7 +15,7 @@ import {
import { InvalidTransformError } from '../../../errors';
import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template';
import { getElastichsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import { getElasticsearchQueryOrThrow, parseIndex, TransformGenerator } from '.';
import {
SLO_DESTINATION_INDEX_NAME,
SLO_INGEST_PIPELINE_NAME,
@ -61,7 +61,7 @@ export class TimesliceMetricTransformGenerator extends TransformGenerator {
},
},
},
getElastichsearchQueryOrThrow(indicator.params.filter),
getElasticsearchQueryOrThrow(indicator.params.filter),
],
},
},