[ML] Fix Anomaly Detection wizard full time range chart blank with saved search containing runtime fields (#95700)

* [ML] Fix AD wizard full time range chart broken with saved search

* [ML] Update runtimeMappingsSchema to be its own thing for better reuse

* [ML] Remove undefined check
This commit is contained in:
Quynh Nguyen 2021-03-30 14:35:23 -05:00 committed by GitHub
parent d6370f4e51
commit a1bc9a57bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 52 additions and 24 deletions

View file

@ -6,10 +6,7 @@
*/
import { isPopulatedObject } from './object_utils';
import {
RUNTIME_FIELD_TYPES,
RuntimeType,
} from '../../../../../src/plugins/data/common/index_patterns';
import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common/index_patterns';
import type { RuntimeField, RuntimeMappings } from '../types/fields';
export function isRuntimeField(arg: unknown): arg is RuntimeField {
@ -24,7 +21,7 @@ export function isRuntimeField(arg: unknown): arg is RuntimeField {
Object.keys(arg.script).length === 1 &&
arg.script.hasOwnProperty('source') &&
typeof arg.script.source === 'string')))) &&
RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType)
RUNTIME_FIELD_TYPES.includes(arg.type)
);
}

View file

@ -30,6 +30,7 @@ export function chartLoaderProvider(mlResultsService: MlResultsService) {
job.data_counts.earliest_record_timestamp,
job.data_counts.latest_record_timestamp,
intervalMs,
job.datafeed_config.runtime_mappings,
// @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options
job.datafeed_config.indices_options
);

View file

@ -128,6 +128,7 @@ export class ChartLoader {
start: number,
end: number,
intervalMs: number,
runtimeMappings?: RuntimeMappings,
indicesOptions?: IndicesOptions
): Promise<LineChartPoint[]> {
if (this._timeFieldName !== '') {
@ -138,6 +139,7 @@ export class ChartLoader {
start,
end,
intervalMs * 3,
runtimeMappings,
indicesOptions
);
if (resp.error !== undefined) {

View file

@ -55,6 +55,7 @@ export const CategorizationDetectorsSummary: FC = () => {
jobCreator.start,
jobCreator.end,
chartInterval.getInterval().asMilliseconds(),
jobCreator.runtimeMappings ?? undefined,
// @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options
jobCreator.datafeedConfig.indices_options
);

View file

@ -48,6 +48,7 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
jobCreator.start,
jobCreator.end,
chartInterval.getInterval().asMilliseconds(),
jobCreator.runtimeMappings ?? undefined,
// @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options
jobCreator.datafeedConfig.indices_options
);

View file

@ -44,6 +44,8 @@ import { JobId } from '../../../../../common/types/anomaly_detection_jobs';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning';
import { isPopulatedObject } from '../../../../../common/util/object_utils';
import { RuntimeMappings } from '../../../../../common/types/fields';
export interface ModuleJobUI extends ModuleJob {
datafeedResult?: DatafeedResponse;
@ -133,10 +135,12 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
timeRange: TimeRange
): Promise<TimeRange> => {
if (useFullIndexData) {
const runtimeMappings = indexPattern.getComputedFields().runtimeFields as RuntimeMappings;
const { start, end } = await ml.getTimeFieldRange({
index: indexPattern.title,
timeFieldName: indexPattern.timeFieldName,
query: combinedQuery,
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
});
return {
start: start.epoch,

View file

@ -10,6 +10,7 @@ import { MlApiServices } from '../ml_api_service';
import type { AnomalyRecordDoc } from '../../../../common/types/anomalies';
import { InfluencersFilterQuery } from '../../../../common/types/es_client';
import { EntityField } from '../../../../common/util/anomaly_utils';
import { RuntimeMappings } from '../../../../common/types/fields';
type RecordForInfluencer = AnomalyRecordDoc;
export function resultsServiceProvider(
@ -64,6 +65,7 @@ export function resultsServiceProvider(
earliestMs: number,
latestMs: number,
intervalMs: number,
runtimeMappings?: RuntimeMappings,
indicesOptions?: IndicesOptions
): Promise<any>;
getEventDistributionData(

View file

@ -14,6 +14,7 @@ import {
SWIM_LANE_DEFAULT_PAGE_SIZE,
} from '../../explorer/explorer_constants';
import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils';
import { isPopulatedObject } from '../../../../common/util/object_utils';
/**
* Service for carrying out Elasticsearch queries to obtain data for the Ml Results dashboards.
@ -1059,6 +1060,7 @@ export function resultsServiceProvider(mlApiServices) {
earliestMs,
latestMs,
intervalMs,
runtimeMappings,
indicesOptions
) {
return new Promise((resolve, reject) => {
@ -1109,6 +1111,12 @@ export function resultsServiceProvider(mlApiServices) {
},
},
},
// Runtime mappings only needed to support when query includes a runtime field
// even though the default timeField can be a search time runtime field
// because currently Kibana doesn't support that
...(isPopulatedObject(runtimeMappings) && query
? { runtime_mappings: runtimeMappings }
: {}),
},
...(indicesOptions ?? {}),
})

View file

@ -6,27 +6,13 @@
*/
import { schema } from '@kbn/config-schema';
import { isRuntimeField } from '../../../common/util/runtime_field_utils';
import { runtimeMappingsSchema } from './runtime_mappings_schema';
export const indexPatternTitleSchema = schema.object({
/** Title of the index pattern for which to return stats. */
indexPatternTitle: schema.string(),
});
const runtimeMappingsSchema = schema.maybe(
schema.object(
{},
{
unknowns: 'allow',
validate: (v: object) => {
if (Object.values(v).some((o) => !isRuntimeField(o))) {
return 'Invalid runtime field';
}
},
}
)
);
export const dataVisualizerFieldHistogramsSchema = schema.object({
/** Query to match documents in the index. */
query: schema.any(),

View file

@ -7,6 +7,7 @@
import { schema } from '@kbn/config-schema';
import { indicesOptionsSchema } from './datafeeds_schema';
import { runtimeMappingsSchema } from './runtime_mappings_schema';
export const getCardinalityOfFieldsSchema = schema.object({
/** Index or indexes for which to return the time range. */
@ -31,6 +32,6 @@ export const getTimeFieldRangeSchema = schema.object({
/** Query to match documents in the index(es). */
query: schema.maybe(schema.any()),
/** Additional search options. */
runtimeMappings: schema.maybe(schema.any()),
runtimeMappings: runtimeMappingsSchema,
indicesOptions: indicesOptionsSchema,
});

View file

@ -8,6 +8,7 @@
import { schema } from '@kbn/config-schema';
import { anomalyDetectionJobSchema } from './anomaly_detectors_schema';
import { datafeedConfigSchema, indicesOptionsSchema } from './datafeeds_schema';
import { runtimeMappingsSchema } from './runtime_mappings_schema';
export const categorizationFieldExamplesSchema = {
indexPatternTitle: schema.string(),
@ -18,7 +19,7 @@ export const categorizationFieldExamplesSchema = {
start: schema.number(),
end: schema.number(),
analyzer: schema.any(),
runtimeMappings: schema.maybe(schema.any()),
runtimeMappings: runtimeMappingsSchema,
indicesOptions: indicesOptionsSchema,
};
@ -32,7 +33,7 @@ export const chartSchema = {
aggFieldNamePairs: schema.arrayOf(schema.any()),
splitFieldName: schema.maybe(schema.nullable(schema.string())),
splitFieldValue: schema.maybe(schema.nullable(schema.string())),
runtimeMappings: schema.maybe(schema.any()),
runtimeMappings: runtimeMappingsSchema,
indicesOptions: indicesOptionsSchema,
};

View file

@ -8,6 +8,7 @@
import { schema } from '@kbn/config-schema';
import { analysisConfigSchema, anomalyDetectionJobSchema } from './anomaly_detectors_schema';
import { datafeedConfigSchema, indicesOptionsSchema } from './datafeeds_schema';
import { runtimeMappingsSchema } from './runtime_mappings_schema';
export const estimateBucketSpanSchema = schema.object({
aggTypes: schema.arrayOf(schema.nullable(schema.string())),
@ -18,7 +19,7 @@ export const estimateBucketSpanSchema = schema.object({
query: schema.any(),
splitField: schema.maybe(schema.string()),
timeField: schema.maybe(schema.string()),
runtimeMappings: schema.maybe(schema.any()),
runtimeMappings: runtimeMappingsSchema,
indicesOptions: indicesOptionsSchema,
});

View file

@ -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 { schema } from '@kbn/config-schema';
import { isRuntimeField } from '../../../common/util/runtime_field_utils';
export const runtimeMappingsSchema = schema.maybe(
schema.object(
{},
{
unknowns: 'allow',
validate: (v: object) => {
if (Object.values(v).some((o) => !isRuntimeField(o))) {
return 'Invalid runtime field';
}
},
}
)
);