Typescript visualize_embeddable file (#26660) (#26708)

* Typescript visualize_embeddable file

* Remove left over comment
This commit is contained in:
Stacey Gammon 2018-12-05 19:59:58 -05:00 committed by GitHub
parent eda90761c8
commit b82bf8272a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 143 additions and 85 deletions

View file

@ -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];

View file

@ -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 class VisualizeEmbeddable extends Embeddable {
constructor({ onEmbeddableStateChanged, savedVisualization, editUrl, loader }) {
export interface VisualizeEmbeddableConfiguration {
onEmbeddableStateChanged: OnEmbeddableStateChanged;
savedVisualization: VisSavedObject;
editUrl?: string;
loader: VisualizeLoader;
}
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);
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;
}
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());
};
}

View file

@ -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,
});
}
});
}
}

View file

@ -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<Embeddable>;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -33,7 +33,7 @@ export interface RequestHandlerParams {
filters?: Filters;
forceFetch: boolean;
queryFilter: QueryFilter;
uiState: PersistedState;
uiState?: PersistedState;
partialRows?: boolean;
inspectorAdapters?: Adapters;
isHierarchical?: boolean;

View file

@ -58,6 +58,7 @@ export class EmbeddedVisualizeHandler {
* @ignore
*/
public readonly data$: Rx.Observable<any>;
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<any>;
private readonly inspectorAdapters: Adapters = {};
private actions: any = {};
private events$: Rx.Observable<any>;
private autoFetch: boolean;

View file

@ -52,6 +52,9 @@ export interface VisSavedObject {
vis: Vis;
description?: string;
searchSource: SearchSource;
title: string;
uiStateJSON?: string;
destroy: () => void;
}
/**

View file

@ -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) {}
/**