mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
feat(slo): Show SLI preview chart for custom kql (#159713)
This commit is contained in:
parent
fa98aa4f8c
commit
f9d16e160b
13 changed files with 335 additions and 29 deletions
|
@ -6,24 +6,22 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import {
|
||||
budgetingMethodSchema,
|
||||
dateType,
|
||||
historicalSummarySchema,
|
||||
indicatorSchema,
|
||||
indicatorTypesArraySchema,
|
||||
kqlCustomIndicatorSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
objectiveSchema,
|
||||
optionalSettingsSchema,
|
||||
previewDataSchema,
|
||||
settingsSchema,
|
||||
sloIdSchema,
|
||||
summarySchema,
|
||||
tagsSchema,
|
||||
timeWindowSchema,
|
||||
metricCustomIndicatorSchema,
|
||||
kqlCustomIndicatorSchema,
|
||||
apmTransactionErrorRateIndicatorSchema,
|
||||
apmTransactionDurationIndicatorSchema,
|
||||
} from '../schema';
|
||||
|
||||
const createSLOParamsSchema = t.type({
|
||||
|
@ -44,6 +42,14 @@ const createSLOResponseSchema = t.type({
|
|||
id: sloIdSchema,
|
||||
});
|
||||
|
||||
const getPreviewDataParamsSchema = t.type({
|
||||
body: t.type({
|
||||
indicator: indicatorSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
const getPreviewDataResponseSchema = t.array(previewDataSchema);
|
||||
|
||||
const deleteSLOParamsSchema = t.type({
|
||||
path: t.type({
|
||||
id: sloIdSchema,
|
||||
|
@ -156,20 +162,22 @@ type FetchHistoricalSummaryParams = t.TypeOf<typeof fetchHistoricalSummaryParams
|
|||
type FetchHistoricalSummaryResponse = t.OutputOf<typeof fetchHistoricalSummaryResponseSchema>;
|
||||
type HistoricalSummaryResponse = t.OutputOf<typeof historicalSummarySchema>;
|
||||
|
||||
type GetPreviewDataParams = t.TypeOf<typeof getPreviewDataParamsSchema.props.body>;
|
||||
type GetPreviewDataResponse = t.TypeOf<typeof getPreviewDataResponseSchema>;
|
||||
|
||||
type BudgetingMethod = t.TypeOf<typeof budgetingMethodSchema>;
|
||||
|
||||
type MetricCustomIndicatorSchema = t.TypeOf<typeof metricCustomIndicatorSchema>;
|
||||
type KQLCustomIndicatorSchema = t.TypeOf<typeof kqlCustomIndicatorSchema>;
|
||||
type APMTransactionErrorRateIndicatorSchema = t.TypeOf<
|
||||
typeof apmTransactionErrorRateIndicatorSchema
|
||||
>;
|
||||
type APMTransactionDurationIndicatorSchema = t.TypeOf<typeof apmTransactionDurationIndicatorSchema>;
|
||||
type Indicator = t.OutputOf<typeof indicatorSchema>;
|
||||
type MetricCustomIndicator = t.OutputOf<typeof metricCustomIndicatorSchema>;
|
||||
type KQLCustomIndicator = t.OutputOf<typeof kqlCustomIndicatorSchema>;
|
||||
|
||||
export {
|
||||
createSLOParamsSchema,
|
||||
deleteSLOParamsSchema,
|
||||
findSLOParamsSchema,
|
||||
findSLOResponseSchema,
|
||||
getPreviewDataParamsSchema,
|
||||
getPreviewDataResponseSchema,
|
||||
getSLODiagnosisParamsSchema,
|
||||
getSLOParamsSchema,
|
||||
getSLOResponseSchema,
|
||||
|
@ -188,6 +196,8 @@ export type {
|
|||
CreateSLOResponse,
|
||||
FindSLOParams,
|
||||
FindSLOResponse,
|
||||
GetPreviewDataParams,
|
||||
GetPreviewDataResponse,
|
||||
GetSLOResponse,
|
||||
FetchHistoricalSummaryParams,
|
||||
FetchHistoricalSummaryResponse,
|
||||
|
@ -198,8 +208,7 @@ export type {
|
|||
UpdateSLOInput,
|
||||
UpdateSLOParams,
|
||||
UpdateSLOResponse,
|
||||
MetricCustomIndicatorSchema,
|
||||
KQLCustomIndicatorSchema,
|
||||
APMTransactionDurationIndicatorSchema,
|
||||
APMTransactionErrorRateIndicatorSchema,
|
||||
Indicator,
|
||||
MetricCustomIndicator,
|
||||
KQLCustomIndicator,
|
||||
};
|
||||
|
|
|
@ -52,6 +52,11 @@ const historicalSummarySchema = t.intersection([
|
|||
summarySchema,
|
||||
]);
|
||||
|
||||
const previewDataSchema = t.type({
|
||||
date: dateType,
|
||||
sliValue: t.number,
|
||||
});
|
||||
|
||||
const dateRangeSchema = t.type({ from: dateType, to: dateType });
|
||||
|
||||
export type { SummarySchema };
|
||||
|
@ -63,6 +68,7 @@ export {
|
|||
dateType,
|
||||
errorBudgetSchema,
|
||||
historicalSummarySchema,
|
||||
previewDataSchema,
|
||||
statusSchema,
|
||||
summarySchema,
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { KQLCustomIndicatorSchema, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { KQLCustomIndicator, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
|
||||
export const buildApmAvailabilityIndicator = (
|
||||
params: Partial<SLOWithSummaryResponse['indicator']['params']> = {}
|
||||
|
@ -53,5 +53,5 @@ export const buildCustomKqlIndicator = (
|
|||
timestampField: '@timestamp',
|
||||
...params,
|
||||
},
|
||||
} as KQLCustomIndicatorSchema;
|
||||
} as KQLCustomIndicator;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Indicator } from '@kbn/slo-schema';
|
||||
|
||||
interface SloKeyFilter {
|
||||
name: string;
|
||||
page: number;
|
||||
|
@ -31,6 +33,7 @@ export const sloKeys = {
|
|||
historicalSummaries: () => [...sloKeys.all, 'historicalSummary'] as const,
|
||||
historicalSummary: (sloIds: string[]) => [...sloKeys.historicalSummaries(), sloIds] as const,
|
||||
globalDiagnosis: () => [...sloKeys.all, 'globalDiagnosis'] as const,
|
||||
preview: (indicator?: Indicator) => [...sloKeys.all, 'preview', indicator] as const,
|
||||
};
|
||||
|
||||
export const compositeSloKeys = {
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { GetPreviewDataResponse, Indicator } from '@kbn/slo-schema';
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { sloKeys } from './query_key_factory';
|
||||
|
||||
export interface UseGetPreviewData {
|
||||
data: GetPreviewDataResponse | undefined;
|
||||
isInitialLoading: boolean;
|
||||
isRefetching: boolean;
|
||||
isLoading: boolean;
|
||||
isSuccess: boolean;
|
||||
isError: boolean;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<GetPreviewDataResponse | undefined, unknown>>;
|
||||
}
|
||||
|
||||
export function useGetPreviewData(indicator?: Indicator): UseGetPreviewData {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery(
|
||||
{
|
||||
queryKey: sloKeys.preview(indicator),
|
||||
queryFn: async ({ signal }) => {
|
||||
const response = await http.post<GetPreviewDataResponse>(
|
||||
'/internal/observability/slos/_preview',
|
||||
{
|
||||
body: JSON.stringify({ indicator }),
|
||||
signal,
|
||||
}
|
||||
);
|
||||
|
||||
return response;
|
||||
},
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: Boolean(indicator),
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
isInitialLoading,
|
||||
isSuccess,
|
||||
isError,
|
||||
refetch,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 { AreaSeries, Axis, Chart, Position, ScaleType, Settings } from '@elastic/charts';
|
||||
import { EuiFlexItem, EuiIcon, EuiLoadingChart, EuiPanel } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CreateSLOInput } from '@kbn/slo-schema';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { useDebouncedGetPreviewData } from '../../hooks/use_preview';
|
||||
export function DataPreviewChart() {
|
||||
const { watch, getFieldState } = useFormContext<CreateSLOInput>();
|
||||
const { charts, uiSettings } = useKibana().services;
|
||||
|
||||
const { data: previewData, isLoading: isPreviewLoading } = useDebouncedGetPreviewData(
|
||||
watch('indicator')
|
||||
);
|
||||
|
||||
const theme = charts.theme.useChartsTheme();
|
||||
const baseTheme = charts.theme.useChartsBaseTheme();
|
||||
const dateFormat = uiSettings.get('dateFormat');
|
||||
const percentFormat = uiSettings.get('format:percent:defaultPattern');
|
||||
|
||||
if (getFieldState('indicator').invalid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
{isPreviewLoading && <EuiLoadingChart size="m" mono />}
|
||||
{!isPreviewLoading && !!previewData && (
|
||||
<EuiPanel hasBorder={true} hasShadow={false}>
|
||||
<Chart size={{ height: 160, width: '100%' }}>
|
||||
<Settings
|
||||
baseTheme={baseTheme}
|
||||
showLegend={false}
|
||||
theme={[
|
||||
{
|
||||
...theme,
|
||||
lineSeriesStyle: {
|
||||
point: { visible: false },
|
||||
},
|
||||
},
|
||||
]}
|
||||
tooltip="vertical"
|
||||
noResults={
|
||||
<EuiIcon type="visualizeApp" size="l" color="subdued" title="no results" />
|
||||
}
|
||||
/>
|
||||
|
||||
<Axis
|
||||
id="y-axis"
|
||||
title={i18n.translate('xpack.observability.slo.sloEdit.dataPreviewChart.yTitle', {
|
||||
defaultMessage: 'SLI',
|
||||
})}
|
||||
ticks={5}
|
||||
position={Position.Left}
|
||||
tickFormat={(d) => numeral(d).format(percentFormat)}
|
||||
/>
|
||||
|
||||
<Axis
|
||||
id="time"
|
||||
title={i18n.translate('xpack.observability.slo.sloEdit.dataPreviewChart.xTitle', {
|
||||
defaultMessage: 'Last hour',
|
||||
})}
|
||||
tickFormat={(d) => moment(d).format(dateFormat)}
|
||||
position={Position.Bottom}
|
||||
timeAxisLayerCount={2}
|
||||
gridLine={{ visible: true }}
|
||||
style={{
|
||||
tickLine: { size: 0.0001, padding: 4, visible: true },
|
||||
tickLabel: {
|
||||
alignment: {
|
||||
horizontal: Position.Left,
|
||||
vertical: Position.Bottom,
|
||||
},
|
||||
padding: 0,
|
||||
offset: { x: 0, y: 0 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<AreaSeries
|
||||
id="SLI"
|
||||
xScaleType={ScaleType.Time}
|
||||
yScaleType={ScaleType.Linear}
|
||||
xAccessor="date"
|
||||
yAccessors={['value']}
|
||||
data={previewData.map((datum) => ({
|
||||
date: new Date(datum.date).getTime(),
|
||||
value: datum.sliValue >= 0 ? datum.sliValue : null,
|
||||
}))}
|
||||
/>
|
||||
</Chart>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
|
@ -21,6 +21,7 @@ import {
|
|||
Field,
|
||||
useFetchIndexPatternFields,
|
||||
} from '../../../../hooks/slo/use_fetch_index_pattern_fields';
|
||||
import { DataPreviewChart } from '../common/data_preview_chart';
|
||||
import { QueryBuilder } from '../common/query_builder';
|
||||
import { IndexSelection } from '../custom_common/index_selection';
|
||||
|
||||
|
@ -31,7 +32,6 @@ interface Option {
|
|||
|
||||
export function CustomKqlIndicatorTypeForm() {
|
||||
const { control, watch, getFieldState } = useFormContext<CreateSLOInput>();
|
||||
|
||||
const { isLoading, data: indexFields } = useFetchIndexPatternFields(
|
||||
watch('indicator.params.index')
|
||||
);
|
||||
|
@ -86,12 +86,7 @@ export function CustomKqlIndicatorTypeForm() {
|
|||
!!watch('indicator.params.index') &&
|
||||
!!field.value &&
|
||||
timestampFields.some((timestampField) => timestampField.name === field.value)
|
||||
? [
|
||||
{
|
||||
value: field.value,
|
||||
label: field.value,
|
||||
},
|
||||
]
|
||||
? [{ value: field.value, label: field.value }]
|
||||
: []
|
||||
}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
|
@ -187,6 +182,8 @@ export function CustomKqlIndicatorTypeForm() {
|
|||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<DataPreviewChart />
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { Indicator } from '@kbn/slo-schema';
|
||||
import { debounce } from 'lodash';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useGetPreviewData } from '../../../hooks/slo/use_get_preview_data';
|
||||
|
||||
export function useDebouncedGetPreviewData(indicator: Indicator) {
|
||||
const serializedIndicator = JSON.stringify(indicator);
|
||||
const [indicatorState, setIndicatorState] = useState<string>(serializedIndicator);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const store = useCallback(
|
||||
debounce((value: string) => setIndicatorState(value), 800),
|
||||
[]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (indicatorState !== serializedIndicator) {
|
||||
store(serializedIndicator);
|
||||
}
|
||||
}, [indicatorState, serializedIndicator, store]);
|
||||
|
||||
return useGetPreviewData(JSON.parse(indicatorState));
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CreateSLOInput, MetricCustomIndicatorSchema } from '@kbn/slo-schema';
|
||||
import { CreateSLOInput, MetricCustomIndicator } from '@kbn/slo-schema';
|
||||
import { FormState, UseFormGetFieldState, UseFormGetValues, UseFormWatch } from 'react-hook-form';
|
||||
import { isObject } from 'lodash';
|
||||
|
||||
|
@ -22,9 +22,7 @@ export function useSectionFormValidation({ getFieldState, getValues, formState,
|
|||
switch (watch('indicator.type')) {
|
||||
case 'sli.metric.custom':
|
||||
const isGoodParamsValid = () => {
|
||||
const data = getValues(
|
||||
'indicator.params.good'
|
||||
) as MetricCustomIndicatorSchema['params']['good'];
|
||||
const data = getValues('indicator.params.good') as MetricCustomIndicator['params']['good'];
|
||||
const isEquationValid = !getFieldState('indicator.params.good.equation').invalid;
|
||||
const areMetricsValid =
|
||||
isObject(data) && (data.metrics ?? []).every((metric) => Boolean(metric.field));
|
||||
|
@ -34,7 +32,7 @@ export function useSectionFormValidation({ getFieldState, getValues, formState,
|
|||
const isTotalParamsValid = () => {
|
||||
const data = getValues(
|
||||
'indicator.params.total'
|
||||
) as MetricCustomIndicatorSchema['params']['total'];
|
||||
) as MetricCustomIndicator['params']['total'];
|
||||
const isEquationValid = !getFieldState('indicator.params.total.equation').invalid;
|
||||
const areMetricsValid =
|
||||
isObject(data) && (data.metrics ?? []).every((metric) => Boolean(metric.field));
|
||||
|
|
|
@ -66,6 +66,12 @@ const mockKibana = () => {
|
|||
application: {
|
||||
navigateToUrl: mockNavigate,
|
||||
},
|
||||
charts: {
|
||||
theme: {
|
||||
useChartsTheme: () => {},
|
||||
useChartsBaseTheme: () => {},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
dataViews: {
|
||||
find: jest.fn().mockReturnValue([]),
|
||||
|
|
|
@ -20,6 +20,7 @@ export class SLOIdConflict extends ObservabilityError {}
|
|||
export class CompositeSLONotFound extends ObservabilityError {}
|
||||
export class CompositeSLOIdConflict extends ObservabilityError {}
|
||||
|
||||
export class InvalidQueryError extends ObservabilityError {}
|
||||
export class InternalQueryError extends ObservabilityError {}
|
||||
export class NotSupportedError extends ObservabilityError {}
|
||||
export class IllegalArgumentError extends ObservabilityError {}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
deleteSLOParamsSchema,
|
||||
fetchHistoricalSummaryParamsSchema,
|
||||
findSLOParamsSchema,
|
||||
getPreviewDataParamsSchema,
|
||||
getSLODiagnosisParamsSchema,
|
||||
getSLOParamsSchema,
|
||||
manageSLOParamsSchema,
|
||||
|
@ -41,6 +42,7 @@ import type { IndicatorTypes } from '../../domain/models';
|
|||
import type { ObservabilityRequestHandlerContext } from '../../types';
|
||||
import { ManageSLO } from '../../services/slo/manage_slo';
|
||||
import { getGlobalDiagnosis, getSloDiagnosis } from '../../services/slo/get_diagnosis';
|
||||
import { GetPreviewData } from '../../services/slo/get_preview_data';
|
||||
|
||||
const transformGenerators: Record<IndicatorTypes, TransformGenerator> = {
|
||||
'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator(),
|
||||
|
@ -303,6 +305,25 @@ const getSloDiagnosisRoute = createObservabilityServerRoute({
|
|||
},
|
||||
});
|
||||
|
||||
const getPreviewData = createObservabilityServerRoute({
|
||||
endpoint: 'POST /internal/observability/slos/_preview',
|
||||
options: {
|
||||
tags: ['access:slo_read'],
|
||||
},
|
||||
params: getPreviewDataParamsSchema,
|
||||
handler: async ({ context, params }) => {
|
||||
const hasCorrectLicense = await isLicenseAtLeastPlatinum(context);
|
||||
|
||||
if (!hasCorrectLicense) {
|
||||
throw badRequest('Platinum license or higher is needed to make use of this feature.');
|
||||
}
|
||||
|
||||
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const service = new GetPreviewData(esClient);
|
||||
return await service.execute(params.body);
|
||||
},
|
||||
});
|
||||
|
||||
export const sloRouteRepository = {
|
||||
...createSLORoute,
|
||||
...deleteSLORoute,
|
||||
|
@ -314,4 +335,5 @@ export const sloRouteRepository = {
|
|||
...updateSLORoute,
|
||||
...getDiagnosisRoute,
|
||||
...getSloDiagnosisRoute,
|
||||
...getPreviewData,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
||||
import { GetPreviewDataParams, GetPreviewDataResponse } from '@kbn/slo-schema';
|
||||
import { computeSLI } from '../../domain/services';
|
||||
import { InvalidQueryError } from '../../errors';
|
||||
|
||||
export class GetPreviewData {
|
||||
constructor(private esClient: ElasticsearchClient) {}
|
||||
|
||||
public async execute(params: GetPreviewDataParams): Promise<GetPreviewDataResponse> {
|
||||
switch (params.indicator.type) {
|
||||
case 'sli.kql.custom':
|
||||
const filterQuery = getElastichsearchQueryOrThrow(params.indicator.params.filter);
|
||||
const goodQuery = getElastichsearchQueryOrThrow(params.indicator.params.good);
|
||||
const totalQuery = getElastichsearchQueryOrThrow(params.indicator.params.total);
|
||||
const timestampField = params.indicator.params.timestampField;
|
||||
|
||||
try {
|
||||
const result = await this.esClient.search({
|
||||
index: params.indicator.params.index,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [{ range: { [timestampField]: { gte: 'now-60m' } } }, filterQuery],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
perMinute: {
|
||||
date_histogram: {
|
||||
field: timestampField,
|
||||
fixed_interval: '1m',
|
||||
},
|
||||
aggs: {
|
||||
good: { filter: goodQuery },
|
||||
total: { filter: totalQuery },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-ignore buckets is not improperly typed
|
||||
return result.aggregations?.perMinute.buckets.map((bucket) => ({
|
||||
date: bucket.key_as_string,
|
||||
sliValue: computeSLI(bucket.good.doc_count, bucket.total.doc_count),
|
||||
}));
|
||||
} catch (err) {
|
||||
throw new InvalidQueryError(`Invalid ES query`);
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getElastichsearchQueryOrThrow(kuery: string) {
|
||||
try {
|
||||
return toElasticsearchQuery(fromKueryExpression(kuery));
|
||||
} catch (err) {
|
||||
throw new InvalidQueryError(`Invalid kuery: ${kuery}`);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue