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] Basic save / load #39257

Merged
merged 25 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
315da75
Add basic routing, save and load to lens
chrisdavies Jun 18, 2019
4a27cb5
Move title editing to editor frame
chrisdavies Jun 19, 2019
5e88843
Allow undefined documents in Lens initialize
chrisdavies Jun 19, 2019
7027fbd
Add file lost in rebase
chrisdavies Jun 21, 2019
8e05f3f
Remove accidental leftover file
chrisdavies Jun 24, 2019
f78e9d0
Rename lensState to state
chrisdavies Jun 24, 2019
be86b07
Address PR feedback, remove Lens
chrisdavies Jun 24, 2019
df3967b
Merge branch 'feature/lens' of github.com:elastic/kibana into lens/ba…
chrisdavies Jun 24, 2019
4a9280e
Add lens documents to the saved object management system
chrisdavies Jun 24, 2019
7d4eb89
Merge branch 'lens/basic-save-load' of github.com:chrisdavies/kibana …
chrisdavies Jun 24, 2019
043bf65
Merge branch 'feature/lens' of github.com:elastic/kibana into lens/ba…
chrisdavies Jun 24, 2019
467efba
Fix chart title localization key
chrisdavies Jun 24, 2019
a80d2d1
Ensure datasource and visualization are always
chrisdavies Jun 25, 2019
6a89956
Add error handling, more tests, tweaked names
chrisdavies Jun 26, 2019
e8c3fb0
Merge branch 'feature/lens' of github.com:elastic/kibana into lens/ba…
chrisdavies Jun 26, 2019
1433675
Fix TypeScript errors
chrisdavies Jun 26, 2019
0a58e2d
Remove commented code
chrisdavies Jun 26, 2019
9a286dc
Remove randomization from tests
chrisdavies Jun 26, 2019
bba3d80
Use global toast notification system
chrisdavies Jun 27, 2019
8d7741c
Change saving status actions
chrisdavies Jun 27, 2019
90ce425
Merge upstream
chrisdavies Jun 27, 2019
3a68367
Fix typescript errors
chrisdavies Jun 27, 2019
6ba71ab
Merge upstream
chrisdavies Jun 28, 2019
0007bd1
Improve styling a bit
chrisdavies Jun 28, 2019
e4351de
Fix typescript errors
chrisdavies Jun 28, 2019
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
18 changes: 17 additions & 1 deletion x-pack/legacy/plugins/lens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as Joi from 'joi';
import { Server } from 'hapi';
import { resolve } from 'path';
import { LegacyPluginInitializer } from 'src/legacy/types';
import mappings from './mappings.json';

import { PLUGIN_ID } from './common';

Expand All @@ -27,7 +28,22 @@ export const lens: LegacyPluginInitializer = kibana => {
main: `plugins/${PLUGIN_ID}/index`,
},
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
},
mappings,
savedObjectsManagement: {
lens: {
defaultSearchField: 'title',
isImportableAndExportable: true,
getTitle: (obj: { attributes: { title: string } }) => obj.attributes.title,
getInAppUrl: (obj: { id: string }) => ({
path: `/app/lens#/edit/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'lens.show',
}),
},
},
// TODO: savedObjectsManagement is not in the uiExports type definition,
// so, we have to either fix the type signature and deal with merge
// conflicts, or simply cas to any here, and fix this later.
} as any,

config: () => {
return Joi.object({
Expand Down
18 changes: 18 additions & 0 deletions x-pack/legacy/plugins/lens/mappings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"lens": {
"properties": {
"title": {
"type": "text"
},
"visualizationType": {
"type": "keyword"
},
"datasourceType": {
"type": "keyword"
},
"state": {
"type": "text"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ describe('editor_frame', () => {

let expressionRendererMock: ExpressionRenderer;

const defaultProps = {
store: {
save: jest.fn(),
load: jest.fn(),
},
redirectTo: jest.fn(),
};

beforeEach(() => {
mockVisualization = createMockVisualization();
mockVisualization2 = createMockVisualization();
Expand All @@ -57,6 +65,7 @@ describe('editor_frame', () => {
act(() => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand All @@ -77,6 +86,7 @@ describe('editor_frame', () => {
act(() => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand All @@ -98,6 +108,7 @@ describe('editor_frame', () => {
act(() => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand All @@ -119,6 +130,7 @@ describe('editor_frame', () => {
act(() => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand All @@ -143,6 +155,7 @@ describe('editor_frame', () => {
act(() => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand Down Expand Up @@ -170,6 +183,7 @@ describe('editor_frame', () => {
act(() => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand Down Expand Up @@ -203,6 +217,7 @@ describe('editor_frame', () => {

mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: { ...mockVisualization, initialize: () => initialState },
}}
Expand All @@ -229,6 +244,7 @@ describe('editor_frame', () => {
it('should render the resulting expression using the expression renderer', async () => {
const instance = mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: { ...mockVisualization, toExpression: () => 'vis' },
}}
Expand Down Expand Up @@ -272,6 +288,7 @@ Object {
it('should re-render config panel after state update', async () => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand Down Expand Up @@ -305,6 +322,7 @@ Object {
it('should re-render data panel after state update', async () => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand Down Expand Up @@ -338,6 +356,7 @@ Object {
it('should re-render config panel with updated datasource api after datasource state update', async () => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand Down Expand Up @@ -377,6 +396,7 @@ Object {
it('should pass the datasource api to the visualization', async () => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand All @@ -403,6 +423,7 @@ Object {

mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand All @@ -426,6 +447,7 @@ Object {
it('should re-create the public api after state has been set', async () => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand Down Expand Up @@ -459,6 +481,7 @@ Object {
beforeEach(async () => {
instance = mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
testVis2: mockVisualization2,
Expand Down Expand Up @@ -541,6 +564,7 @@ Object {
it('should fetch suggestions of currently active datasource', async () => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
}}
Expand All @@ -563,6 +587,7 @@ Object {
it('should fetch suggestions of all visualizations', async () => {
mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: mockVisualization,
testVis2: mockVisualization2,
Expand All @@ -586,6 +611,7 @@ Object {
it('should display suggestions in descending order', async () => {
const instance = mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: {
...mockVisualization,
Expand Down Expand Up @@ -652,6 +678,7 @@ Object {
const suggestionVisState = {};
const instance = mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: {
...mockVisualization,
Expand Down Expand Up @@ -706,6 +733,7 @@ Object {
const suggestionVisState = {};
const instance = mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: {
...mockVisualization,
Expand Down Expand Up @@ -760,6 +788,7 @@ Object {
const suggestionVisState = {};
const instance = mount(
<EditorFrame
{...defaultProps}
visualizationMap={{
testVis: {
...mockVisualization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*/

import React, { useEffect, useReducer, useMemo } from 'react';
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/data/public';
import { Datasource, Visualization } from '../../types';
import { reducer, getInitialState } from './state_management';
Expand All @@ -13,14 +15,18 @@ import { ConfigPanelWrapper } from './config_panel_wrapper';
import { FrameLayout } from './frame_layout';
import { SuggestionPanel } from './suggestion_panel';
import { WorkspacePanel } from './workspace_panel';
import { SavedObjectStore, Document } from '../../persistence/saved_object_store';
import { save } from './save';
import { WorkspacePanelWrapper } from './workspace_panel_wrapper';

export interface EditorFrameProps {
doc?: Document;
store: SavedObjectStore;
datasourceMap: Record<string, Datasource>;
visualizationMap: Record<string, Visualization>;

redirectTo: (path: string) => void;
initialDatasourceId: string | null;
initialVisualizationId: string | null;

ExpressionRenderer: ExpressionRenderer;
}

Expand Down Expand Up @@ -50,26 +56,46 @@ export function EditorFrame(props: EditorFrameProps) {
]
);

useEffect(
() => {
if (props.doc) {
dispatch({
type: 'VISUALIZATION_LOADED',
doc: props.doc,
});
} else {
dispatch({
type: 'RESET',
state: getInitialState(props),
});
}
},
[props.doc]
);

// Initialize current datasource
useEffect(
() => {
let datasourceGotSwitched = false;
if (state.datasource.isLoading && state.datasource.activeId) {
props.datasourceMap[state.datasource.activeId].initialize().then(datasourceState => {
if (!datasourceGotSwitched) {
dispatch({
type: 'UPDATE_DATASOURCE_STATE',
newState: datasourceState,
});
}
});
// TODO: .catch / error handling
props.datasourceMap[state.datasource.activeId]
.initialize(props.doc && props.doc.state.datasource)
.then(datasourceState => {
if (!datasourceGotSwitched) {
dispatch({
type: 'UPDATE_DATASOURCE_STATE',
newState: datasourceState,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like you're trying to prevent a race condition here, right?

});

return () => {
datasourceGotSwitched = true;
};
}
},
[state.datasource.activeId, state.datasource.isLoading]
[props.doc, state.datasource.activeId, state.datasource.isLoading]
);

// Initialize visualization as soon as datasource is ready
Expand All @@ -92,9 +118,40 @@ export function EditorFrame(props: EditorFrameProps) {
[datasourcePublicAPI, state.visualization.activeId, state.visualization.state]
);

if (state.datasource.activeId && !state.datasource.isLoading) {
const datasource =
state.datasource.activeId && !state.datasource.isLoading
? props.datasourceMap[state.datasource.activeId]
: undefined;

const visualization = state.visualization.activeId
? props.visualizationMap[state.visualization.activeId]
: undefined;

if (datasource) {
return (
<FrameLayout
navPanel={
<nav>
<EuiButton
fill
onClick={() =>
save({
datasource,
dispatch,
visualization,
state,
redirectTo: props.redirectTo,
store: props.store,
})
}
disabled={state.saving || !state.datasource.activeId || !state.visualization.activeId}
>
{i18n.translate('xpack.lens.editorFrame.Save', {
defaultMessage: 'Save',
})}
</EuiButton>
</nav>
}
dataPanel={
<DataPanelWrapper
datasourceMap={props.datasourceMap}
Expand All @@ -114,20 +171,22 @@ export function EditorFrame(props: EditorFrameProps) {
/>
}
workspacePanel={
<WorkspacePanel
activeDatasource={props.datasourceMap[state.datasource.activeId]}
activeVisualizationId={state.visualization.activeId}
datasourcePublicAPI={datasourcePublicAPI!}
datasourceState={state.datasource.state}
visualizationState={state.visualization.state}
visualizationMap={props.visualizationMap}
dispatch={dispatch}
ExpressionRenderer={props.ExpressionRenderer}
/>
<WorkspacePanelWrapper title={state.title} dispatch={dispatch}>
<WorkspacePanel
activeDatasource={datasource}
activeVisualizationId={state.visualization.activeId}
datasourcePublicAPI={datasourcePublicAPI!}
datasourceState={state.datasource.state}
visualizationState={state.visualization.state}
visualizationMap={props.visualizationMap}
dispatch={dispatch}
ExpressionRenderer={props.ExpressionRenderer}
/>
</WorkspacePanelWrapper>
}
suggestionsPanel={
<SuggestionPanel
activeDatasource={props.datasourceMap[state.datasource.activeId]}
activeDatasource={datasource}
activeVisualizationId={state.visualization.activeId}
datasourceState={state.datasource.state}
visualizationState={state.visualization.state}
Expand Down
Loading