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

[lens] Editor frame initializes datasources and visualizations #36060

Merged
merged 6 commits into from
May 9, 2019
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
4 changes: 4 additions & 0 deletions x-pack/plugins/lens/public/app_plugin/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import React from 'react';
import { editorFrameSetup, editorFrameStop } from '../editor_frame_plugin';
import { indexPatternDatasourceSetup, indexPatternDatasourceStop } from '../indexpattern_plugin';
import { xyVisualizationSetup, xyVisualizationStop } from '../xy_visualization_plugin';
import { App } from './app';

export class AppPlugin {
Expand All @@ -16,15 +17,18 @@ export class AppPlugin {
// TODO: These plugins should not be called from the top level, but since this is the
// entry point to the app we have no choice until the new platform is ready
const indexPattern = indexPatternDatasourceSetup();
const xyVisualization = xyVisualizationSetup();
const editorFrame = editorFrameSetup();

editorFrame.registerDatasource('indexpattern', indexPattern);
editorFrame.registerVisualization('xy', xyVisualization);

return <App editorFrame={editorFrame} />;
}

stop() {
indexPatternDatasourceStop();
xyVisualizationStop();
editorFrameStop();
}
}
Expand Down
116 changes: 101 additions & 15 deletions x-pack/plugins/lens/public/editor_frame_plugin/editor_frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,120 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import React, { useReducer, useEffect } from 'react';
import { Datasource, Visualization } from '../types';

interface EditorFrameProps {
datasources: { [key: string]: Datasource };
visualizations: { [key: string]: Visualization };

initialDatasource?: string;
}

interface DatasourceState {
datasourceName: string;
visualizationName: string;

datasourceState: any;
visualizationState: any;
}

interface UpdateDatasourceAction {
type: 'UPDATE_DATASOURCE';
payload: any;
}

interface UpdateVisualizationAction {
type: 'UPDATE_VISUALIZATION';
payload: any;
}

type Action = UpdateDatasourceAction | UpdateVisualizationAction;

function stateReducer(state: DatasourceState, action: Action): DatasourceState {
switch (action.type) {
case 'UPDATE_DATASOURCE':
return {
...state,
datasourceState: action.payload,
};
case 'UPDATE_VISUALIZATION':
return {
...state,
visualizationState: action.payload,
};
}
return state;
}

export function EditorFrame(props: EditorFrameProps) {
const keys = Object.keys(props.datasources);
const dsKeys = Object.keys(props.datasources);
const vKeys = Object.keys(props.visualizations);

const [state, dispatch] = useReducer(stateReducer, {
datasourceName: props.initialDatasource || dsKeys[0],
visualizationName: vKeys[0],

datasourceState: null,
visualizationState: null,
});

useEffect(() => {
const vState = props.visualizations[state.visualizationName].initialize();
props.datasources[state.datasourceName].initialize().then(dsState => {
dispatch({
type: 'UPDATE_DATASOURCE',
payload: dsState,
});
});

dispatch({
type: 'UPDATE_VISUALIZATION',
payload: vState,
});
}, []);

return (
<div>
<h2>Editor Frame</h2>

{keys.map(key => (
<div
key={key}
ref={domElement => {
if (domElement) {
props.datasources[key].renderDataPanel(domElement, {
state: {},
setState: () => {},
});
}
}}
/>
))}
<div
ref={domElement => {
if (domElement) {
props.datasources[state.datasourceName].renderDataPanel(domElement, {
state: state.datasourceState,
setState: newState =>
dispatch({
type: 'UPDATE_DATASOURCE',
payload: newState,
}),
});
}
}}
/>

<div
ref={domElement => {
if (domElement) {
props.visualizations[state.visualizationName].renderConfigPanel(domElement, {
datasource: props.datasources[state.datasourceName].getPublicAPI(
state.datasourceState,
newState =>
dispatch({
type: 'UPDATE_DATASOURCE',
payload: newState,
})
),
state: state.visualizationState,
setState: newState =>
dispatch({
type: 'UPDATE_VISUALIZATION',
payload: newState,
}),
});
}
}}
/>
</div>
);
}
22 changes: 18 additions & 4 deletions x-pack/plugins/lens/public/editor_frame_plugin/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ import { EditorFrame } from './editor_frame';
class EditorFramePlugin {
constructor() {}

private datasources: { [key: string]: Datasource } = {};
private visualizations: { [key: string]: Visualization } = {};
private datasources: {
[key: string]: Datasource;
} = {};
private visualizations: {
[key: string]: Visualization;
} = {};

private initialDatasource?: string;

private element: Element | null = null;

Expand All @@ -23,7 +29,11 @@ class EditorFramePlugin {
render: domElement => {
this.element = domElement;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't follow the createInstance proposal form above (and I think we should), we have to check whether render was already called on another element and clean up there:

if (this.element !== null && this.element !== domElement) {
  unmountComponentAtNode(this.element);
}

render(
<EditorFrame datasources={this.datasources} visualizations={this.visualizations} />,
<EditorFrame
datasources={this.datasources}
visualizations={this.visualizations}
initialDatasource={this.initialDatasource}
/>,
domElement
);
},
Expand All @@ -33,7 +43,11 @@ class EditorFramePlugin {
// on it's own because we are loosing type information here.
// So it's basically explicitly saying "I'm dropping the information about type T here
// because this information isn't useful to me." but without using any which can leak
this.datasources[name] = datasource as Datasource<unknown, unknown>;
this.datasources[name] = datasource as Datasource<unknown>;

if (!this.initialDatasource) {
this.initialDatasource = name;
}
},
registerVisualization: (name, visualization) => {
this.visualizations[name] = visualization as Visualization<unknown, unknown>;
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/lens/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export interface Visualization<T = unknown, P = unknown> {
// Given the current state, which parts should be saved?
getPersistableState: (state: T) => P;

renderConfigPanel: (props: VisualizationProps<T>) => void;
renderConfigPanel: (domElement: Element, props: VisualizationProps<T>) => void;

toExpression: (state: T, datasource: DatasourcePublicAPI) => string;

Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/lens/public/xy_visualization_plugin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export * from './plugin';
22 changes: 22 additions & 0 deletions x-pack/plugins/lens/public/xy_visualization_plugin/plugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { xyVisualization } from './xy_visualization';

class XyVisualizationPlugin {
constructor() {}

setup() {
return xyVisualization;
}

stop() {}
}

const plugin = new XyVisualizationPlugin();

export const xyVisualizationSetup = () => plugin.setup();
export const xyVisualizationStop = () => plugin.stop();
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { xyVisualization, XyVisualizationPersistedState } from './xy_visualization';

describe('IndexPattern Data Source', () => {
let persistedState: XyVisualizationPersistedState;

beforeEach(() => {
persistedState = {
roles: [],
};
});

describe('#initialize', () => {
it('loads default state', () => {
expect(xyVisualization.initialize()).toEqual({
roles: [],
});
});

it('loads from persisted state', () => {
expect(xyVisualization.initialize(persistedState)).toEqual({
roles: [],
});
});
});

describe('#getPersistableState', () => {
it('persists the state as given', () => {
expect(
xyVisualization.getPersistableState({
roles: [],
})
).toEqual({
roles: [],
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { render } from 'react-dom';
import { Visualization, DimensionRole } from '../types';

export interface XyVisualizationState {
roles: DimensionRole[];
}

export type XyVisualizationPersistedState = XyVisualizationState;

export const xyVisualization: Visualization<XyVisualizationState, XyVisualizationPersistedState> = {
initialize() {
return {
roles: [],
};
},

getPersistableState(state) {
return state;
},

renderConfigPanel: (domElement, props) => {
render(<div>XY Visualization</div>, domElement);
},

getSuggestions: options => [],

getMappingOfTableToRoles: (state, datasource) => [],

toExpression: state => '',
};