Skip to content

Commit

Permalink
[ML] Fix query bar autocompletion for ML and AIOps embeddables (elast…
Browse files Browse the repository at this point in the history
…ic#164485)

(cherry picked from commit 5d5ac37)
  • Loading branch information
darnautov committed Aug 23, 2023
1 parent ea9ec1f commit be50f81
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,31 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable<

private node?: HTMLElement;

// Need to defer embeddable load in order to resolve data views
deferEmbeddableLoad = true;

constructor(
private readonly deps: EmbeddableChangePointChartDeps,
initialInput: EmbeddableChangePointChartInput,
parent?: IContainer
) {
super(initialInput, { defaultTitle: initialInput.title }, parent);

this.initOutput().finally(() => this.setInitializationFinished());
}

private async initOutput() {
const {
data: { dataViews: dataViewsService },
} = this.deps;

const { dataViewId } = this.getInput();

const dataView = await dataViewsService.get(dataViewId);

this.updateOutput({
indexPatterns: [dataView],
});
}

public reportsEmbeddableLoad() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { type Observable } from 'rxjs';
import React, { FC, useCallback, useEffect, useMemo } from 'react';
import React, { FC, useEffect, useMemo } from 'react';
import { useTimefilter } from '@kbn/ml-date-picker';
import { css } from '@emotion/react';
import useObservable from 'react-use/lib/useObservable';
Expand All @@ -21,11 +21,7 @@ import type {
} from './embeddable_change_point_chart';
import { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
import { FilterQueryContextProvider, useFilerQueryUpdates } from '../hooks/use_filters_query';
import {
DataSourceContextProvider,
type DataSourceContextProviderProps,
useDataSource,
} from '../hooks/use_data_source';
import { DataSourceContextProvider, useDataSource } from '../hooks/use_data_source';
import { useAiopsAppContext } from '../hooks/use_aiops_app_context';
import { useTimeBuckets } from '../hooks/use_time_buckets';
import { createMergedEsQuery } from '../application/utils/search_utils';
Expand Down Expand Up @@ -59,16 +55,9 @@ export const EmbeddableInputTracker: FC<EmbeddableInputTrackerProps> = ({
}) => {
const input = useObservable(input$, initialInput);

const onChange = useCallback<Exclude<DataSourceContextProviderProps['onChange'], undefined>>(
({ dataViews }) => {
onOutputChange({ indexPatterns: dataViews });
},
[onOutputChange]
);

return (
<ReloadContextProvider reload$={reload$}>
<DataSourceContextProvider dataViewId={input.dataViewId} onChange={onChange}>
<DataSourceContextProvider dataViewId={input.dataViewId}>
<FilterQueryContextProvider timeRange={input.timeRange}>
<ChartGridEmbeddableWrapper
timeRange={input.timeRange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { Subject } from 'rxjs';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { Embeddable, IContainer } from '@kbn/embeddable-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { IContainer } from '@kbn/embeddable-plugin/public';
import { EmbeddableAnomalyChartsContainer } from './embeddable_anomaly_charts_container_lazy';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
import type { MlDependencies } from '../../application/app';
Expand All @@ -23,6 +22,8 @@ import {
AnomalyChartsServices,
} from '..';
import { EmbeddableLoading } from '../common/components/embeddable_loading_fallback';
import { AnomalyDetectionEmbeddable } from '../common/anomaly_detection_embeddable';

export const getDefaultExplorerChartsPanelTitle = (jobIds: JobId[]) =>
i18n.translate('xpack.ml.anomalyChartsEmbeddable.title', {
defaultMessage: 'ML anomaly charts for {jobIds}',
Expand All @@ -31,7 +32,7 @@ export const getDefaultExplorerChartsPanelTitle = (jobIds: JobId[]) =>

export type IAnomalyChartsEmbeddable = typeof AnomalyChartsEmbeddable;

export class AnomalyChartsEmbeddable extends Embeddable<
export class AnomalyChartsEmbeddable extends AnomalyDetectionEmbeddable<
AnomalyChartsEmbeddableInput,
AnomalyChartsEmbeddableOutput
> {
Expand All @@ -44,52 +45,7 @@ export class AnomalyChartsEmbeddable extends Embeddable<
public services: [CoreStart, MlDependencies, AnomalyChartsServices],
parent?: IContainer
) {
super(
initialInput,
{
defaultTitle: initialInput.title,
defaultDescription: initialInput.description,
},
parent
);
this.initializeOutput(initialInput);
}

private async initializeOutput(initialInput: AnomalyChartsEmbeddableInput) {
const { anomalyExplorerService } = this.services[2];
const { jobIds } = initialInput;

try {
const jobs = await anomalyExplorerService.getCombinedJobs(jobIds);
const dataViewsService = this.services[1].data.dataViews;

// First get list of unique indices from the selected jobs
const indices = new Set(jobs.map((j) => j.datafeed_config.indices).flat());
// Then find the data view assuming the data view title matches the index name
const indexPatterns: Record<string, DataView> = {};
for (const indexName of indices) {
const response = await dataViewsService.find(`"${indexName}"`);

const indexPattern = response.find(
(obj) => obj.title.toLowerCase() === indexName.toLowerCase()
);
if (indexPattern !== undefined) {
indexPatterns[indexPattern.id!] = indexPattern;
}
}

this.updateOutput({
...this.getOutput(),
indexPatterns: Object.values(indexPatterns),
});
} catch (e) {
// Unable to find and load data view but we can ignore the error
// as we only load it to support the filter & query bar
// the visualizations should still work correctly

// eslint-disable-next-line no-console
console.error(`Unable to load data views for ${jobIds}`, e);
}
super(initialInput, services[2].anomalyDetectorService, services[1].data.dataViews, parent);
}

public onLoading() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { Subject } from 'rxjs';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { Embeddable, IContainer } from '@kbn/embeddable-plugin/public';
import { IContainer } from '@kbn/embeddable-plugin/public';
import { EmbeddableSwimLaneContainer } from './embeddable_swim_lane_container_lazy';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
import type { MlDependencies } from '../../application/app';
Expand All @@ -23,6 +23,7 @@ import {
AnomalySwimlaneServices,
} from '..';
import { EmbeddableLoading } from '../common/components/embeddable_loading_fallback';
import { AnomalyDetectionEmbeddable } from '../common/anomaly_detection_embeddable';

export const getDefaultSwimlanePanelTitle = (jobIds: JobId[]) =>
i18n.translate('xpack.ml.swimlaneEmbeddable.title', {
Expand All @@ -32,7 +33,7 @@ export const getDefaultSwimlanePanelTitle = (jobIds: JobId[]) =>

export type IAnomalySwimlaneEmbeddable = typeof AnomalySwimlaneEmbeddable;

export class AnomalySwimlaneEmbeddable extends Embeddable<
export class AnomalySwimlaneEmbeddable extends AnomalyDetectionEmbeddable<
AnomalySwimlaneEmbeddableInput,
AnomalySwimlaneEmbeddableOutput
> {
Expand All @@ -45,14 +46,7 @@ export class AnomalySwimlaneEmbeddable extends Embeddable<
public services: [CoreStart, MlDependencies, AnomalySwimlaneServices],
parent?: IContainer
) {
super(
initialInput,
{
defaultTitle: initialInput.title,
defaultDescription: initialInput.description,
},
parent
);
super(initialInput, services[2].anomalyDetectorService, services[1].data.dataViews, parent);
}

public reportsEmbeddableLoad() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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 {
Embeddable,
type EmbeddableInput,
type EmbeddableOutput,
type IContainer,
} from '@kbn/embeddable-plugin/public';
import { type DataView } from '@kbn/data-views-plugin/common';
import { type DataViewsContract } from '@kbn/data-views-plugin/public';
import { firstValueFrom } from 'rxjs';
import { type AnomalyDetectorService } from '../../application/services/anomaly_detector_service';

export type CommonInput = { jobIds: string[] } & EmbeddableInput;

export type CommonOutput = { indexPatterns?: DataView[] } & EmbeddableOutput;

export abstract class AnomalyDetectionEmbeddable<
Input extends CommonInput,
Output extends CommonOutput
> extends Embeddable<Input, Output> {
// Need to defer embeddable load in order to resolve data views
deferEmbeddableLoad = true;

protected constructor(
initialInput: Input,
private anomalyDetectorService: AnomalyDetectorService,
private dataViewsService: DataViewsContract,
parent?: IContainer
) {
super(
initialInput,
{
defaultTitle: initialInput.title,
defaultDescription: initialInput.description,
} as Output,
parent
);

this.initializeOutput(initialInput).finally(() => {
this.setInitializationFinished();
});
}

protected async initializeOutput(initialInput: CommonInput) {
const { jobIds } = initialInput;

try {
const jobs = await firstValueFrom(this.anomalyDetectorService.getJobs$(jobIds));

// First get list of unique indices from the selected jobs
const indices = new Set(jobs.map((j) => j.datafeed_config!.indices).flat());
// Then find the data view assuming the data view title matches the index name
const indexPatterns: Record<string, DataView> = {};
for (const indexName of indices) {
const response = await this.dataViewsService.find(`"${indexName}"`);
const indexPattern = response.find((obj) =>
obj.getIndexPattern().toLowerCase().includes(indexName.toLowerCase())
);

if (indexPattern !== undefined) {
indexPatterns[indexPattern.id!] = indexPattern;
}
}

this.updateOutput({
...this.getOutput(),
indexPatterns: Object.values(indexPatterns),
});
} catch (e) {
// Unable to find and load data view but we can ignore the error
// as we only load it to support the filter & query bar
// the visualizations should still work correctly

// eslint-disable-next-line no-console
console.error(`Unable to load data views for ${jobIds}`, e);
}
}
}
1 change: 1 addition & 0 deletions x-pack/plugins/ml/public/embeddables/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface AnomalySwimlaneEmbeddableCustomOutput {
perPage?: number;
fromPage?: number;
interval?: number;
indexPatterns?: DataView[];
}

export type AnomalySwimlaneEmbeddableOutput = EmbeddableOutput &
Expand Down

0 comments on commit be50f81

Please sign in to comment.