Skip to content

Commit

Permalink
Remove dashboard embeddable (elastic#194892)
Browse files Browse the repository at this point in the history
Closes elastic#197281

PR replaces `DashboardContainer`, which implements legacy Container and
Embeddable interfaces, with plain old javascript object implementation
returned from `getDashboardApi`.

The following are out of scope for this PR and will be accomplished at a
later time:
1) re-factoring dashboard folder structure
2) removing all uses of Embeddable and EmbeddableInput types
3) removing legacy types like DashboardContainerInput

---------

Co-authored-by: Elastic Machine <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Hannah Mudge <[email protected]>
Co-authored-by: Devon Thomson <[email protected]>
  • Loading branch information
5 people authored and CAWilson94 committed Dec 12, 2024
1 parent 9d8783d commit 93c3deb
Show file tree
Hide file tree
Showing 82 changed files with 2,470 additions and 5,212 deletions.
1 change: 0 additions & 1 deletion packages/presentation/presentation_containers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export {
export {
canTrackContentfulRender,
type TrackContentfulRender,
type TracksQueryPerformance,
} from './interfaces/performance_trackers';
export {
apiIsPresentationContainer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,3 @@ export interface TrackContentfulRender {
export const canTrackContentfulRender = (root: unknown): root is TrackContentfulRender => {
return root !== null && typeof root === 'object' && 'trackContentfulRender' in root;
};

export interface TracksQueryPerformance {
firstLoad: boolean;
creationStartTime?: number;
creationEndTime?: number;
lastLoadStartTime?: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import { OverlayRef } from '@kbn/core-mount-utils-browser';

interface TracksOverlaysOptions {
export interface TracksOverlaysOptions {
/**
* If present, the panel with this ID will be focused when the overlay is opened. This can be used in tandem with a push
* flyout to edit a panel's settings in context
Expand Down
1 change: 1 addition & 0 deletions packages/presentation/presentation_publishing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export {
initializeTimeRange,
type SerializedTimeRange,
} from './interfaces/fetch/initialize_time_range';
export { apiPublishesReload, type PublishesReload } from './interfaces/fetch/publishes_reload';
export {
apiPublishesFilters,
apiPublishesPartialUnifiedSearch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { PublishingSubject } from '../../publishing_subject';
import { Observable } from 'rxjs';

export interface PublishesReload {
reload$: PublishingSubject<void>;
reload$: Omit<Observable<void>, 'next'>;
}

export const apiPublishesReload = (unknownApi: null | unknown): unknownApi is PublishesReload => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@ export const initializeTitles = (
const panelDescription = new BehaviorSubject<string | undefined>(rawState.description);
const hidePanelTitle = new BehaviorSubject<boolean | undefined>(rawState.hidePanelTitles);

const setPanelTitle = (value: string | undefined) => panelTitle.next(value);
const setHidePanelTitle = (value: boolean | undefined) => hidePanelTitle.next(value);
const setPanelDescription = (value: string | undefined) => panelDescription.next(value);
const setPanelTitle = (value: string | undefined) => {
if (value !== panelTitle.value) panelTitle.next(value);
};
const setHidePanelTitle = (value: boolean | undefined) => {
if (value !== hidePanelTitle.value) hidePanelTitle.next(value);
};
const setPanelDescription = (value: string | undefined) => {
if (value !== panelDescription.value) panelDescription.next(value);
};

const titleComparators: StateComparators<SerializedTitles> = {
title: [panelTitle, setPanelTitle],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,14 @@
*/

import { isEmpty, xor } from 'lodash';
import moment, { Moment } from 'moment';
import fastIsEqual from 'fast-deep-equal';

import { DashboardPanelMap } from '../../../../common';

const convertTimeToUTCString = (time?: string | Moment): undefined | string => {
if (moment(time).isValid()) {
return moment(time).utc().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
} else {
// If it's not a valid moment date, then it should be a string representing a relative time
// like 'now' or 'now-15m'.
return time as string;
}
};

export const areTimesEqual = (
timeA?: string | Moment | undefined,
timeB?: string | Moment | undefined
) => {
return convertTimeToUTCString(timeA) === convertTimeToUTCString(timeB);
};

export const defaultDiffFunction = (a: unknown, b: unknown) => fastIsEqual(a, b);
import { DashboardPanelMap } from '../../common';

/**
* Checks whether the panel maps have the same keys, and if they do, whether all of the other keys inside each panel
* are equal. Skips explicit input as that needs to be handled asynchronously.
*/
export const getPanelLayoutsAreEqual = (
export const arePanelLayoutsEqual = (
originalPanels: DashboardPanelMap,
newPanels: DashboardPanelMap
) => {
Expand All @@ -57,7 +36,7 @@ export const getPanelLayoutsAreEqual = (
];
for (const key of keys) {
if (key === undefined) continue;
if (!defaultDiffFunction(originalObj[key], newObj[key])) differences[key] = newObj[key];
if (!fastIsEqual(originalObj[key], newObj[key])) differences[key] = newObj[key];
}
return differences;
};
Expand Down
61 changes: 61 additions & 0 deletions src/plugins/dashboard/public/dashboard_api/data_loading_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { BehaviorSubject, debounceTime, first, map } from 'rxjs';
import {
PublishesDataLoading,
PublishingSubject,
apiPublishesDataLoading,
} from '@kbn/presentation-publishing';
import { combineCompatibleChildrenApis } from '@kbn/presentation-containers';

export function initializeDataLoadingManager(
children$: PublishingSubject<{ [key: string]: unknown }>
) {
const dataLoading$ = new BehaviorSubject<boolean | undefined>(undefined);

const dataLoadingSubscription = combineCompatibleChildrenApis<
PublishesDataLoading,
boolean | undefined
>(
{ children$ },
'dataLoading',
apiPublishesDataLoading,
undefined,
// flatten method
(values) => {
return values.some((isLoading) => isLoading);
}
).subscribe((isAtLeastOneChildLoading) => {
dataLoading$.next(isAtLeastOneChildLoading);
});

return {
api: {
dataLoading: dataLoading$,
},
internalApi: {
waitForPanelsToLoad$: dataLoading$.pipe(
// debounce to give time for panels to start loading if they are going to load
debounceTime(300),
first((isLoading: boolean | undefined) => {
return !isLoading;
}),
map(() => {
// Observable notifies subscriber when loading is finished
// Return void to not expose internal implementation details of observable
return;
})
),
},
cleanup: () => {
dataLoadingSubscription.unsubscribe();
},
};
}
72 changes: 72 additions & 0 deletions src/plugins/dashboard/public/dashboard_api/data_views_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { uniqBy } from 'lodash';
import { BehaviorSubject, combineLatest, Observable, of, switchMap } from 'rxjs';

import { DataView } from '@kbn/data-views-plugin/common';
import { combineCompatibleChildrenApis } from '@kbn/presentation-containers';
import {
apiPublishesDataViews,
PublishesDataViews,
PublishingSubject,
} from '@kbn/presentation-publishing';

import { ControlGroupApi } from '@kbn/controls-plugin/public';
import { dataService } from '../services/kibana_services';

export function initializeDataViewsManager(
controlGroupApi$: PublishingSubject<ControlGroupApi | undefined>,
children$: PublishingSubject<{ [key: string]: unknown }>
) {
const dataViews = new BehaviorSubject<DataView[] | undefined>([]);

const controlGroupDataViewsPipe: Observable<DataView[] | undefined> = controlGroupApi$.pipe(
switchMap((controlGroupApi) => {
return controlGroupApi ? controlGroupApi.dataViews : of([]);
})
);

const childDataViewsPipe = combineCompatibleChildrenApis<PublishesDataViews, DataView[]>(
{ children$ },
'dataViews',
apiPublishesDataViews,
[]
);

const dataViewsSubscription = combineLatest([controlGroupDataViewsPipe, childDataViewsPipe])
.pipe(
switchMap(async ([controlGroupDataViews, childDataViews]) => {
const allDataViews = [...(controlGroupDataViews ?? []), ...childDataViews];
if (allDataViews.length === 0) {
try {
const defaultDataView = await dataService.dataViews.getDefaultDataView();
if (defaultDataView) {
allDataViews.push(defaultDataView);
}
} catch (error) {
// ignore error getting default data view
}
}
return uniqBy(allDataViews, 'id');
})
)
.subscribe((newDataViews) => {
dataViews.next(newDataViews);
});

return {
api: {
dataViews,
},
cleanup: () => {
dataViewsSubscription.unsubscribe();
},
};
}
Loading

0 comments on commit 93c3deb

Please sign in to comment.