[ML] AIOps: Fix Data View runtime fields support in Change point detection UI (#168249)

## Summary

Fixes [#162212](https://github.com/elastic/kibana/issues/168212)

If a Data View runtime field is used as a metric or split field, appends
a `runtime_mappings` to the search request.

### Checklist

- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Dima Arnautov 2023-10-10 16:55:03 +02:00 committed by GitHub
parent e3d9f3d62e
commit 9f06e30a4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 81 additions and 44 deletions

View file

@ -9,12 +9,17 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { type QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { i18n } from '@kbn/i18n';
import { isDefined } from '@kbn/ml-is-defined';
import type {
MappingRuntimeFields,
SearchRequest,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { useReload } from '../../hooks/use_reload';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import {
ChangePointAnnotation,
ChangePointDetectionRequestParams,
FieldConfig,
useChangePointDetectionControlsContext,
} from './change_point_detection_context';
import { useDataSource } from '../../hooks/use_data_source';
import { useCancellableSearch } from '../../hooks/use_cancellable_search';
@ -37,8 +42,9 @@ interface RequestOptions {
function getChangePointDetectionRequestBody(
{ index, fn, metricField, splitField, timeInterval, timeField, afterKey }: RequestOptions,
query: QueryDslQueryContainer
) {
query: QueryDslQueryContainer,
runtimeMappings: MappingRuntimeFields
): SearchRequest {
const timeSeriesAgg = {
over_time: {
date_histogram: {
@ -98,15 +104,14 @@ function getChangePointDetectionRequestBody(
: timeSeriesAgg;
return {
params: {
index,
size: 0,
body: {
...(query ? { query } : {}),
aggregations,
},
index,
size: 0,
body: {
...(query ? { query } : {}),
...(runtimeMappings ? { runtime_mappings: runtimeMappings } : {}),
aggregations,
},
};
} as SearchRequest;
}
export function useChangePointResults(
@ -120,7 +125,7 @@ export function useChangePointResults(
} = useAiopsAppContext();
const { dataView } = useDataSource();
const { splitFieldsOptions, metricFieldOptions } = useChangePointDetectionControlsContext();
const { refreshTimestamp: refresh } = useReload();
const [results, setResults] = useState<ChangePointAnnotation[]>([]);
@ -152,7 +157,23 @@ export function useChangePointResults(
return;
}
const requestPayload = getChangePointDetectionRequestBody(
const metricFieldDV = metricFieldOptions.find(
(option) => option.name === fieldConfig.metricField
);
const splitFieldDV = splitFieldsOptions.find(
(option) => option.name === fieldConfig.splitField
);
const runtimeMappings = {
...(metricFieldDV?.isRuntimeField
? { [metricFieldDV.name]: metricFieldDV.runtimeField! }
: {}),
...(splitFieldDV?.isRuntimeField
? { [splitFieldDV.name]: splitFieldDV.runtimeField! }
: {}),
} as MappingRuntimeFields;
const requestPayload: SearchRequest = getChangePointDetectionRequestBody(
{
index: dataView.getIndexPattern(),
fn: fieldConfig.fn,
@ -162,13 +183,14 @@ export function useChangePointResults(
splitField: fieldConfig.splitField,
afterKey,
},
query
query,
runtimeMappings
);
const result = await runRequest<
typeof requestPayload,
{ params: SearchRequest },
{ rawResponse: ChangePointAggResponse }
>(requestPayload);
>({ params: requestPayload });
if (result === null) {
setProgress(null);
@ -176,7 +198,7 @@ export function useChangePointResults(
}
const isFetchCompleted = !(
result.rawResponse.aggregations?.groupings?.after_key?.splitFieldTerm &&
isDefined(result.rawResponse.aggregations?.groupings?.after_key?.splitFieldTerm) &&
pageNumber < totalAggPages
);
@ -227,11 +249,11 @@ export function useChangePointResults(
if (
!isFetchCompleted &&
result.rawResponse.aggregations?.groupings?.after_key?.splitFieldTerm
isDefined(result.rawResponse.aggregations?.groupings?.after_key?.splitFieldTerm)
) {
await fetchResults(
pageNumber + 1,
result.rawResponse.aggregations.groupings.after_key.splitFieldTerm
result.rawResponse.aggregations.groupings.after_key!.splitFieldTerm
);
}
} catch (e) {
@ -243,17 +265,19 @@ export function useChangePointResults(
}
},
[
runRequest,
requestParams.interval,
requestParams.changePointType,
isSingleMetric,
totalAggPages,
dataView,
fieldConfig.fn,
fieldConfig.metricField,
fieldConfig.splitField,
requestParams.interval,
requestParams.changePointType,
query,
dataView,
totalAggPages,
metricFieldOptions,
splitFieldsOptions,
runRequest,
toasts,
isSingleMetric,
]
);

View file

@ -12,6 +12,7 @@ import type {
SearchResponseBody,
} from '@elastic/elasticsearch/lib/api/types';
import usePrevious from 'react-use/lib/usePrevious';
import { useChangePointDetectionControlsContext } from './change_point_detection_context';
import { useCancellableSearch } from '../../hooks/use_cancellable_search';
import { useDataSource } from '../../hooks/use_data_source';
@ -25,11 +26,19 @@ export function useSplitFieldCardinality(
query: QueryDslQueryContainer
) {
const prevSplitField = usePrevious(splitField);
const { splitFieldsOptions } = useChangePointDetectionControlsContext();
const [cardinality, setCardinality] = useState<number | null>(null);
const { dataView } = useDataSource();
const requestPayload = useMemo(() => {
const optionDefinition = splitFieldsOptions.find((option) => option.name === splitField);
let runtimeMappings = {};
if (optionDefinition?.isRuntimeField) {
runtimeMappings = {
runtime_mappings: { [optionDefinition.name]: optionDefinition.runtimeField },
};
}
return {
params: {
index: dataView.getIndexPattern(),
@ -43,10 +52,11 @@ export function useSplitFieldCardinality(
},
},
},
...runtimeMappings,
},
},
};
}, [splitField, dataView, query]);
}, [splitField, dataView, query, splitFieldsOptions]);
const { runRequest: getSplitFieldCardinality, cancelRequest } = useCancellableSearch();

View file

@ -12,9 +12,10 @@ import { useTimefilter } from '@kbn/ml-date-picker';
import { css } from '@emotion/react';
import useObservable from 'react-use/lib/useObservable';
import { ReloadContextProvider } from '../hooks/use_reload';
import type {
ChangePointAnnotation,
ChangePointDetectionRequestParams,
import {
type ChangePointAnnotation,
ChangePointDetectionControlsContextProvider,
type ChangePointDetectionRequestParams,
} from '../components/change_point_detection/change_point_detection_context';
import type {
EmbeddableChangePointChartInput,
@ -82,22 +83,24 @@ export const EmbeddableInputTracker: FC<EmbeddableInputTrackerProps> = ({
return (
<ReloadContextProvider reload$={resultObservable$}>
<DataSourceContextProvider dataViewId={input.dataViewId}>
<FilterQueryContextProvider timeRange={input.timeRange}>
<ChartGridEmbeddableWrapper
timeRange={input.timeRange}
fn={input.fn}
metricField={input.metricField}
splitField={input.splitField}
maxSeriesToPlot={input.maxSeriesToPlot}
dataViewId={input.dataViewId}
partitions={input.partitions}
onLoading={onLoading}
onRenderComplete={onRenderComplete}
onError={onError}
onChange={input.onChange}
emptyState={input.emptyState}
/>
</FilterQueryContextProvider>
<ChangePointDetectionControlsContextProvider>
<FilterQueryContextProvider timeRange={input.timeRange}>
<ChartGridEmbeddableWrapper
timeRange={input.timeRange}
fn={input.fn}
metricField={input.metricField}
splitField={input.splitField}
maxSeriesToPlot={input.maxSeriesToPlot}
dataViewId={input.dataViewId}
partitions={input.partitions}
onLoading={onLoading}
onRenderComplete={onRenderComplete}
onError={onError}
onChange={input.onChange}
emptyState={input.emptyState}
/>
</FilterQueryContextProvider>
</ChangePointDetectionControlsContextProvider>
</DataSourceContextProvider>
</ReloadContextProvider>
);