Skip to content

Commit

Permalink
[lens] Editor frame initializes datasources and visualizations (#36060)
Browse files Browse the repository at this point in the history
* [lens] Editor frame initializes datasources and visualizations

* Respond to review comments

* Fix build issues

* Fix state management issue
  • Loading branch information
wylieconlon authored May 9, 2019
1 parent 38ce5a4 commit c7e0704
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 20 deletions.
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;
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 => '',
};

0 comments on commit c7e0704

Please sign in to comment.