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

[Dashboard] [Embeddable] Add Ability to Defer Embeddable Loaded State #107227

Merged
merged 11 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export declare abstract class Container<TChildInput extends Partial<EmbeddableIn
| [getPanelState(embeddableId)](./kibana-plugin-plugins-embeddable-public.container.getpanelstate.md) | | |
| [reload()](./kibana-plugin-plugins-embeddable-public.container.reload.md) | | |
| [removeEmbeddable(embeddableId)](./kibana-plugin-plugins-embeddable-public.container.removeembeddable.md) | | |
| [setChildLoaded(embeddable)](./kibana-plugin-plugins-embeddable-public.container.setchildloaded.md) | | |
| [untilEmbeddableLoaded(id)](./kibana-plugin-plugins-embeddable-public.container.untilembeddableloaded.md) | | |
| [updateInputForChild(id, changes)](./kibana-plugin-plugins-embeddable-public.container.updateinputforchild.md) | | |

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) &gt; [Container](./kibana-plugin-plugins-embeddable-public.container.md) &gt; [setChildLoaded](./kibana-plugin-plugins-embeddable-public.container.setchildloaded.md)

## Container.setChildLoaded() method

<b>Signature:</b>

```typescript
setChildLoaded(embeddable: IEmbeddable): void;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| embeddable | <code>IEmbeddable</code> | |

<b>Returns:</b>

`void`

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) &gt; [Embeddable](./kibana-plugin-plugins-embeddable-public.embeddable.md) &gt; [deferEmbeddableLoad](./kibana-plugin-plugins-embeddable-public.embeddable.deferembeddableload.md)

## Embeddable.deferEmbeddableLoad property

<b>Signature:</b>

```typescript
readonly deferEmbeddableLoad: boolean;
```
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export declare abstract class Embeddable<TEmbeddableInput extends EmbeddableInpu

| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [deferEmbeddableLoad](./kibana-plugin-plugins-embeddable-public.embeddable.deferembeddableload.md) | | <code>boolean</code> | |
| [fatalError](./kibana-plugin-plugins-embeddable-public.embeddable.fatalerror.md) | | <code>Error</code> | |
| [id](./kibana-plugin-plugins-embeddable-public.embeddable.id.md) | | <code>string</code> | |
| [input](./kibana-plugin-plugins-embeddable-public.embeddable.input.md) | | <code>TEmbeddableInput</code> | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface IContainer<Inherited extends {} = {}, I extends ContainerInput<
| [getChild(id)](./kibana-plugin-plugins-embeddable-public.icontainer.getchild.md) | Returns the child embeddable with the given id. |
| [getInputForChild(id)](./kibana-plugin-plugins-embeddable-public.icontainer.getinputforchild.md) | Returns the input for the given child. Uses a combination of explicit input for the child stored on the parent and derived/inherited input taken from the container itself. |
| [removeEmbeddable(embeddableId)](./kibana-plugin-plugins-embeddable-public.icontainer.removeembeddable.md) | Removes the embeddable with the given id. |
| [setChildLoaded(embeddable)](./kibana-plugin-plugins-embeddable-public.icontainer.setchildloaded.md) | Embeddables which have deferEmbeddableLoad set to true need to manually call setChildLoaded on their parent container to communicate when they have finished loading. |
| [untilEmbeddableLoaded(id)](./kibana-plugin-plugins-embeddable-public.icontainer.untilembeddableloaded.md) | Call if you want to wait until an embeddable with that id has finished loading. |
| [updateInputForChild(id, changes)](./kibana-plugin-plugins-embeddable-public.icontainer.updateinputforchild.md) | Changes the input for a given child. Note, this will override any inherited state taken from the container itself. |

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) &gt; [IContainer](./kibana-plugin-plugins-embeddable-public.icontainer.md) &gt; [setChildLoaded](./kibana-plugin-plugins-embeddable-public.icontainer.setchildloaded.md)

## IContainer.setChildLoaded() method

Embeddables which have deferEmbeddableLoad set to true need to manually call setChildLoaded on their parent container to communicate when they have finished loading.

<b>Signature:</b>

```typescript
setChildLoaded<E extends IEmbeddable = IEmbeddable>(embeddable: E): void;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| embeddable | <code>E</code> | the embeddable to set |

<b>Returns:</b>

`void`

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) &gt; [IEmbeddable](./kibana-plugin-plugins-embeddable-public.iembeddable.md) &gt; [deferEmbeddableLoad](./kibana-plugin-plugins-embeddable-public.iembeddable.deferembeddableload.md)

## IEmbeddable.deferEmbeddableLoad property

If set to true, defer embeddable load tells the container that this embeddable type isn't completely loaded when the constructor returns. This embeddable will have to manually call setChildLoaded on its parent when all of its initial output is finalized. For instance, after loading a saved object.

<b>Signature:</b>

```typescript
readonly deferEmbeddableLoad: boolean;
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface IEmbeddable<I extends EmbeddableInput = EmbeddableInput, O exte

| Property | Type | Description |
| --- | --- | --- |
| [deferEmbeddableLoad](./kibana-plugin-plugins-embeddable-public.iembeddable.deferembeddableload.md) | <code>boolean</code> | If set to true, defer embeddable load tells the container that this embeddable type isn't completely loaded when the constructor returns. This embeddable will have to manually call setChildLoaded on its parent when all of its initial output is finalized. For instance, after loading a saved object. |
| [enhancements](./kibana-plugin-plugins-embeddable-public.iembeddable.enhancements.md) | <code>object</code> | Extra abilities added to Embeddable by <code>*_enhanced</code> plugins. |
| [fatalError](./kibana-plugin-plugins-embeddable-public.iembeddable.fatalerror.md) | <code>Error</code> | If this embeddable has encountered a fatal error, that error will be stored here |
| [id](./kibana-plugin-plugins-embeddable-public.iembeddable.id.md) | <code>string</code> | A unique identifier for this embeddable. Mainly only used by containers to map their Panel States to a child embeddable instance. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,8 @@

import { uniqBy } from 'lodash';
import deepEqual from 'fast-deep-equal';
import { merge, Observable, pipe, EMPTY } from 'rxjs';
import {
distinctUntilChanged,
catchError,
switchMap,
startWith,
filter,
mapTo,
map,
} from 'rxjs/operators';
import { Observable, pipe } from 'rxjs';
import { distinctUntilChanged, switchMap, filter, mapTo, map } from 'rxjs/operators';

import { DashboardContainer } from '..';
import { isErrorEmbeddable } from '../../services/embeddable';
Expand All @@ -36,7 +28,7 @@ export const syncDashboardIndexPatterns = ({
}: SyncDashboardIndexPatternsProps) => {
const updateIndexPatternsOperator = pipe(
filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)),
map((container: DashboardContainer): IndexPattern[] => {
map((container: DashboardContainer): IndexPattern[] | undefined => {
let panelIndexPatterns: IndexPattern[] = [];
Object.values(container.getChildIds()).forEach((id) => {
const embeddableInstance = container.getChild(id);
Expand All @@ -46,18 +38,31 @@ export const syncDashboardIndexPatterns = ({
panelIndexPatterns.push(...embeddableIndexPatterns);
});
panelIndexPatterns = uniqBy(panelIndexPatterns, 'id');

/**
* If no index patterns have been returned yet, and there is at least one embeddable which
* hasn't yet loaded, defer the loading of the default index pattern by returning undefined.
*/
if (
panelIndexPatterns.length === 0 &&
Object.keys(container.getOutput().embeddableLoaded).length > 0 &&
Object.values(container.getOutput().embeddableLoaded).some((value) => value === false)
) {
return;
}
return panelIndexPatterns;
}),
distinctUntilChanged((a, b) =>
deepEqual(
a.map((ip) => ip && ip.id),
b.map((ip) => ip && ip.id)
a?.map((ip) => ip && ip.id),
b?.map((ip) => ip && ip.id)
)
),
// using switchMap for previous task cancellation
switchMap((panelIndexPatterns: IndexPattern[]) => {
switchMap((panelIndexPatterns?: IndexPattern[]) => {
return new Observable((observer) => {
if (panelIndexPatterns && panelIndexPatterns.length > 0) {
if (!panelIndexPatterns) return;
if (panelIndexPatterns.length > 0) {
if (observer.closed) return;
onUpdateIndexPatterns(panelIndexPatterns);
observer.complete();
Expand All @@ -72,32 +77,8 @@ export const syncDashboardIndexPatterns = ({
})
);

return merge(
// output of dashboard container itself
dashboardContainer.getOutput$(),
// plus output of dashboard container children,
// children may change, so make sure we subscribe/unsubscribe with switchMap
dashboardContainer.getOutput$().pipe(
Dosant marked this conversation as resolved.
Show resolved Hide resolved
map(() => dashboardContainer!.getChildIds()),
distinctUntilChanged(deepEqual),
switchMap((newChildIds: string[]) =>
merge(
...newChildIds.map((childId) =>
dashboardContainer!
.getChild(childId)
.getOutput$()
// Embeddables often throw errors into their output streams.
// This should not affect dashboard loading
.pipe(catchError(() => EMPTY))
)
)
)
)
)
.pipe(
mapTo(dashboardContainer),
startWith(dashboardContainer), // to trigger initial index pattern update
updateIndexPatternsOperator
)
return dashboardContainer
.getOutput$()
.pipe(mapTo(dashboardContainer), updateIndexPatternsOperator)
.subscribe();
};
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@ export interface DashboardTopNavState {

type CompleteDashboardAppState = Required<
DashboardAppState,
| 'getLatestDashboardState'
| 'dashboardContainer'
| 'savedDashboard'
| 'indexPatterns'
| 'applyFilters'
'getLatestDashboardState' | 'dashboardContainer' | 'savedDashboard' | 'applyFilters'
>;

export const isCompleteDashboardAppState = (
Expand All @@ -78,7 +74,6 @@ export const isCompleteDashboardAppState = (
Boolean(state.getLatestDashboardState) &&
Boolean(state.dashboardContainer) &&
Boolean(state.savedDashboard) &&
Boolean(state.indexPatterns) &&
Boolean(state.applyFilters)
);
};
Expand Down
20 changes: 13 additions & 7 deletions src/plugins/embeddable/public/lib/containers/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ export abstract class Container<
});
}

public setChildLoaded(embeddable: IEmbeddable) {
this.children[embeddable.id] = embeddable;
Dosant marked this conversation as resolved.
Show resolved Hide resolved
this.updateOutput({
embeddableLoaded: {
...this.output.embeddableLoaded,
[embeddable.id]: true,
},
} as Partial<TContainerOutput>);
}

public updateInputForChild<EEI extends EmbeddableInput = EmbeddableInput>(
id: string,
changes: Partial<EEI>
Expand Down Expand Up @@ -313,13 +323,9 @@ export abstract class Container<
return;
}

this.children[embeddable.id] = embeddable;
this.updateOutput({
embeddableLoaded: {
...this.output.embeddableLoaded,
[panel.explicitInput.id]: true,
},
} as Partial<TContainerOutput>);
if (!embeddable.deferEmbeddableLoad) {
this.setChildLoaded(embeddable);
}
} else if (embeddable === undefined) {
this.removeEmbeddable(panel.explicitInput.id);
}
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/embeddable/public/lib/containers/i_container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ export interface IContainer<
*/
getChild<E extends Embeddable<EmbeddableInput> = Embeddable<EmbeddableInput>>(id: string): E;

/**
* Embeddables which have deferEmbeddableLoad set to true need to manually call setChildLoaded
* on their parent container to communicate when they have finished loading.
* @param embeddable - the embeddable to set
*/
setChildLoaded<E extends IEmbeddable = IEmbeddable>(embeddable: E): void;

/**
* Removes the embeddable with the given id.
* @param embeddableId
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export abstract class Embeddable<

public readonly parent?: IContainer;
public readonly isContainer: boolean = false;
public readonly deferEmbeddableLoad: boolean = false;
public abstract readonly type: string;
public readonly id: string;
public fatalError?: Error;
Expand Down Expand Up @@ -210,6 +211,11 @@ export abstract class Embeddable<
protected onFatalError(e: Error) {
this.fatalError = e;
this.output$.error(e);
// if the container is waiting for this embeddable to complete loading,
// a fatal error counts as complete.
if (this.deferEmbeddableLoad && this.parent?.isContainer) {
this.parent.setChildLoaded(this);
}
}

private onResetInput(newInput: TEmbeddableInput) {
Expand Down
8 changes: 8 additions & 0 deletions src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ export interface IEmbeddable<
**/
readonly id: string;

/**
* If set to true, defer embeddable load tells the container that this embeddable
* type isn't completely loaded when the constructor returns. This embeddable
* will have to manually call setChildLoaded on its parent when all of its initial
* output is finalized. For instance, after loading a saved object.
*/
readonly deferEmbeddableLoad: boolean;

/**
* Unique ID an embeddable is assigned each time it is initialized. This ID
* is different for different instances of the same embeddable. For example,
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/embeddable/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ export abstract class Container<TChildInput extends Partial<EmbeddableInput> = {
// (undocumented)
removeEmbeddable(embeddableId: string): void;
// (undocumented)
setChildLoaded(embeddable: IEmbeddable): void;
// (undocumented)
untilEmbeddableLoaded<TEmbeddable extends IEmbeddable>(id: string): Promise<TEmbeddable | ErrorEmbeddable>;
// (undocumented)
updateInputForChild<EEI extends EmbeddableInput = EmbeddableInput>(id: string, changes: Partial<EEI>): void;
Expand Down Expand Up @@ -263,6 +265,8 @@ export class EditPanelAction implements Action_3<ActionContext_3> {
// @public (undocumented)
export abstract class Embeddable<TEmbeddableInput extends EmbeddableInput = EmbeddableInput, TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput> implements IEmbeddable<TEmbeddableInput, TEmbeddableOutput> {
constructor(input: TEmbeddableInput, output: TEmbeddableOutput, parent?: IContainer);
// (undocumented)
readonly deferEmbeddableLoad: boolean;
destroy(): void;
// (undocumented)
fatalError?: Error;
Expand Down Expand Up @@ -648,6 +652,7 @@ export interface IContainer<Inherited extends {} = {}, I extends ContainerInput<
getChild<E extends Embeddable<EmbeddableInput> = Embeddable<EmbeddableInput>>(id: string): E;
getInputForChild<EEI extends EmbeddableInput>(id: string): EEI;
removeEmbeddable(embeddableId: string): void;
setChildLoaded<E extends IEmbeddable = IEmbeddable>(embeddable: E): void;
untilEmbeddableLoaded<TEmbeddable extends IEmbeddable>(id: string): Promise<TEmbeddable | ErrorEmbeddable>;
updateInputForChild<EEI extends EmbeddableInput>(id: string, changes: Partial<EEI>): void;
}
Expand All @@ -656,6 +661,7 @@ export interface IContainer<Inherited extends {} = {}, I extends ContainerInput<
//
// @public (undocumented)
export interface IEmbeddable<I extends EmbeddableInput = EmbeddableInput, O extends EmbeddableOutput = EmbeddableOutput> {
readonly deferEmbeddableLoad: boolean;
destroy(): void;
enhancements?: object;
fatalError?: Error;
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/lens/public/embeddable/embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export class Embeddable
implements ReferenceOrValueEmbeddable<LensByValueInput, LensByReferenceInput> {
type = DOC_TYPE;

deferEmbeddableLoad = true;

private expressionRenderer: ReactExpressionRendererType;
private savedVis: Document | undefined;
private expression: string | undefined | null;
Expand Down Expand Up @@ -484,6 +486,11 @@ export class Embeddable
editUrl: this.deps.basePath.prepend(`/app/lens${getEditPath(savedObjectId)}`),
indexPatterns,
});

// communicate to the parent that this embeddable's initialization is done.
if (this.parent?.isContainer) {
this.parent.setChildLoaded(this);
}
}

private getIsEditable() {
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class MapEmbeddable
extends Embeddable<MapEmbeddableInput, MapEmbeddableOutput>
implements ReferenceOrValueEmbeddable<MapByValueInput, MapByReferenceInput> {
type = MAP_SAVED_OBJECT_TYPE;
deferEmbeddableLoad = true;

private _isActive: boolean;
private _savedMap: SavedMap;
Expand Down Expand Up @@ -148,6 +149,11 @@ export class MapEmbeddable
return;
}

// communicate to the parent that this embeddable's initialization is done.
if (this.parent?.isContainer) {
this.parent.setChildLoaded(this);
}

this._isInitialized = true;
if (this._domNode) {
this.render(this._domNode);
Expand Down