diff --git a/src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts index f0ab91d7910de..afdd1657e686b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts @@ -19,6 +19,7 @@ import _ from 'lodash'; import { ContainerState, EmbeddableMetadata, Filters, Query, TimeRange } from 'ui/embeddable'; +import { EmbeddableCustomization } from 'ui/embeddable/types'; import { DashboardViewMode } from '../dashboard_view_mode'; import { DashboardMetadata, @@ -42,8 +43,10 @@ export const getEmbeddables = (dashboard: DashboardState): EmbeddablesMap => das // TODO: rename panel.embeddableConfig to embeddableCustomization. Because it's on the panel that's stored on a // dashboard, renaming this will require a migration step. -export const getEmbeddableCustomization = (dashboard: DashboardState, panelId: PanelId): object => - getPanel(dashboard, panelId).embeddableConfig; +export const getEmbeddableCustomization = ( + dashboard: DashboardState, + panelId: PanelId +): EmbeddableCustomization => getPanel(dashboard, panelId).embeddableConfig; export const getEmbeddable = (dashboard: DashboardState, panelId: PanelId): EmbeddableReduxState => dashboard.embeddables[panelId]; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.js b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts similarity index 60% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.js rename to src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index 04d731dbd77b0..b78c7ac41ac3f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.js +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -17,42 +17,71 @@ * under the License. */ -import { PersistedState } from 'ui/persisted_state'; -import { Embeddable } from 'ui/embeddable'; import _ from 'lodash'; +import { ContainerState, Embeddable } from 'ui/embeddable'; +import { OnEmbeddableStateChanged } from 'ui/embeddable/embeddable_factory'; +import { Filters, Query, TimeRange } from 'ui/embeddable/types'; +import { PersistedState } from 'ui/persisted_state'; +import { VisualizeLoader } from 'ui/visualize/loader'; +import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; +import { + VisSavedObject, + VisualizeLoaderParams, + VisualizeUpdateParams, +} from 'ui/visualize/loader/types'; + +export interface VisualizeEmbeddableConfiguration { + onEmbeddableStateChanged: OnEmbeddableStateChanged; + savedVisualization: VisSavedObject; + editUrl?: string; + loader: VisualizeLoader; +} -export class VisualizeEmbeddable extends Embeddable { - constructor({ onEmbeddableStateChanged, savedVisualization, editUrl, loader }) { +export class VisualizeEmbeddable extends Embeddable { + private onEmbeddableStateChanged: OnEmbeddableStateChanged; + private savedVisualization: VisSavedObject; + private loader: VisualizeLoader; + private uiState: PersistedState; + private handler?: EmbeddedVisualizeHandler; + private customization?: object; + private panelTitle?: string; + private timeRange?: TimeRange; + private query?: Query; + private filters?: Filters; + + constructor({ + onEmbeddableStateChanged, + savedVisualization, + editUrl, + loader, + }: VisualizeEmbeddableConfiguration) { super({ metadata: { title: savedVisualization.title, editUrl, - indexPattern: savedVisualization.vis.indexPattern - } + indexPattern: savedVisualization.vis.indexPattern, + }, }); - this._onEmbeddableStateChanged = onEmbeddableStateChanged; + this.onEmbeddableStateChanged = onEmbeddableStateChanged; this.savedVisualization = savedVisualization; this.loader = loader; - const parsedUiState = savedVisualization.uiStateJSON ? JSON.parse(savedVisualization.uiStateJSON) : {}; + const parsedUiState = savedVisualization.uiStateJSON + ? JSON.parse(savedVisualization.uiStateJSON) + : {}; this.uiState = new PersistedState(parsedUiState); - this.uiState.on('change', this._uiStateChangeHandler); + this.uiState.on('change', this.uiStateChangeHandler); } - _uiStateChangeHandler = () => { - this.customization = this.uiState.toJSON(); - this._onEmbeddableStateChanged(this.getEmbeddableState()); - }; - - getInspectorAdapters() { + public getInspectorAdapters() { if (!this.handler) { return undefined; } return this.handler.inspectorAdapters; } - getEmbeddableState() { + public getEmbeddableState() { return { customization: this.customization, }; @@ -62,41 +91,27 @@ export class VisualizeEmbeddable extends Embeddable { * Transfers all changes in the containerState.embeddableCustomization into * the uiState of this visualization. */ - transferCustomizationsToUiState(containerState) { + public transferCustomizationsToUiState(containerState: ContainerState) { // Check for changes that need to be forwarded to the uiState // Since the vis has an own listener on the uiState we don't need to // pass anything from here to the handler.update method const customization = containerState.embeddableCustomization; - if (!_.isEqual(this.customization, customization)) { + if (customization && !_.isEqual(this.customization, customization)) { // Turn this off or the uiStateChangeHandler will fire for every modification. - this.uiState.off('change', this._uiStateChangeHandler); + this.uiState.off('change', this.uiStateChangeHandler); this.uiState.clearAllKeys(); Object.getOwnPropertyNames(customization).forEach(key => { this.uiState.set(key, customization[key]); }); this.customization = customization; - this.uiState.on('change', this._uiStateChangeHandler); - } - } - - /** - * Retrieve the panel title for this panel from the container state. - * This will either return the overwritten panel title or the visualization title. - */ - getPanelTitle(containerState) { - let derivedPanelTitle = ''; - if (!containerState.hidePanelTitles) { - derivedPanelTitle = containerState.customTitle !== undefined ? - containerState.customTitle : - this.savedVisualization.title; + this.uiState.on('change', this.uiStateChangeHandler); } - return derivedPanelTitle; } - onContainerStateChanged(containerState) { + public onContainerStateChanged(containerState: ContainerState) { this.transferCustomizationsToUiState(containerState); - const updatedParams = {}; + const updatedParams: VisualizeUpdateParams = {}; // Check if timerange has changed if (containerState.timeRange !== this.timeRange) { @@ -134,7 +149,7 @@ export class VisualizeEmbeddable extends Embeddable { * @param {Element} domNode * @param {ContainerState} containerState */ - render(domNode, containerState) { + public render(domNode: HTMLElement, containerState: ContainerState) { this.panelTitle = this.getPanelTitle(containerState); this.timeRange = containerState.timeRange; this.query = containerState.query; @@ -142,7 +157,15 @@ export class VisualizeEmbeddable extends Embeddable { this.transferCustomizationsToUiState(containerState); - const handlerParams = { + const dataAttrs: { [key: string]: string } = { + 'shared-item': '', + title: this.panelTitle, + }; + if (this.savedVisualization.description) { + dataAttrs.description = this.savedVisualization.description; + } + + const handlerParams: VisualizeLoaderParams = { uiState: this.uiState, // Append visualization to container instead of replacing its content append: true, @@ -150,28 +173,42 @@ export class VisualizeEmbeddable extends Embeddable { query: containerState.query, filters: containerState.filters, cssClass: `panel-content panel-content--fullWidth`, - // The chrome is permanently hidden in "embed mode" in which case we don't want to show the spy pane, since - // we deem that situation to be more public facing and want to hide more detailed information. - dataAttrs: { - 'shared-item': '', - title: this.panelTitle, - description: this.savedVisualization.description, - } + dataAttrs, }; this.handler = this.loader.embedVisualizationWithSavedObject( domNode, this.savedVisualization, - handlerParams, + handlerParams ); } - destroy() { - this.uiState.off('change', this._uiStateChangeHandler); + public destroy() { + this.uiState.off('change', this.uiStateChangeHandler); this.savedVisualization.destroy(); if (this.handler) { this.handler.destroy(); this.handler.getElement().remove(); } } + + /** + * Retrieve the panel title for this panel from the container state. + * This will either return the overwritten panel title or the visualization title. + */ + private getPanelTitle(containerState: ContainerState) { + let derivedPanelTitle = ''; + if (!containerState.hidePanelTitles) { + derivedPanelTitle = + containerState.customTitle !== undefined + ? containerState.customTitle + : this.savedVisualization.title; + } + return derivedPanelTitle; + } + + private uiStateChangeHandler = () => { + this.customization = this.uiState.toJSON(); + this.onEmbeddableStateChanged(this.getEmbeddableState()); + }; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js index 3bbc21beee682..5c2a873a25bed 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js @@ -18,8 +18,8 @@ */ import $ from 'jquery'; +import { Embeddable, EmbeddableFactory } from 'ui/embeddable'; import { getVisualizeLoader } from 'ui/visualize/loader'; -import { EmbeddableFactory, Embeddable } from 'ui/embeddable'; import { VisualizeEmbeddable } from './visualize_embeddable'; import labDisabledTemplate from './visualize_lab_disabled.html'; @@ -48,30 +48,29 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory { const visId = panelMetadata.id; const editUrl = this.getEditPath(visId); - const waitFor = [ getVisualizeLoader(), this.savedVisualizations.get(visId) ]; - return Promise.all(waitFor) - .then(([loader, savedObject]) => { - const isLabsEnabled = this._config.get('visualize:enableLabs'); + const waitFor = [getVisualizeLoader(), this.savedVisualizations.get(visId)]; + return Promise.all(waitFor).then(([loader, savedObject]) => { + const isLabsEnabled = this._config.get('visualize:enableLabs'); - if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { - return new Embeddable({ - metadata: { - title: savedObject.title, - }, - render: (domNode) => { - const template = $(labDisabledTemplate); - template.find('.visDisabledLabVisualization__title').text(savedObject.title); - $(domNode).html(template); - } - }); - } else { - return new VisualizeEmbeddable({ - onEmbeddableStateChanged, - savedVisualization: savedObject, - editUrl, - loader, - }); - } - }); + if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { + return new Embeddable({ + metadata: { + title: savedObject.title, + }, + render: domNode => { + const template = $(labDisabledTemplate); + template.find('.visDisabledLabVisualization__title').text(savedObject.title); + $(domNode).html(template); + }, + }); + } else { + return new VisualizeEmbeddable({ + onEmbeddableStateChanged, + savedVisualization: savedObject, + editUrl, + loader, + }); + } + }); } } diff --git a/src/ui/public/embeddable/embeddable_factory.ts b/src/ui/public/embeddable/embeddable_factory.ts index 09a2c7535c365..c85b00b58fb92 100644 --- a/src/ui/public/embeddable/embeddable_factory.ts +++ b/src/ui/public/embeddable/embeddable_factory.ts @@ -20,6 +20,8 @@ import { Embeddable } from './embeddable'; import { EmbeddableState } from './types'; +export type OnEmbeddableStateChanged = (embeddableStateChanges: EmbeddableState) => void; + /** * The EmbeddableFactory creates and initializes an embeddable instance */ @@ -35,6 +37,6 @@ export abstract class EmbeddableFactory { */ public abstract create( containerMetadata: { id: string }, - onEmbeddableStateChanged: (embeddableStateChanges: EmbeddableState) => void + onEmbeddableStateChanged: OnEmbeddableStateChanged ): Promise; } diff --git a/src/ui/public/embeddable/types.ts b/src/ui/public/embeddable/types.ts index 1f883e0b920d8..683e3471df6f6 100644 --- a/src/ui/public/embeddable/types.ts +++ b/src/ui/public/embeddable/types.ts @@ -22,9 +22,13 @@ export interface TimeRange { from: string; } +export interface FilterMeta { + disabled: boolean; +} + // TODO: Filter object representation needs to be fleshed out. export interface Filter { - meta: object; + meta: FilterMeta; query: object; } @@ -39,6 +43,9 @@ export interface Query { language: QueryLanguageType; query: string; } +export interface EmbeddableCustomization { + [key: string]: object | string; +} export interface ContainerState { // 'view' or 'edit'. Should probably be an enum but I'm undecided where to define it, here or in dashboard code. @@ -51,7 +58,7 @@ export interface ContainerState { query: Query; // The shape will be up to the embeddable type. - embeddableCustomization?: object; + embeddableCustomization?: EmbeddableCustomization; /** * Whether or not panel titles are hidden. It is not the embeddable's responsibility to hide the title (the container @@ -77,9 +84,9 @@ export interface EmbeddableState { * Any customization data that should be stored at the panel level. For * example, pie slice colors, or custom per panel sort order or columns. */ - customization: object; + customization?: object; /** * A possible filter the embeddable wishes dashboard to apply. */ - stagedFilter: object; + stagedFilter?: object; } diff --git a/src/ui/public/persisted_state/persisted_state.d.ts b/src/ui/public/persisted_state/persisted_state.d.ts index 6a02df8f67f7b..b5d7513172e76 100644 --- a/src/ui/public/persisted_state/persisted_state.d.ts +++ b/src/ui/public/persisted_state/persisted_state.d.ts @@ -18,5 +18,12 @@ */ // It's currenty hard to properly type PersistedState, since it dynamically -// inherits the class passed into the constructor. -export type PersistedState = any; +// inherits the class passed into the constructor. These typings are really pretty bad +// but needed in the short term to make incremental progress elsewhere. Can't even +// just use `any` since then typescript complains about using PersistedState as a +// constructor. +export class PersistedState { + constructor(value?: any, path?: any, EmitterClass?: any); + // method you want typed so far + [prop: string]: any; +} diff --git a/src/ui/public/vis/request_handlers/request_handlers.d.ts b/src/ui/public/vis/request_handlers/request_handlers.d.ts index 357cced0e1fb5..e6f93c5353f93 100644 --- a/src/ui/public/vis/request_handlers/request_handlers.d.ts +++ b/src/ui/public/vis/request_handlers/request_handlers.d.ts @@ -33,7 +33,7 @@ export interface RequestHandlerParams { filters?: Filters; forceFetch: boolean; queryFilter: QueryFilter; - uiState: PersistedState; + uiState?: PersistedState; partialRows?: boolean; inspectorAdapters?: Adapters; isHierarchical?: boolean; diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/ui/public/visualize/loader/embedded_visualize_handler.ts index 4b6551921d915..39d19d12592a7 100644 --- a/src/ui/public/visualize/loader/embedded_visualize_handler.ts +++ b/src/ui/public/visualize/loader/embedded_visualize_handler.ts @@ -58,6 +58,7 @@ export class EmbeddedVisualizeHandler { * @ignore */ public readonly data$: Rx.Observable; + public readonly inspectorAdapters: Adapters = {}; private vis: Vis; private loaded: boolean = false; private destroyed: boolean = false; @@ -81,7 +82,6 @@ export class EmbeddedVisualizeHandler { private uiState: PersistedState; private dataLoader: VisualizeDataLoader; private dataSubject: Rx.Subject; - private readonly inspectorAdapters: Adapters = {}; private actions: any = {}; private events$: Rx.Observable; private autoFetch: boolean; diff --git a/src/ui/public/visualize/loader/types.ts b/src/ui/public/visualize/loader/types.ts index 797d360073090..79e6133dbcad8 100644 --- a/src/ui/public/visualize/loader/types.ts +++ b/src/ui/public/visualize/loader/types.ts @@ -52,6 +52,9 @@ export interface VisSavedObject { vis: Vis; description?: string; searchSource: SearchSource; + title: string; + uiStateJSON?: string; + destroy: () => void; } /** diff --git a/src/ui/public/visualize/loader/visualize_loader.ts b/src/ui/public/visualize/loader/visualize_loader.ts index 24aa615cbb343..ee08ae3cd85a0 100644 --- a/src/ui/public/visualize/loader/visualize_loader.ts +++ b/src/ui/public/visualize/loader/visualize_loader.ts @@ -29,7 +29,7 @@ import { IPrivate } from '../../private'; import { EmbeddedVisualizeHandler } from './embedded_visualize_handler'; import { VisSavedObject, VisualizeLoaderParams } from './types'; -class VisualizeLoader { +export class VisualizeLoader { constructor(private readonly savedVisualizations: any, private readonly Private: IPrivate) {} /**