Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions x-pack/solutions/observability/plugins/slo/common/parse_kuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,31 @@
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import type { QuerySchema } from '@kbn/slo-schema';
import { kqlQuerySchema } from '@kbn/slo-schema';
import { buildEsQuery, fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { buildEsQuery } from '@kbn/es-query';
import type { DataViewBase } from '@kbn/es-query';
import { isEmpty } from 'lodash';

export function getElasticsearchQueryOrThrow(kuery: QuerySchema = ''): QueryDslQueryContainer {
export function getElasticsearchQueryOrThrow(
kuery: QuerySchema = '',
dataView?: DataViewBase
): QueryDslQueryContainer {
try {
if (kqlQuerySchema.is(kuery)) {
return toElasticsearchQuery(fromKueryExpression(kuery));
} else {
return buildEsQuery(
undefined,
{
query: kuery?.kqlQuery,
language: 'kuery',
},
kuery?.filters
);
if (isEmpty(kuery)) {
return { match_all: {} };
}
const kqlQuery = kqlQuerySchema.is(kuery) ? kuery : kuery.kqlQuery;
const filters = kqlQuerySchema.is(kuery) ? [] : kuery.filters;
return buildEsQuery(
dataView,
{
query: kqlQuery,
language: 'kuery',
},
filters,
{
allowLeadingWildcards: true,
}
);
} catch (err) {
// @ts-expect-error `getElasticsearchQueryOrThrow` but it doesn't throw :shrug:
return [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ export function DataPreviewChart({
yAccessors={['value']}
data={(previewData?.results ?? []).map((datum) => ({
date: new Date(datum.date).getTime(),
value: datum.sliValue && datum.sliValue >= 0 ? datum.sliValue : null,
value: datum.sliValue != null && datum.sliValue >= 0 ? datum.sliValue : null,
events: datum.events,
}))}
/>
Expand All @@ -315,7 +315,7 @@ export function DataPreviewChart({
yAccessors={['value']}
data={data.map((datum) => ({
date: new Date(datum.date).getTime(),
value: datum.sliValue && datum.sliValue >= 0 ? datum.sliValue : null,
value: datum.sliValue != null && datum.sliValue >= 0 ? datum.sliValue : null,
events: datum.events,
}))}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const useTableDocs = ({
const errorMessages = getFieldState(name).error?.message;
const filter = watch(name) as QuerySchema;

const esFilter = getElasticsearchQueryOrThrow(filter);
const esFilter = getElasticsearchQueryOrThrow(filter, dataView);

const { data, loading, error } = useEsSearch(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@

import type { MetricCustomIndicator } from '@kbn/slo-schema';
import { metricCustomDocCountMetric } from '@kbn/slo-schema';
import type { DataView } from '@kbn/data-views-plugin/common';
import { getElasticsearchQueryOrThrow } from '../transform_generators';

type MetricCustomMetricDef =
| MetricCustomIndicator['params']['good']
| MetricCustomIndicator['params']['total'];

export class GetCustomMetricIndicatorAggregation {
constructor(private indicator: MetricCustomIndicator) {}
constructor(private indicator: MetricCustomIndicator, private dataView?: DataView) {}

private buildMetricAggregations(type: 'good' | 'total', metricDef: MetricCustomMetricDef) {
return metricDef.metrics.reduce((acc, metric) => {
const filter = metric.filter
? getElasticsearchQueryOrThrow(metric.filter)
? getElasticsearchQueryOrThrow(metric.filter, this.dataView)
: { match_all: {} };

if (metricCustomDocCountMetric.is(metric)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@

import type { HistogramIndicator } from '@kbn/slo-schema';
import type { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/types';
import type { DataView } from '@kbn/data-views-plugin/common';
import { getElasticsearchQueryOrThrow } from '../transform_generators/common';

type HistogramIndicatorDef =
| HistogramIndicator['params']['good']
| HistogramIndicator['params']['total'];

export class GetHistogramIndicatorAggregation {
constructor(private indicator: HistogramIndicator) {}
constructor(private indicator: HistogramIndicator, private dataView?: DataView) {}

private buildAggregation(indicator: HistogramIndicatorDef): AggregationsAggregationContainer {
const filter = indicator.filter
? getElasticsearchQueryOrThrow(indicator.filter)
? getElasticsearchQueryOrThrow(indicator.filter, this.dataView)
: { match_all: {} };
if (indicator.aggregation === 'value_count') {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
import type { TimesliceMetricIndicator, timesliceMetricMetricDef } from '@kbn/slo-schema';
import type * as t from 'io-ts';
import { assertNever } from '@kbn/std';
import type { DataView } from '@kbn/data-views-plugin/common';

import { getElasticsearchQueryOrThrow } from '../transform_generators';

type TimesliceMetricDef = TimesliceMetricIndicator['params']['metric'];
type TimesliceMetricMetricDef = t.TypeOf<typeof timesliceMetricMetricDef>;

export class GetTimesliceMetricIndicatorAggregation {
constructor(private indicator: TimesliceMetricIndicator) {}
constructor(private indicator: TimesliceMetricIndicator, private dataView?: DataView) {}

private buildAggregation(metric: TimesliceMetricMetricDef) {
const { aggregation } = metric;
Expand Down Expand Up @@ -85,7 +86,7 @@ export class GetTimesliceMetricIndicatorAggregation {
private buildMetricAggregations(metricDef: TimesliceMetricDef) {
return metricDef.metrics.reduce((acc, metric) => {
const filter = metric.filter
? getElasticsearchQueryOrThrow(metric.filter)
? getElasticsearchQueryOrThrow(metric.filter, this.dataView)
: { match_all: {} };
const aggs = { metric: this.buildAggregation(metric) };
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,56 @@ import { dataViewsService } from '@kbn/data-views-plugin/server/mocks';
import type { GetPreviewDataParams } from '@kbn/slo-schema';
import { GetPreviewData } from './get_preview_data';
import { oneMinute } from './fixtures/duration';
import { createStubDataView } from '@kbn/data-views-plugin/common/data_views/data_view.stub';
import type { DataViewsService } from '@kbn/data-views-plugin/common';

describe('GetPreviewData', () => {
let esClientMock: ElasticsearchClientMock;
let service: GetPreviewData;
let mockDataViewsService: jest.Mocked<DataViewsService>;

beforeEach(() => {
esClientMock = elasticsearchServiceMock.createElasticsearchClient();
service = new GetPreviewData(esClientMock, 'default', dataViewsService);
mockDataViewsService = {
...dataViewsService,
get: jest.fn().mockImplementation((dataViewId: string) => {
if (dataViewId === 'e7744dbe-a7a4-457b-83aa-539e9c88764c') {
return Promise.resolve(
createStubDataView({
spec: {
id: dataViewId,
title: 'kbn-data-forge-fake_stack.admin-console-*',
timeFieldName: '@timestamp',
fields: {
'http.response.status_code': {
name: 'http.response.status_code',
type: 'number',
esTypes: ['long'],
searchable: true,
aggregatable: true,
readFromDocValues: true,
},
},
},
})
);
}
if (dataViewId === '593f894a-3378-42cc-bafc-61b4877b64b0') {
return Promise.resolve(
createStubDataView({
spec: {
id: dataViewId,
title: 'kbn-data-forge-fake_stack.message_processor-*',
timeFieldName: '@timestamp',
fields: {},
},
})
);
}
return Promise.reject(new Error('Data view not found'));
}),
} as any;
service = new GetPreviewData(esClientMock, 'default', mockDataViewsService);
});

describe("for 'Custom KQL' indicator type", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,20 @@ export class GetPreviewData {
private dataViewService: DataViewsService
) {}

private async buildRuntimeMappings({ dataViewId }: { dataViewId?: string }) {
let dataView: DataView | undefined;
if (dataViewId) {
try {
dataView = await this.dataViewService.get(dataViewId);
} catch (e) {
// If the data view is not found, we will continue without it
}
private async getDataView(dataViewId?: string): Promise<DataView | undefined> {
if (!dataViewId) {
return undefined;
}
try {
return await this.dataViewService.get(dataViewId);
} catch (e) {
// If the data view is not found, we will continue without it
return undefined;
}
}

private async buildRuntimeMappings({ dataViewId }: { dataViewId?: string }) {
const dataView = await this.getDataView(dataViewId);
return dataView?.getRuntimeMappings?.() ?? {};
}

Expand Down Expand Up @@ -101,6 +106,7 @@ export class GetPreviewData {
indicator: APMTransactionDurationIndicator,
options: Options
): Promise<GetPreviewDataResponse> {
const dataView = await this.getDataView(indicator.params.dataViewId);
const filter: estypes.QueryDslQueryContainer[] = [];
const groupingFilters = this.getGroupingFilters(options);
if (groupingFilters) {
Expand All @@ -123,7 +129,7 @@ export class GetPreviewData {
match: { 'transaction.type': indicator.params.transactionType },
});
if (!!indicator.params.filter)
filter.push(getElasticsearchQueryOrThrow(indicator.params.filter));
filter.push(getElasticsearchQueryOrThrow(indicator.params.filter, dataView));

const truncatedThreshold = Math.trunc(indicator.params.threshold * 1000);

Expand Down Expand Up @@ -227,6 +233,7 @@ export class GetPreviewData {
indicator: APMTransactionErrorRateIndicator,
options: Options
): Promise<GetPreviewDataResponse> {
const dataView = await this.getDataView(indicator.params.dataViewId);
const filter: estypes.QueryDslQueryContainer[] = [];
const groupingFilters = this.getGroupingFilters(options);
if (groupingFilters) {
Expand All @@ -249,7 +256,7 @@ export class GetPreviewData {
match: { 'transaction.type': indicator.params.transactionType },
});
if (!!indicator.params.filter)
filter.push(getElasticsearchQueryOrThrow(indicator.params.filter));
filter.push(getElasticsearchQueryOrThrow(indicator.params.filter, dataView));

const index = options.remoteName
? `${options.remoteName}:${indicator.params.index}`
Expand Down Expand Up @@ -345,8 +352,12 @@ export class GetPreviewData {
indicator: HistogramIndicator,
options: Options
): Promise<GetPreviewDataResponse> {
const getHistogramIndicatorAggregations = new GetHistogramIndicatorAggregation(indicator);
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
const dataView = await this.getDataView(indicator.params.dataViewId);
const getHistogramIndicatorAggregations = new GetHistogramIndicatorAggregation(
indicator,
dataView
);
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter, dataView);
const timestampField = indicator.params.timestampField;

const filter: estypes.QueryDslQueryContainer[] = [
Expand Down Expand Up @@ -447,9 +458,14 @@ export class GetPreviewData {
indicator: MetricCustomIndicator,
options: Options
): Promise<GetPreviewDataResponse> {
const dataView = await this.getDataView(indicator.params.dataViewId);
const timestampField = indicator.params.timestampField;
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
const getCustomMetricIndicatorAggregation = new GetCustomMetricIndicatorAggregation(indicator);
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter, dataView);

const getCustomMetricIndicatorAggregation = new GetCustomMetricIndicatorAggregation(
indicator,
dataView
);

const filter: estypes.QueryDslQueryContainer[] = [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
Expand Down Expand Up @@ -549,10 +565,12 @@ export class GetPreviewData {
indicator: TimesliceMetricIndicator,
options: Options
): Promise<GetPreviewDataResponse> {
const dataView = await this.getDataView(indicator.params.dataViewId);
const timestampField = indicator.params.timestampField;
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter, dataView);
const getCustomMetricIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(
indicator
indicator,
dataView
);

const filter: estypes.QueryDslQueryContainer[] = [
Expand Down Expand Up @@ -629,9 +647,11 @@ export class GetPreviewData {
indicator: KQLCustomIndicator,
options: Options
): Promise<GetPreviewDataResponse> {
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
const goodQuery = getElasticsearchQueryOrThrow(indicator.params.good);
const totalQuery = getElasticsearchQueryOrThrow(indicator.params.total);
const dataView = await this.getDataView(indicator.params.dataViewId);
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter, dataView);
const goodQuery = getElasticsearchQueryOrThrow(indicator.params.good, dataView);
const totalQuery = getElasticsearchQueryOrThrow(indicator.params.total, dataView);

const timestampField = indicator.params.timestampField;
const filter: estypes.QueryDslQueryContainer[] = [
{ range: { [timestampField]: { gte: options.range.start, lte: options.range.end } } },
Expand Down Expand Up @@ -682,9 +702,10 @@ export class GetPreviewData {
response.aggregations?.perInterval.buckets.map((bucket) => {
const good = bucket.good?.doc_count ?? 0;
const total = bucket.total?.doc_count ?? 0;
const sliValue = computeSLIForPreview(good, total);
return {
date: bucket.key_as_string,
sliValue: computeSLIForPreview(good, total),
sliValue,
events: {
good,
bad: total - good,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class HistogramTransformGenerator extends TransformGenerator {
await this.buildSource(slo, slo.indicator),
this.buildDestination(slo),
this.buildCommonGroupBy(slo, slo.indicator.params.timestampField),
this.buildAggregations(slo, slo.indicator),
await this.buildAggregations(slo, slo.indicator),
this.buildSettings(slo, slo.indicator.params.timestampField),
slo
);
Expand Down Expand Up @@ -71,8 +71,12 @@ export class HistogramTransformGenerator extends TransformGenerator {
};
}

private buildAggregations(slo: SLODefinition, indicator: HistogramIndicator) {
const getHistogramIndicatorAggregations = new GetHistogramIndicatorAggregation(indicator);
private async buildAggregations(slo: SLODefinition, indicator: HistogramIndicator) {
const dataView = await this.getIndicatorDataView(indicator.params.dataViewId);
const getHistogramIndicatorAggregations = new GetHistogramIndicatorAggregation(
indicator,
dataView
);

return {
...getHistogramIndicatorAggregations.execute({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class KQLCustomTransformGenerator extends TransformGenerator {
bool: {
filter: [
getFilterRange(slo, indicator.params.timestampField, this.isServerless),
getElasticsearchQueryOrThrow(indicator.params.filter),
getElasticsearchQueryOrThrow(indicator.params.filter, dataView),
],
},
},
Expand Down
Loading