Skip to content

Commit

Permalink
[ML] Decouple edit-anomaly-swimlane UI action (#179073)
Browse files Browse the repository at this point in the history
## Summary

- Decouples edit-anomaly-swimlane and apply-time-range-selection UI
actions from embeddable framework

Part of #178375
Part of #174967
  • Loading branch information
darnautov authored Mar 22, 2024
1 parent 6dcfbd7 commit 917f854
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n';
import { Subject, Subscription, type BehaviorSubject } from 'rxjs';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import type { IContainer } from '@kbn/embeddable-plugin/public';
import { embeddableInputToSubject } from '@kbn/embeddable-plugin/public';
import { embeddableOutputToSubject } from '@kbn/embeddable-plugin/public';
import type { MlEntityField } from '@kbn/ml-anomaly-utils';
import { EmbeddableAnomalyChartsContainer } from './embeddable_anomaly_charts_container_lazy';
Expand Down Expand Up @@ -43,6 +44,7 @@ export class AnomalyChartsEmbeddable extends AnomalyDetectionEmbeddable<
public readonly type: string = ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE;

// API
public readonly jobIds: BehaviorSubject<JobId[] | undefined>;
public entityFields: BehaviorSubject<MlEntityField[] | undefined>;

private apiSubscriptions = new Subscription();
Expand All @@ -54,6 +56,12 @@ export class AnomalyChartsEmbeddable extends AnomalyDetectionEmbeddable<
) {
super(initialInput, services[2].anomalyDetectorService, services[1].data.dataViews, parent);

this.jobIds = embeddableInputToSubject<JobId[], AnomalyChartsEmbeddableInput>(
this.apiSubscriptions,
this,
'jobIds'
);

this.entityFields = embeddableOutputToSubject<MlEntityField[], AnomalyChartsEmbeddableOutput>(
this.apiSubscriptions,
this,
Expand Down
22 changes: 22 additions & 0 deletions x-pack/plugins/ml/public/embeddables/anomaly_charts/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 type { MlEntityField } from '@kbn/ml-anomaly-utils';
import type { HasType, PublishingSubject } from '@kbn/presentation-publishing';
import type { JobId } from '../../shared';
import type { AnomalyExplorerChartsEmbeddableType } from '../constants';
import type { MlEmbeddableBaseApi } from '../types';

export interface AnomalyChartsFieldSelectionApi {
jobIds: PublishingSubject<JobId[]>;
entityFields: PublishingSubject<MlEntityField[] | undefined>;
}

export interface AnomalyChartsEmbeddableApi
extends HasType<AnomalyExplorerChartsEmbeddableType>,
MlEmbeddableBaseApi,
AnomalyChartsFieldSelectionApi {}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import { Subject, Subscription, type BehaviorSubject } from 'rxjs';
import type {
AnomalySwimlaneEmbeddableInput,
AnomalySwimlaneEmbeddableOutput,
AnomalySwimlaneEmbeddableUserInput,
AnomalySwimlaneServices,
} from '..';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '..';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
import type { MlDependencies } from '../../application/app';
import type { SwimlaneType } from '../../application/explorer/explorer_constants';
import { SWIM_LANE_SELECTION_TRIGGER } from '../../ui_actions';
import { AnomalyDetectionEmbeddable } from '../common/anomaly_detection_embeddable';
import { EmbeddableLoading } from '../common/components/embeddable_loading_fallback';
Expand All @@ -43,9 +45,12 @@ export class AnomalySwimlaneEmbeddable extends AnomalyDetectionEmbeddable<
public readonly type: string = ANOMALY_SWIMLANE_EMBEDDABLE_TYPE;

// API
public viewBy: BehaviorSubject<string | undefined>;
public perPage: BehaviorSubject<number | undefined>;
public fromPage: BehaviorSubject<number | undefined>;
public readonly jobIds: BehaviorSubject<JobId[] | undefined>;
public readonly viewBy: BehaviorSubject<string | undefined>;
public readonly swimlaneType: BehaviorSubject<SwimlaneType | undefined>;
public readonly perPage: BehaviorSubject<number | undefined>;
public readonly fromPage: BehaviorSubject<number | undefined>;
public readonly interval: BehaviorSubject<number | undefined>;

private apiSubscriptions = new Subscription();

Expand All @@ -56,12 +61,24 @@ export class AnomalySwimlaneEmbeddable extends AnomalyDetectionEmbeddable<
) {
super(initialInput, services[2].anomalyDetectorService, services[1].data.dataViews, parent);

this.jobIds = embeddableInputToSubject<JobId[], AnomalySwimlaneEmbeddableInput>(
this.apiSubscriptions,
this,
'jobIds'
);

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

this.swimlaneType = embeddableInputToSubject<SwimlaneType, AnomalySwimlaneEmbeddableInput>(
this.apiSubscriptions,
this,
'swimlaneType'
);

this.perPage = embeddableOutputToSubject<number, AnomalySwimlaneEmbeddableOutput>(
this.apiSubscriptions,
this,
Expand All @@ -73,6 +90,16 @@ export class AnomalySwimlaneEmbeddable extends AnomalyDetectionEmbeddable<
this,
'fromPage'
);

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

public updateUserInput(update: AnomalySwimlaneEmbeddableUserInput) {
this.updateInput(update);
}

public reportsEmbeddableLoad() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ export class AnomalySwimlaneEmbeddableFactory

try {
const { resolveAnomalySwimlaneUserInput } = await import('./anomaly_swimlane_setup_flyout');
return await resolveAnomalySwimlaneUserInput(coreStart, deps.data.dataViews);
const userInput = await resolveAnomalySwimlaneUserInput(coreStart, deps.data.dataViews);

return {
...userInput,
title: userInput.panelTitle,
};
} catch (e) {
return Promise.reject();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { SwimlaneType } from '../../application/explorer/explorer_constants';
import { SWIMLANE_TYPE } from '../../application/explorer/explorer_constants';
import type { AnomalySwimlaneEmbeddableInput } from '..';
import type { AnomalySwimlaneEmbeddableInput, AnomalySwimlaneEmbeddableUserInput } from '..';

interface ExplicitInput {
panelTitle: string;
swimlaneType: SwimlaneType;
viewBy?: string;
}
export type ExplicitInput = Omit<AnomalySwimlaneEmbeddableUserInput, 'jobIds'>;

export interface AnomalySwimlaneInitializerProps {
defaultTitle: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import { VIEW_BY_JOB_LABEL } from '../../application/explorer/explorer_constants
import { AnomalySwimlaneInitializer } from './anomaly_swimlane_initializer';
import { getDefaultSwimlanePanelTitle } from './anomaly_swimlane_embeddable';
import { HttpService } from '../../application/services/http_service';
import type { AnomalySwimlaneEmbeddableInput } from '..';
import type { AnomalySwimlaneEmbeddableInput, AnomalySwimlaneEmbeddableUserInput } from '..';
import { resolveJobSelection } from '../common/resolve_job_selection';
import { mlApiServicesProvider } from '../../application/services/ml_api_service';

export async function resolveAnomalySwimlaneUserInput(
coreStart: CoreStart,
dataViews: DataViewsContract,
input?: AnomalySwimlaneEmbeddableInput
): Promise<Partial<AnomalySwimlaneEmbeddableInput>> {
input?: Partial<AnomalySwimlaneEmbeddableInput>
): Promise<AnomalySwimlaneEmbeddableUserInput> {
const { http, overlays, theme, i18n } = coreStart;

const { getJobs } = mlApiServicesProvider(new HttpService(http));
Expand All @@ -44,7 +44,6 @@ export async function resolveAnomalySwimlaneUserInput(
modalSession.close();
resolve({
jobIds,
title: explicitInput.panelTitle,
...explicitInput,
});
}}
Expand Down
48 changes: 48 additions & 0 deletions x-pack/plugins/ml/public/embeddables/anomaly_swimlane/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 type {
HasType,
PublishesWritablePanelTitle,
PublishingSubject,
} from '@kbn/presentation-publishing';
import { apiIsOfType } from '@kbn/presentation-publishing';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { SwimlaneType } from '../../application/explorer/explorer_constants';
import type { JobId } from '../../shared';
import type { AnomalySwimLaneEmbeddableType } from '../constants';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '../constants';
import type { AnomalySwimlaneEmbeddableUserInput, MlEmbeddableBaseApi } from '../types';
import type { AppStateSelectedCells } from '../../application/explorer/explorer_utils';

export interface AnomalySwimLaneComponentApi {
jobIds: PublishingSubject<JobId[]>;
swimlaneType: PublishingSubject<SwimlaneType>;
viewBy: PublishingSubject<string>;
perPage: PublishingSubject<number>;
fromPage: PublishingSubject<number>;
interval: PublishingSubject<number | undefined>;
updateUserInput: (input: AnomalySwimlaneEmbeddableUserInput) => void;
}

export interface AnomalySwimLaneEmbeddableApi
extends HasType<AnomalySwimLaneEmbeddableType>,
PublishesWritablePanelTitle,
MlEmbeddableBaseApi,
AnomalySwimLaneComponentApi {}

export interface AnomalySwimLaneActionContext {
embeddable: AnomalySwimLaneEmbeddableApi;
data?: AppStateSelectedCells;
}

export function isSwimLaneEmbeddableContext(arg: unknown): arg is AnomalySwimLaneActionContext {
return (
isPopulatedObject(arg, ['embeddable']) &&
apiIsOfType(arg.embeddable, ANOMALY_SWIMLANE_EMBEDDABLE_TYPE)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
* 2.0.
*/

import { type DataView } from '@kbn/data-views-plugin/common';
import { type DataViewsContract } from '@kbn/data-views-plugin/public';
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 type { BehaviorSubject } from 'rxjs';
import { firstValueFrom } from 'rxjs';
import { type AnomalyDetectorService } from '../../application/services/anomaly_detector_service';
import type { JobId } from '../../shared';
Expand All @@ -28,7 +29,8 @@ export abstract class AnomalyDetectionEmbeddable<
// Need to defer embeddable load in order to resolve data views
deferEmbeddableLoad = true;

public jobIds: JobId[] = [];
// API
public abstract jobIds: BehaviorSubject<JobId[] | undefined>;

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

this.jobIds = jobIds;

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

Expand Down
26 changes: 24 additions & 2 deletions x-pack/plugins/ml/public/embeddables/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import type { DataView } from '@kbn/data-views-plugin/common';
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 {
EmbeddableApiContext,
HasParentApi,
HasType,
PublishesUnifiedSearch,
PublishesViewMode,
} from '@kbn/presentation-publishing';
import type { JobId } from '../../common/types/anomaly_detection_jobs';
import type { MlDependencies } from '../application/app';
import type { MlCapabilitiesService } from '../application/capabilities/check_capabilities';
Expand All @@ -30,6 +37,18 @@ import type {
MlEmbeddableTypes,
} from './constants';

export type MlEmbeddableBaseApi = Partial<
HasParentApi<PublishesUnifiedSearch> & PublishesViewMode & PublishesUnifiedSearch
>;

/** Manual input by the user */
export interface AnomalySwimlaneEmbeddableUserInput {
jobIds: JobId[];
panelTitle: string;
swimlaneType: SwimlaneType;
viewBy?: string;
}

export interface AnomalySwimlaneEmbeddableCustomInput {
jobIds: JobId[];
swimlaneType: SwimlaneType;
Expand Down Expand Up @@ -66,8 +85,11 @@ export interface AnomalySwimlaneEmbeddableCustomOutput {
export type AnomalySwimlaneEmbeddableOutput = EmbeddableOutput &
AnomalySwimlaneEmbeddableCustomOutput;

export interface EditSwimlanePanelContext {
embeddable: IEmbeddable<AnomalySwimlaneEmbeddableInput, AnomalySwimlaneEmbeddableOutput>;
export type EditSwimLaneActionApi = HasType<AnomalySwimLaneEmbeddableType> &
Partial<HasParentApi<PublishesUnifiedSearch>>;

export interface EditSwimlanePanelContext extends EmbeddableApiContext {
embeddable: EditSwimLaneActionApi;
}

export interface SwimLaneDrilldownContext extends EditSwimlanePanelContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
* 2.0.
*/

import { DASHBOARD_APP_ID } from '@kbn/dashboard-plugin/public';
import type { Filter } from '@kbn/es-query';
import { FilterStateStore } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { firstValueFrom } from 'rxjs';
import { DASHBOARD_APP_ID } from '@kbn/dashboard-plugin/public';
import type { MlCoreSetup } from '../plugin';
import { isAnomalySwimlaneSelectionTriggerContext } from './triggers';
import { SWIMLANE_TYPE, VIEW_BY_JOB_LABEL } from '../application/explorer/explorer_constants';
import type { SwimLaneDrilldownContext } from '../embeddables';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '../embeddables';
import type { MlCoreSetup } from '../plugin';
import { CONTROLLED_BY_SWIM_LANE_FILTER } from './constants';

export const APPLY_INFLUENCER_FILTERS_ACTION = 'applyInfluencerFiltersAction';
Expand All @@ -27,7 +28,7 @@ export function createApplyInfluencerFiltersAction(
return {
id: 'apply-to-current-view',
type: APPLY_INFLUENCER_FILTERS_ACTION,
getIconType(context: SwimLaneDrilldownContext): string {
getIconType(): string {
return 'filter';
},
getDisplayName() {
Expand Down Expand Up @@ -73,18 +74,18 @@ export function createApplyInfluencerFiltersAction(
})
);
},
async isCompatible({ embeddable, data }) {
async isCompatible(context: EmbeddableApiContext) {
const [{ application }] = await getStartServices();
const appId = await firstValueFrom(application.currentAppId$);

// Only compatible with view by influencer swim lanes and single selection
return (
embeddable.type === ANOMALY_SWIMLANE_EMBEDDABLE_TYPE &&
data !== undefined &&
data.type === SWIMLANE_TYPE.VIEW_BY &&
data.viewByFieldName !== VIEW_BY_JOB_LABEL &&
data.lanes.length === 1 &&
supportedApps.includes(appId!)
supportedApps.includes(appId!) &&
isAnomalySwimlaneSelectionTriggerContext(context) &&
context.data !== undefined &&
context.data.type === SWIMLANE_TYPE.VIEW_BY &&
context.data.viewByFieldName !== VIEW_BY_JOB_LABEL &&
context.data.lanes.length === 1
);
},
};
Expand Down
Loading

0 comments on commit 917f854

Please sign in to comment.