Skip to content

Commit

Permalink
Typescript visualize_embeddable file (elastic#26660)
Browse files Browse the repository at this point in the history
* Typescript visualize_embeddable file

* Remove left over comment
  • Loading branch information
stacey-gammon committed Dec 5, 2018
1 parent d3b88c2 commit 989a110
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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) {
Expand Down Expand Up @@ -134,44 +149,66 @@ 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;
this.filters = containerState.filters;

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,
timeRange: containerState.timeRange,
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());
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
});
}
});
}
}
4 changes: 3 additions & 1 deletion src/ui/public/embeddable/embeddable_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -35,6 +37,6 @@ export abstract class EmbeddableFactory {
*/
public abstract create(
containerMetadata: { id: string },
onEmbeddableStateChanged: (embeddableStateChanges: EmbeddableState) => void
onEmbeddableStateChanged: OnEmbeddableStateChanged
): Promise<Embeddable>;
}
15 changes: 11 additions & 4 deletions src/ui/public/embeddable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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;
}
11 changes: 9 additions & 2 deletions src/ui/public/persisted_state/persisted_state.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 1 addition & 1 deletion src/ui/public/vis/request_handlers/request_handlers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface RequestHandlerParams {
filters?: Filters;
forceFetch: boolean;
queryFilter: QueryFilter;
uiState: PersistedState;
uiState?: PersistedState;
partialRows?: boolean;
inspectorAdapters?: Adapters;
isHierarchical?: boolean;
Expand Down
Loading

0 comments on commit 989a110

Please sign in to comment.