Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Decouple open-in-anomaly-explorer action #178729

Merged
5 changes: 5 additions & 0 deletions src/plugins/embeddable/public/index.ts
Original file line number Diff line number Diff line change
@@ -112,3 +112,8 @@ export { registerSavedObjectToPanelMethod } from './registry/saved_object_to_pan
export function plugin(initializerContext: PluginInitializerContext) {
return new EmbeddablePublicPlugin(initializerContext);
}

export {
embeddableInputToSubject,
embeddableOutputToSubject,
} from './lib/embeddables/compatibility/embeddable_compatibility_utils';
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import {
map,
Subscription,
} from 'rxjs';
import { IEmbeddable } from '../..';
import { Container } from '../../containers';
import { ViewMode as LegacyViewMode } from '../../types';
import {
@@ -23,10 +24,13 @@ import {
CommonLegacyOutput,
} from './legacy_embeddable_to_api';

export const embeddableInputToSubject = <T extends unknown = unknown>(
export const embeddableInputToSubject = <
T extends unknown = unknown,
LegacyInput extends CommonLegacyInput = CommonLegacyInput
>(
subscription: Subscription,
embeddable: CommonLegacyEmbeddable,
key: keyof CommonLegacyInput,
embeddable: IEmbeddable<LegacyInput>,
key: keyof LegacyInput,
useExplicitInput = false
) => {
const subject = new BehaviorSubject<T | undefined>(embeddable.getExplicitInput()?.[key] as T);
@@ -36,12 +40,10 @@ export const embeddableInputToSubject = <T extends unknown = unknown>(
.getInput$()
.pipe(
distinctUntilChanged((prev, current) => {
const previousValue = (prev.panels[embeddable.id]?.explicitInput as CommonLegacyInput)[
const previousValue = (prev.panels[embeddable.id]?.explicitInput as LegacyInput)[key];
const currentValue = (current.panels[embeddable.id]?.explicitInput as LegacyInput)?.[
key
];
const currentValue = (
current.panels[embeddable.id]?.explicitInput as CommonLegacyInput
)?.[key];
return deepEqual(previousValue, currentValue);
})
)
@@ -58,10 +60,13 @@ export const embeddableInputToSubject = <T extends unknown = unknown>(
return subject;
};

export const embeddableOutputToSubject = <T extends unknown = unknown>(
export const embeddableOutputToSubject = <
T extends unknown = unknown,
LegacyOutput extends CommonLegacyOutput = CommonLegacyOutput
>(
subscription: Subscription,
embeddable: CommonLegacyEmbeddable,
key: keyof CommonLegacyOutput
embeddable: IEmbeddable<CommonLegacyInput, LegacyOutput>,
key: keyof LegacyOutput
) => {
const subject = new BehaviorSubject<T | undefined>(embeddable.getOutput()[key] as T);
subscription.add(
Original file line number Diff line number Diff line change
@@ -9,9 +9,10 @@ import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import type { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { Subject } from 'rxjs';
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import type { IContainer } from '@kbn/embeddable-plugin/public';
import type { MlEntityField } from '@kbn/ml-anomaly-utils';
import { EmbeddableAnomalyChartsContainer } from './embeddable_anomaly_charts_container_lazy';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
import type { MlDependencies } from '../../application/app';
@@ -40,12 +41,25 @@ export class AnomalyChartsEmbeddable extends AnomalyDetectionEmbeddable<
private reload$ = new Subject<void>();
public readonly type: string = ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE;

// API
public entityFields = new BehaviorSubject<MlEntityField[] | undefined>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use embeddableOutputToSubject?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made it for one embeddable and forgot the other 🤦🏻 Updated in d4debca

this.getOutput().entityFields
);

private apiSubscriptions = new Subscription();

constructor(
initialInput: AnomalyChartsEmbeddableInput,
public services: [CoreStart, MlDependencies, AnomalyChartsServices],
parent?: IContainer
) {
super(initialInput, services[2].anomalyDetectorService, services[1].data.dataViews, parent);

this.apiSubscriptions.add(
this.getOutput$().subscribe((output) => {
this.entityFields.next(output.entityFields);
})
);
}

public onLoading() {
@@ -108,6 +122,9 @@ export class AnomalyChartsEmbeddable extends AnomalyDetectionEmbeddable<

public destroy() {
super.destroy();

this.apiSubscriptions.unsubscribe();

if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
Original file line number Diff line number Diff line change
@@ -5,25 +5,26 @@
* 2.0.
*/

import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import type { CoreStart } from '@kbn/core/public';
import type { IContainer } from '@kbn/embeddable-plugin/public';
import { embeddableInputToSubject, embeddableOutputToSubject } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import { Subject } from 'rxjs';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import type { 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';
import { SWIM_LANE_SELECTION_TRIGGER } from '../../ui_actions';
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import type {
AnomalySwimlaneEmbeddableInput,
AnomalySwimlaneEmbeddableOutput,
AnomalySwimlaneServices,
} from '..';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '..';
import { EmbeddableLoading } from '../common/components/embeddable_loading_fallback';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
import type { MlDependencies } from '../../application/app';
import { SWIM_LANE_SELECTION_TRIGGER } from '../../ui_actions';
import { AnomalyDetectionEmbeddable } from '../common/anomaly_detection_embeddable';
import { EmbeddableLoading } from '../common/components/embeddable_loading_fallback';
import { EmbeddableSwimLaneContainer } from './embeddable_swim_lane_container_lazy';

export const getDefaultSwimlanePanelTitle = (jobIds: JobId[]) =>
i18n.translate('xpack.ml.swimlaneEmbeddable.title', {
@@ -41,12 +42,37 @@ export class AnomalySwimlaneEmbeddable extends AnomalyDetectionEmbeddable<
private reload$ = new Subject<void>();
public readonly type: string = ANOMALY_SWIMLANE_EMBEDDABLE_TYPE;

// API
public viewBy = new BehaviorSubject<string | undefined>(this.getInput().viewBy);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. If you're initializing these in the constructor via embeddableInputToSubject or embeddableOutputToSubject, you don't need to initialize them via new here. The helpers will handle that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot, leftovers from the original implementation, updated in 48d9c50

public perPage = new BehaviorSubject<number | undefined>(this.getOutput().perPage);
public fromPage = new BehaviorSubject<number | undefined>(this.getOutput().fromPage);

private apiSubscriptions = new Subscription();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I pass it to embeddableInputToSubject and embeddableOutputToSubject to populate subscriptions and unsubscribe on destroy


constructor(
initialInput: AnomalySwimlaneEmbeddableInput,
public services: [CoreStart, MlDependencies, AnomalySwimlaneServices],
parent?: IContainer
) {
super(initialInput, services[2].anomalyDetectorService, services[1].data.dataViews, parent);

this.viewBy = embeddableInputToSubject<string, AnomalySwimlaneEmbeddableInput>(
this.apiSubscriptions,
this,
'viewBy'
);

this.perPage = embeddableOutputToSubject<number, AnomalySwimlaneEmbeddableOutput>(
this.apiSubscriptions,
this,
'perPage'
);

this.fromPage = embeddableOutputToSubject<number, AnomalySwimlaneEmbeddableOutput>(
this.apiSubscriptions,
this,
'fromPage'
);
}

public reportsEmbeddableLoad() {
@@ -104,6 +130,7 @@ export class AnomalySwimlaneEmbeddable extends AnomalyDetectionEmbeddable<
}

public destroy() {
this.apiSubscriptions.unsubscribe();
super.destroy();
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ 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';
import type { JobId } from '../../shared';

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

@@ -27,6 +28,8 @@ export abstract class AnomalyDetectionEmbeddable<
// Need to defer embeddable load in order to resolve data views
deferEmbeddableLoad = true;

public jobIds: JobId[] = [];

protected constructor(
initialInput: Input,
private anomalyDetectorService: AnomalyDetectorService,
@@ -43,6 +46,8 @@ export abstract class AnomalyDetectionEmbeddable<
protected async initializeOutput(initialInput: CommonInput) {
const { jobIds } = initialInput;

this.jobIds = jobIds;

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

45 changes: 12 additions & 33 deletions x-pack/plugins/ml/public/embeddables/types.ts
Original file line number Diff line number Diff line change
@@ -6,34 +6,29 @@
*/

import type { CoreStart } from '@kbn/core/public';
import type { Filter, Query, TimeRange } from '@kbn/es-query';
import type { RefreshInterval } from '@kbn/data-plugin/common';
import type { EmbeddableInput, EmbeddableOutput, IEmbeddable } from '@kbn/embeddable-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { EmbeddableInput, EmbeddableOutput, IEmbeddable } from '@kbn/embeddable-plugin/public';
import type { Filter, Query, TimeRange } from '@kbn/es-query';
import type { MlEntityField } from '@kbn/ml-anomaly-utils';
import type { JobId } from '../../common/types/anomaly_detection_jobs';
import type { SwimlaneType } from '../application/explorer/explorer_constants';
import type { AnomalyDetectorService } from '../application/services/anomaly_detector_service';
import type { AnomalyTimelineService } from '../application/services/anomaly_timeline_service';
import type { MlDependencies } from '../application/app';
import type { MlCapabilitiesService } from '../application/capabilities/check_capabilities';
import type { SwimlaneType } from '../application/explorer/explorer_constants';
import type { AppStateSelectedCells } from '../application/explorer/explorer_utils';
import type { AnomalyDetectorService } from '../application/services/anomaly_detector_service';
import type { AnomalyExplorerChartsService } from '../application/services/anomaly_explorer_charts_service';
import type { AnomalyTimelineService } from '../application/services/anomaly_timeline_service';
import type { MlFieldFormatService } from '../application/services/field_format_service';
import type { MlJobService } from '../application/services/job_service';
import type { MlApiServices } from '../application/services/ml_api_service';
import type { MlResultsService } from '../application/services/results_service';
import type { MlTimeSeriesSearchService } from '../application/timeseriesexplorer/timeseriesexplorer_utils/time_series_search_service';
import type {
AnomalyExplorerChartsEmbeddableType,
AnomalySwimLaneEmbeddableType,
MlEmbeddableTypes,
} from './constants';
import {
ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE,
ANOMALY_SWIMLANE_EMBEDDABLE_TYPE,
} from './constants';
import type { MlResultsService } from '../application/services/results_service';
import type { MlApiServices } from '../application/services/ml_api_service';
import type { MlFieldFormatService } from '../application/services/field_format_service';
import type { MlTimeSeriesSearchService } from '../application/timeseriesexplorer/timeseriesexplorer_utils/time_series_search_service';
import type { MlCapabilitiesService } from '../application/capabilities/check_capabilities';

export interface AnomalySwimlaneEmbeddableCustomInput {
jobIds: JobId[];
@@ -65,7 +60,7 @@ export interface AnomalySwimlaneEmbeddableCustomOutput {
perPage?: number;
fromPage?: number;
interval?: number;
indexPatterns?: DataView[];
indexPatterns: DataView[];
}

export type AnomalySwimlaneEmbeddableOutput = EmbeddableOutput &
@@ -82,14 +77,6 @@ export interface SwimLaneDrilldownContext extends EditSwimlanePanelContext {
data?: AppStateSelectedCells;
}

export function isSwimLaneEmbeddable(arg: unknown): arg is SwimLaneDrilldownContext {
return (
isPopulatedObject(arg, ['embeddable']) &&
isPopulatedObject(arg.embeddable, ['type']) &&
arg.embeddable.type === ANOMALY_SWIMLANE_EMBEDDABLE_TYPE
);
}

/**
* Anomaly Explorer
*/
@@ -158,21 +145,13 @@ export type AnomalyChartsEmbeddableOutput = EmbeddableOutput & AnomalyChartsCust
export interface EditAnomalyChartsPanelContext {
embeddable: IEmbeddable<AnomalyChartsEmbeddableInput, AnomalyChartsEmbeddableOutput>;
}

export interface AnomalyChartsFieldSelectionContext extends EditAnomalyChartsPanelContext {
/**
* Optional fields selected using anomaly charts
*/
data?: MlEntityField[];
}
export function isAnomalyExplorerEmbeddable(
arg: unknown
): arg is AnomalyChartsFieldSelectionContext {
return (
isPopulatedObject(arg, ['embeddable']) &&
isPopulatedObject(arg.embeddable, ['type']) &&
arg.embeddable.type === ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE
);
}

export type MappedEmbeddableTypeOf<TEmbeddableType extends MlEmbeddableTypes> =
TEmbeddableType extends AnomalySwimLaneEmbeddableType
1 change: 0 additions & 1 deletion x-pack/plugins/ml/public/ui_actions/index.ts
Original file line number Diff line number Diff line change
@@ -54,7 +54,6 @@ export function registerMlUiActions(

// Register actions
uiActions.registerAction(editSwimlanePanelAction);
uiActions.registerAction(openInExplorerAction);
uiActions.registerAction(applyInfluencerFiltersAction);
uiActions.registerAction(applyEntityFieldFilterAction);
uiActions.registerAction(applyTimeRangeSelectionAction);
Loading