Skip to content

Commit

Permalink
wip swim lane react embeddable
Browse files Browse the repository at this point in the history
  • Loading branch information
darnautov committed Mar 27, 2024
1 parent 59386ba commit d337847
Show file tree
Hide file tree
Showing 15 changed files with 452 additions and 196 deletions.
1 change: 1 addition & 0 deletions src/plugins/embeddable/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export {
ReactEmbeddableRenderer,
reactEmbeddableRegistryHasKey,
registerReactEmbeddableFactory,
getReactEmbeddableFactory,
type DefaultEmbeddableApi,
type ReactEmbeddableFactory,
type ReactEmbeddableRegistration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export {
reactEmbeddableRegistryHasKey,
registerReactEmbeddableFactory,
getReactEmbeddableFactory,
} from './react_embeddable_registry';
export { ReactEmbeddableRenderer } from './react_embeddable_renderer';
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
PublishesPanelTitle,
HasParentApi,
PublishesViewMode,
PublishesUnifiedSearch,
} from '@kbn/presentation-publishing';
import { UiActionsService } from '@kbn/ui-actions-plugin/public';
import { MaybePromise } from '@kbn/utility-types';
Expand Down Expand Up @@ -68,9 +69,9 @@ export interface DefaultPresentationPanelApi
PublishesBlockingError &
PublishesPanelDescription &
PublishesDisabledActionIds &
HasParentApi<
PresentationContainer &
Partial<Pick<PublishesPanelTitle, 'hidePanelTitle'> & PublishesViewMode>
HasParentApi<PresentationContainer> &
Partial<
Pick<PublishesPanelTitle, 'hidePanelTitle'> & PublishesViewMode & PublishesUnifiedSearch
>
> {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,75 @@ import { i18n } from '@kbn/i18n';

import type { StartServicesAccessor } from '@kbn/core/public';

import type { EmbeddableFactoryDefinition, IContainer } from '@kbn/embeddable-plugin/public';
import type { IAnomalySwimlaneEmbeddable } from './anomaly_swimlane_embeddable';
import { PLUGIN_ID, PLUGIN_ICON, ML_APP_NAME } from '../../../common/constants/app';
import { HttpService } from '../../application/services/http_service';
import type { MlPluginStart, MlStartDependencies } from '../../plugin';
import type { MlDependencies } from '../../application/app';
import type {
EmbeddableFactoryDefinition,
IContainer,
ReactEmbeddableFactory,
} from '@kbn/embeddable-plugin/public';
import { registerReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import type { AnomalySwimlaneEmbeddableInput, AnomalySwimlaneEmbeddableServices } from '..';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '..';
import { ML_APP_NAME, PLUGIN_ICON, PLUGIN_ID } from '../../../common/constants/app';
import type { MlDependencies } from '../../application/app';
import { HttpService } from '../../application/services/http_service';
import type { MlPluginStart, MlStartDependencies } from '../../plugin';
import type { IAnomalySwimlaneEmbeddable } from './anomaly_swimlane_embeddable';
import type { AnomalySwimLaneEmbeddableApi, AnomalySwimLaneEmbeddableState } from './types';

/**
* Provides the services required by the Anomaly Swimlane Embeddable.
*/
export const getServices = async (
getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>
): Promise<AnomalySwimlaneEmbeddableServices> => {
const [coreStart, pluginsStart] = await getStartServices();

const { AnomalyDetectorService } = await import(
'../../application/services/anomaly_detector_service'
);
const { AnomalyTimelineService } = await import(
'../../application/services/anomaly_timeline_service'
);
const { mlApiServicesProvider } = await import('../../application/services/ml_api_service');
const { mlResultsServiceProvider } = await import('../../application/services/results_service');

const httpService = new HttpService(coreStart.http);
const anomalyDetectorService = new AnomalyDetectorService(httpService);
const anomalyTimelineService = new AnomalyTimelineService(
pluginsStart.data.query.timefilter.timefilter,
coreStart.uiSettings,
mlResultsServiceProvider(mlApiServicesProvider(httpService))
);

return [
coreStart,
pluginsStart as MlDependencies,
{ anomalyDetectorService, anomalyTimelineService },
];
};

export const registerAnomalySwimLaneEmbeddableFactory = (
getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>
) => {
const factory: ReactEmbeddableFactory<
AnomalySwimLaneEmbeddableState,
AnomalySwimLaneEmbeddableApi
> = {
type: ANOMALY_SWIMLANE_EMBEDDABLE_TYPE,
deserializeState: (state) => {
return state.rawState as AnomalySwimLaneEmbeddableState;
},
buildEmbeddable: async (state, buildApi) => {
const { buildAnomalySwimLaneEmbeddable } = await import(
'./build_anomaly_swim_lane_embeddable'
);
const services = await getServices(getStartServices);
return await buildAnomalySwimLaneEmbeddable(state, buildApi, services);
},
};

registerReactEmbeddableFactory(factory);
};

export class AnomalySwimlaneEmbeddableFactory
implements EmbeddableFactoryDefinition<AnomalySwimlaneEmbeddableInput>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* 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 { initializeReactEmbeddableTitles } from '@kbn/embeddable-plugin/public';
import type {
EmbeddableStateComparators,
ReactEmbeddableApiRegistration,
} from '@kbn/embeddable-plugin/public/react_embeddable_system/types';
import type { TimeRange } from '@kbn/es-query';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import fastIsEqual from 'fast-deep-equal';
import React, { Suspense, useEffect } from 'react';
import { BehaviorSubject, Subscription } from 'rxjs';
import type { AnomalySwimlaneEmbeddableServices, AnomalySwimlaneEmbeddableUserInput } from '..';
import type { SwimlaneType } from '../../application/explorer/explorer_constants';
import { SWIM_LANE_DEFAULT_PAGE_SIZE } from '../../application/explorer/explorer_constants';
import type { JobId } from '../../shared';
import { buildDataViewPublishingApi } from '../common/anomaly_detection_embeddable';
import { EmbeddableLoading } from '../common/components/embeddable_loading_fallback';
import EmbeddableSwimLaneContainer from './embeddable_swim_lane_container';
import type { AnomalySwimLaneEmbeddableApi, AnomalySwimLaneEmbeddableState } from './types';

export const buildAnomalySwimLaneEmbeddable = async (
state: AnomalySwimLaneEmbeddableState,
buildApi: (
apiRegistration: ReactEmbeddableApiRegistration<
AnomalySwimLaneEmbeddableState,
AnomalySwimLaneEmbeddableApi
>,
comparators: EmbeddableStateComparators<AnomalySwimLaneEmbeddableState>
) => AnomalySwimLaneEmbeddableApi,
services: AnomalySwimlaneEmbeddableServices
) => {
const subscriptions = new Subscription();

const jobIds = new BehaviorSubject<JobId[]>(state.jobIds);
const swimlaneType = new BehaviorSubject<SwimlaneType>(state.swimlaneType);
const viewBy = new BehaviorSubject<string | undefined>(state.viewBy);
const fromPage = new BehaviorSubject<number>(1);
const perPage = new BehaviorSubject<number>(state.perPage ?? SWIM_LANE_DEFAULT_PAGE_SIZE);
const interval = new BehaviorSubject<number | undefined>(undefined);

const { titlesApi, titleComparators, serializeTitles } = initializeReactEmbeddableTitles(state);

const timeRange$ = new BehaviorSubject<TimeRange | undefined>(state.timeRange);
function setTimeRange(nextTimeRange: TimeRange | undefined) {
timeRange$.next(nextTimeRange);
}

const dataLoading$ = new BehaviorSubject<boolean | undefined>(true);

const api = buildApi(
{
...titlesApi,
jobIds,
swimlaneType,
viewBy,
fromPage,
perPage,
interval,
setInterval: (v) => interval.next(v),
updateUserInput: (update: AnomalySwimlaneEmbeddableUserInput) => {
jobIds.next(update.jobIds);
swimlaneType.next(update.swimlaneType);
viewBy.next(update.viewBy);
titlesApi.setPanelTitle(update.panelTitle);
},
updatePagination: (update: { perPage?: number; fromPage: number }) => {
fromPage.next(update.fromPage);
if (update.perPage) {
perPage.next(update.perPage);
}
},
dataViews: buildDataViewPublishingApi(
{
anomalyDetectorService: services[2].anomalyDetectorService,
dataViewsService: services[1].data.dataViews,
},
{ jobIds },
subscriptions
),
timeRange$,
setTimeRange,
dataLoading: dataLoading$,
serializeState: () => {
return {
rawState: {
...serializeTitles(),
jobIds: jobIds.value,
swimlaneType: swimlaneType.value,
viewBy: viewBy.value,
perPage: perPage.value,
timeRange: timeRange$.value,
},

references: [],
};
},
},
{
timeRange: [timeRange$, setTimeRange, fastIsEqual],
...titleComparators,
jobIds: [jobIds, jobIds.next, fastIsEqual],
swimlaneType: [swimlaneType, swimlaneType.next],
viewBy: [viewBy, viewBy.next],
// We do not want to store the current page
fromPage: [fromPage, fromPage.next, () => true],
perPage: [perPage, perPage.next],
}
);

const appliedTimeRange$ = new BehaviorSubject(
timeRange$.value ?? api.parentApi?.timeRange$?.value
);
subscriptions.add(
api.timeRange$.subscribe((timeRange) => {
appliedTimeRange$.next(timeRange ?? api.parentApi?.timeRange$?.value);
})
);
if (api.parentApi?.timeRange$) {
subscriptions.add(
api.parentApi?.timeRange$.subscribe((parentTimeRange) => {
if (timeRange$?.value) {
return;
}
appliedTimeRange$.next(parentTimeRange);
})
);
}
api.appliedTimeRange$ = appliedTimeRange$;

const onError = () => {
dataLoading$.next(false);
};

const onLoading = () => {
dataLoading$.next(true);
};

const refresh$ = new BehaviorSubject<void>(undefined);

const onRenderComplete = () => {};

return {
api,
Component: () => {
const I18nContext = services[0].i18n.Context;
const theme = services[0].theme;

useEffect(function onUnmount() {
return () => {
subscriptions.unsubscribe();
};
}, []);

return (
<I18nContext>
<KibanaThemeProvider theme={theme}>
<KibanaContextProvider services={{ ...services[0] }}>
<Suspense fallback={<EmbeddableLoading />}>
<EmbeddableSwimLaneContainer
api={api}
id={api.uuid}
services={services}
refresh={refresh$}
onRenderComplete={onRenderComplete}
onLoading={onLoading}
onError={onError}
/>
</Suspense>
</KibanaContextProvider>
</KibanaThemeProvider>
</I18nContext>
);
},
};
};
Loading

0 comments on commit d337847

Please sign in to comment.