[Lens] Add toolbar api (#69263)
This commit is contained in:
parent
eedc86fbe3
commit
67e48527e7
|
@ -10,11 +10,14 @@
|
|||
.lnsWorkspacePanelWrapper__pageContentHeader {
|
||||
@include euiTitle('xs');
|
||||
padding: $euiSizeM;
|
||||
border-bottom: $euiBorderThin;
|
||||
// override EuiPage
|
||||
margin-bottom: 0 !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
|
||||
.lnsWorkspacePanelWrapper__pageContentHeader--unsaved {
|
||||
color: $euiTextSubduedColor;
|
||||
}
|
||||
|
||||
.lnsWorkspacePanelWrapper__pageContentBody {
|
||||
@include euiScrollBar;
|
||||
flex-grow: 1;
|
||||
|
|
|
@ -23,7 +23,6 @@ import { WorkspacePanel } from './workspace_panel';
|
|||
import { Document } from '../../persistence/saved_object_store';
|
||||
import { RootDragDropProvider } from '../../drag_drop';
|
||||
import { getSavedObjectFormat } from './save';
|
||||
import { WorkspacePanelWrapper } from './workspace_panel_wrapper';
|
||||
import { generateId } from '../../id_generator';
|
||||
import { Filter, Query, SavedQuery } from '../../../../../../src/plugins/data/public';
|
||||
import { EditorFrameStartPlugins } from '../service';
|
||||
|
@ -275,21 +274,20 @@ export function EditorFrame(props: EditorFrameProps) {
|
|||
}
|
||||
workspacePanel={
|
||||
allLoaded && (
|
||||
<WorkspacePanelWrapper title={state.title}>
|
||||
<WorkspacePanel
|
||||
activeDatasourceId={state.activeDatasourceId}
|
||||
activeVisualizationId={state.visualization.activeId}
|
||||
datasourceMap={props.datasourceMap}
|
||||
datasourceStates={state.datasourceStates}
|
||||
framePublicAPI={framePublicAPI}
|
||||
visualizationState={state.visualization.state}
|
||||
visualizationMap={props.visualizationMap}
|
||||
dispatch={dispatch}
|
||||
ExpressionRenderer={props.ExpressionRenderer}
|
||||
core={props.core}
|
||||
plugins={props.plugins}
|
||||
/>
|
||||
</WorkspacePanelWrapper>
|
||||
<WorkspacePanel
|
||||
title={state.title}
|
||||
activeDatasourceId={state.activeDatasourceId}
|
||||
activeVisualizationId={state.visualization.activeId}
|
||||
datasourceMap={props.datasourceMap}
|
||||
datasourceStates={state.datasourceStates}
|
||||
framePublicAPI={framePublicAPI}
|
||||
visualizationState={state.visualization.state}
|
||||
visualizationMap={props.visualizationMap}
|
||||
dispatch={dispatch}
|
||||
ExpressionRenderer={props.ExpressionRenderer}
|
||||
core={props.core}
|
||||
plugins={props.plugins}
|
||||
/>
|
||||
)
|
||||
}
|
||||
suggestionsPanel={
|
||||
|
|
|
@ -37,6 +37,7 @@ import { trackUiEvent } from '../../lens_ui_telemetry';
|
|||
import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
|
||||
import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public';
|
||||
import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';
|
||||
import { WorkspacePanelWrapper } from './workspace_panel_wrapper';
|
||||
|
||||
export interface WorkspacePanelProps {
|
||||
activeVisualizationId: string | null;
|
||||
|
@ -56,6 +57,7 @@ export interface WorkspacePanelProps {
|
|||
ExpressionRenderer: ReactExpressionRendererType;
|
||||
core: CoreStart | CoreSetup;
|
||||
plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart };
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const WorkspacePanel = debouncedComponent(InnerWorkspacePanel);
|
||||
|
@ -73,6 +75,7 @@ export function InnerWorkspacePanel({
|
|||
core,
|
||||
plugins,
|
||||
ExpressionRenderer: ExpressionRendererComponent,
|
||||
title,
|
||||
}: WorkspacePanelProps) {
|
||||
const IS_DARK_THEME = core.uiSettings.get('theme:darkMode');
|
||||
const emptyStateGraphicURL = IS_DARK_THEME
|
||||
|
@ -291,13 +294,22 @@ export function InnerWorkspacePanel({
|
|||
}
|
||||
|
||||
return (
|
||||
<DragDrop
|
||||
data-test-subj="lnsWorkspace"
|
||||
draggable={false}
|
||||
droppable={Boolean(suggestionForDraggedField)}
|
||||
onDrop={onDrop}
|
||||
<WorkspacePanelWrapper
|
||||
title={title}
|
||||
framePublicAPI={framePublicAPI}
|
||||
dispatch={dispatch}
|
||||
emptyExpression={expression === null}
|
||||
visualizationState={visualizationState}
|
||||
activeVisualization={activeVisualization}
|
||||
>
|
||||
{renderVisualization()}
|
||||
</DragDrop>
|
||||
<DragDrop
|
||||
data-test-subj="lnsWorkspace"
|
||||
draggable={false}
|
||||
droppable={Boolean(suggestionForDraggedField)}
|
||||
onDrop={onDrop}
|
||||
>
|
||||
{renderVisualization()}
|
||||
</DragDrop>
|
||||
</WorkspacePanelWrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { Visualization } from '../../types';
|
||||
import { createMockVisualization, createMockFramePublicAPI, FrameMock } from '../mocks';
|
||||
import { mountWithIntl as mount } from 'test_utils/enzyme_helpers';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { WorkspacePanelWrapper, WorkspacePanelWrapperProps } from './workspace_panel_wrapper';
|
||||
|
||||
describe('workspace_panel_wrapper', () => {
|
||||
let mockVisualization: jest.Mocked<Visualization>;
|
||||
let mockFrameAPI: FrameMock;
|
||||
let instance: ReactWrapper<WorkspacePanelWrapperProps>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockVisualization = createMockVisualization();
|
||||
mockFrameAPI = createMockFramePublicAPI();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.unmount();
|
||||
});
|
||||
|
||||
it('should render its children', () => {
|
||||
const MyChild = () => <span>The child elements</span>;
|
||||
instance = mount(
|
||||
<WorkspacePanelWrapper
|
||||
dispatch={jest.fn()}
|
||||
framePublicAPI={mockFrameAPI}
|
||||
visualizationState={{}}
|
||||
activeVisualization={mockVisualization}
|
||||
emptyExpression={false}
|
||||
>
|
||||
<MyChild />
|
||||
</WorkspacePanelWrapper>
|
||||
);
|
||||
|
||||
expect(instance.find(MyChild)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should call the toolbar renderer if provided', () => {
|
||||
const renderToolbarMock = jest.fn();
|
||||
const visState = { internalState: 123 };
|
||||
instance = mount(
|
||||
<WorkspacePanelWrapper
|
||||
dispatch={jest.fn()}
|
||||
framePublicAPI={mockFrameAPI}
|
||||
visualizationState={visState}
|
||||
children={<span />}
|
||||
activeVisualization={{ ...mockVisualization, renderToolbar: renderToolbarMock }}
|
||||
emptyExpression={false}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(renderToolbarMock).toHaveBeenCalledWith(expect.any(Element), {
|
||||
state: visState,
|
||||
frame: mockFrameAPI,
|
||||
setState: expect.anything(),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,25 +4,86 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiPageContent, EuiPageContentHeader, EuiPageContentBody } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
EuiPageContent,
|
||||
EuiPageContentBody,
|
||||
EuiPageContentHeader,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { FramePublicAPI, Visualization } from '../../types';
|
||||
import { NativeRenderer } from '../../native_renderer';
|
||||
import { Action } from './state_management';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
export interface WorkspacePanelWrapperProps {
|
||||
children: React.ReactNode | React.ReactNode[];
|
||||
framePublicAPI: FramePublicAPI;
|
||||
visualizationState: unknown;
|
||||
activeVisualization: Visualization | null;
|
||||
dispatch: (action: Action) => void;
|
||||
emptyExpression: boolean;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function WorkspacePanelWrapper({ children, title }: Props) {
|
||||
export function WorkspacePanelWrapper({
|
||||
children,
|
||||
framePublicAPI,
|
||||
visualizationState,
|
||||
activeVisualization,
|
||||
dispatch,
|
||||
title,
|
||||
emptyExpression,
|
||||
}: WorkspacePanelWrapperProps) {
|
||||
const setVisualizationState = useCallback(
|
||||
(newState: unknown) => {
|
||||
if (!activeVisualization) {
|
||||
return;
|
||||
}
|
||||
dispatch({
|
||||
type: 'UPDATE_VISUALIZATION_STATE',
|
||||
visualizationId: activeVisualization.id,
|
||||
newState,
|
||||
clearStagedPreview: false,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
return (
|
||||
<EuiPageContent className="lnsWorkspacePanelWrapper">
|
||||
{title && (
|
||||
<EuiPageContentHeader className="lnsWorkspacePanelWrapper__pageContentHeader">
|
||||
<span data-test-subj="lns_ChartTitle">{title}</span>
|
||||
</EuiPageContentHeader>
|
||||
<EuiFlexGroup gutterSize="s" direction="column" alignItems="stretch">
|
||||
{activeVisualization && activeVisualization.renderToolbar && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderToolbar}
|
||||
nativeProps={{
|
||||
frame: framePublicAPI,
|
||||
state: visualizationState,
|
||||
setState: setVisualizationState,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiPageContentBody className="lnsWorkspacePanelWrapper__pageContentBody">
|
||||
{children}
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
<EuiFlexItem>
|
||||
<EuiPageContent className="lnsWorkspacePanelWrapper">
|
||||
{(!emptyExpression || title) && (
|
||||
<EuiPageContentHeader
|
||||
className={classNames('lnsWorkspacePanelWrapper__pageContentHeader', {
|
||||
'lnsWorkspacePanelWrapper__pageContentHeader--unsaved': !title,
|
||||
})}
|
||||
>
|
||||
<span data-test-subj="lns_ChartTitle">
|
||||
{title ||
|
||||
i18n.translate('xpack.lens.chartTitle.unsaved', { defaultMessage: 'Unsaved' })}
|
||||
</span>
|
||||
</EuiPageContentHeader>
|
||||
)}
|
||||
<EuiPageContentBody className="lnsWorkspacePanelWrapper__pageContentBody">
|
||||
{children}
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -290,6 +290,12 @@ export type VisualizationLayerWidgetProps<T = unknown> = VisualizationConfigProp
|
|||
setState: (newState: T) => void;
|
||||
};
|
||||
|
||||
export interface VisualizationToolbarProps<T = unknown> {
|
||||
setState: (newState: T) => void;
|
||||
frame: FramePublicAPI;
|
||||
state: T;
|
||||
}
|
||||
|
||||
export type VisualizationDimensionEditorProps<T = unknown> = VisualizationConfigProps<T> & {
|
||||
groupId: string;
|
||||
accessor: string;
|
||||
|
@ -454,6 +460,11 @@ export interface Visualization<T = unknown, P = unknown> {
|
|||
* for extra configurability, such as for styling the legend or axis
|
||||
*/
|
||||
renderLayerContextMenu?: (domElement: Element, props: VisualizationLayerWidgetProps<T>) => void;
|
||||
/**
|
||||
* Toolbar rendered above the visualization. This is meant to be used to provide chart-level
|
||||
* settings for the visualization.
|
||||
*/
|
||||
renderToolbar?: (domElement: Element, props: VisualizationToolbarProps<T>) => void;
|
||||
/**
|
||||
* Visualizations can provide a custom icon which will open a layer-specific popover
|
||||
* If no icon is provided, gear icon is default
|
||||
|
|
Loading…
Reference in a new issue