diff --git a/src/plugins/embeddable/kibana.json b/src/plugins/embeddable/kibana.json index c9694ad7b9423f..6a8e6079232aa6 100644 --- a/src/plugins/embeddable/kibana.json +++ b/src/plugins/embeddable/kibana.json @@ -12,6 +12,7 @@ ], "requiredBundles": [ "savedObjects", - "kibanaReact" + "kibanaReact", + "kibanaUtils" ] } diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index fcecf117d7d52b..f5d902e6859ad9 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -19,6 +19,7 @@ import { cloneDeep, isEqual } from 'lodash'; import * as Rx from 'rxjs'; +import { RenderCompleteDispatcher } from '../../../../kibana_utils/public'; import { Adapters, ViewMode } from '../types'; import { IContainer } from '../containers'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; @@ -47,6 +48,8 @@ export abstract class Embeddable< private readonly input$: Rx.BehaviorSubject; private readonly output$: Rx.BehaviorSubject; + protected renderComplete = new RenderCompleteDispatcher(); + // Listener to parent changes, if this embeddable exists in a parent, in order // to update input when the parent changes. private parentSubscription?: Rx.Subscription; @@ -133,7 +136,9 @@ export abstract class Embeddable< } } - public render(domNode: HTMLElement | Element): void { + public render(domNode: HTMLElement): void { + this.renderComplete.setEl(domNode); + if (this.destroyed) { throw new Error('Embeddable has been destroyed'); } diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 0d6ff61e8a8e53..7628b1d41b4528 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -197,21 +197,4 @@ export interface IEmbeddable< * List of triggers that this embeddable will execute. */ supportedTriggers(): Array; - - /** - * Should emit `true` when embeddable has finished loading its data and has - * completely rendered. Should emit `false` when when embeddable is loading data - * again. At the start it is assumed that embeddable is has not completed - * rendering, so this embeddable has to emit `true` at least once. - * - * This is used for reporting to know that embeddable is ready, so - * it can take a screenshot. It is also used in functional tests to know that - * page has stabilized. - */ - renderComplete$?: Observable; - - /** - * Use this hook to report when error happened during rendering. - */ - renderError$?: Observable; } diff --git a/src/plugins/kibana_utils/public/render_complete/render_complete.ts b/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts similarity index 52% rename from src/plugins/kibana_utils/public/render_complete/render_complete.ts rename to src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts index 26c88292ef8772..8462f799bd8e81 100644 --- a/src/plugins/kibana_utils/public/render_complete/render_complete.ts +++ b/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts @@ -29,25 +29,50 @@ export function dispatchRenderStart(el: HTMLElement) { dispatchEvent(el, 'renderStart'); } -export class RenderComplete { - constructor(private readonly el: HTMLElement) {} +/** + * Should call `dispatchComplete()` when UI block has finished loading its data and has + * completely rendered. Should `dispatchInProgress()` every time UI block + * starts loading data again. At the start it is assumed that UI block is loading + * so it dispatches "in progress" automatically, so you need to call `setRenderComplete` + * at least once. + * + * This is used for reporting to know that UI block is ready, so + * it can take a screenshot. It is also used in functional tests to know that + * page has stabilized. + */ +export class RenderCompleteDispatcher { + private count: number = 0; + private el?: HTMLElement; + + constructor(el?: HTMLElement) { + this.setEl(el); + } + + public setEl(el?: HTMLElement) { + this.el = el; + this.count = 0; + if (el) this.dispatchInProgress(); + } - public readonly dispatchInProgress = () => { + public dispatchInProgress() { + if (!this.el) return; this.el.setAttribute('data-render-complete', 'false'); + this.el.setAttribute('data-rendering-count', String(this.count)); dispatchRenderStart(this.el); - }; + } - public readonly dispatchComplete = (count: number) => { + public dispatchComplete() { + if (!this.el) return; + this.count++; this.el.setAttribute('data-render-complete', 'true'); - this.el.setAttribute('data-rendering-count', count.toString()); + this.el.setAttribute('data-rendering-count', String(this.count)); dispatchRenderComplete(this.el); - }; + } - public readonly dispatchError = () => { - this.el.setAttribute( - 'data-rendering-count', - String(Number(this.el.getAttribute('data-rendering-count')) + 1) - ); + public dispatchError() { + if (!this.el) return; + this.count++; this.el.setAttribute('data-render-complete', 'false'); - }; + this.el.setAttribute('data-rendering-count', String(this.count)); + } } diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 726d6db7dd14c7..bffab9afc16e61 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -36,7 +36,6 @@ import { IContainer, Adapters, } from '../../../../plugins/embeddable/public'; -import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public'; import { IExpressionLoaderParams, ExpressionsStart, @@ -97,9 +96,6 @@ export class VisualizeEmbeddable extends Embeddable(); - protected renderError$ = new Rx.Subject(); - constructor( timefilter: TimefilterContract, { vis, editPath, editUrl, indexPatterns, editable, deps }: VisualizeEmbeddableConfiguration, @@ -248,26 +244,20 @@ export class VisualizeEmbeddable extends Embeddable Boolean(this.getInspectorAdapters()); onContainerLoading = () => { - this.domNode.setAttribute('data-render-complete', 'false'); + this.renderComplete.dispatchInProgress(); this.updateOutput({ loading: true, error: undefined }); }; - onContainerRender = (count: number) => { - this.domNode.setAttribute('data-render-complete', 'true'); - this.domNode.setAttribute('data-rendering-count', count.toString()); + onContainerRender = () => { + this.renderComplete.dispatchComplete(); this.updateOutput({ loading: false, error: undefined }); - dispatchRenderComplete(this.domNode); }; onContainerError = (error: ExpressionRenderError) => { if (this.abortController) { this.abortController.abort(); } - this.domNode.setAttribute( - 'data-rendering-count', - this.domNode.getAttribute('data-rendering-count') + 1 - ); - this.domNode.setAttribute('data-render-complete', 'false'); + this.renderComplete.dispatchError(); this.updateOutput({ loading: false, error }); }; @@ -287,6 +277,7 @@ export class VisualizeEmbeddable extends Embeddable