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

[7.x] [Dashboard] [Embeddable] Add Ability to Defer Embeddable Loaded State (#107227) #108459

Merged
merged 1 commit into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all 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 Expand Up @@ -48,6 +49,7 @@ export declare abstract class Embeddable<TEmbeddableInput extends EmbeddableInpu
| [onFatalError(e)](./kibana-plugin-plugins-embeddable-public.embeddable.onfatalerror.md) | | |
| [reload()](./kibana-plugin-plugins-embeddable-public.embeddable.reload.md) | | Reload will be called when there is a request to refresh the data or view, even if the input data did not change.<!-- -->In case if input data did change and reload is requested input$ and output$ would still emit before <code>reload</code> is called<!-- -->The order would be as follows: input$ output$ reload() \-\-\-- updated$ |
| [render(el)](./kibana-plugin-plugins-embeddable-public.embeddable.render.md) | | |
| [setInitializationFinished()](./kibana-plugin-plugins-embeddable-public.embeddable.setinitializationfinished.md) | | communicate to the parent embeddable that this embeddable's initialization is finished. This only applies to embeddables which defer their loading state with deferEmbeddableLoad. |
| [supportedTriggers()](./kibana-plugin-plugins-embeddable-public.embeddable.supportedtriggers.md) | | |
| [updateInput(changes)](./kibana-plugin-plugins-embeddable-public.embeddable.updateinput.md) | | |
| [updateOutput(outputChanges)](./kibana-plugin-plugins-embeddable-public.embeddable.updateoutput.md) | | |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- 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; [setInitializationFinished](./kibana-plugin-plugins-embeddable-public.embeddable.setinitializationfinished.md)

## Embeddable.setInitializationFinished() method

communicate to the parent embeddable that this embeddable's initialization is finished. This only applies to embeddables which defer their loading state with deferEmbeddableLoad.

<b>Signature:</b>

```typescript
protected setInitializationFinished(): void;
```
<b>Returns:</b>

`void`

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(
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
30 changes: 18 additions & 12 deletions src/plugins/embeddable/public/lib/containers/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ export abstract class Container<
});
}

public setChildLoaded(embeddable: IEmbeddable) {
// make sure the panel wasn't removed in the mean time, since the embeddable creation is async
if (!this.input.panels[embeddable.id]) {
embeddable.destroy();
return;
}

this.children[embeddable.id] = embeddable;
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 @@ -307,19 +323,9 @@ export abstract class Container<
// switch over to inline creation we can probably clean this up, and force EmbeddableFactory.create to always
// return an embeddable, or throw an error.
if (embeddable) {
// make sure the panel wasn't removed in the mean time, since the embeddable creation is async
if (!this.input.panels[panel.explicitInput.id]) {
embeddable.destroy();
return;
if (!embeddable.deferEmbeddableLoad) {
this.setChildLoaded(embeddable);
}

this.children[embeddable.id] = embeddable;
this.updateOutput({
embeddableLoaded: {
...this.output.embeddableLoaded,
[panel.explicitInput.id]: true,
},
} as Partial<TContainerOutput>);
} 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
16 changes: 16 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 @@ -196,6 +197,16 @@ export abstract class Embeddable<
return;
}

/**
* communicate to the parent embeddable that this embeddable's initialization is finished.
* This only applies to embeddables which defer their loading state with deferEmbeddableLoad.
*/
protected setInitializationFinished() {
if (this.deferEmbeddableLoad && this.parent?.isContainer) {
this.parent.setChildLoaded(this);
}
}

protected updateOutput(outputChanges: Partial<TEmbeddableOutput>): void {
const newOutput = {
...this.output,
Expand All @@ -210,6 +221,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
Loading