mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
typescript more dashboard panel code (#21810)
* typescript more dashboard panel code * use ? instead of | undefined
This commit is contained in:
parent
222dd91823
commit
62f35df89b
22 changed files with 298 additions and 147 deletions
|
@ -241,6 +241,7 @@
|
|||
"@types/prop-types": "^15.5.3",
|
||||
"@types/react": "^16.3.14",
|
||||
"@types/react-dom": "^16.0.5",
|
||||
"@types/react-redux": "^5.0.6",
|
||||
"@types/redux": "^3.6.31",
|
||||
"@types/redux-actions": "^2.2.1",
|
||||
"@types/sinon": "^5.0.0",
|
||||
|
|
|
@ -44,7 +44,7 @@ export interface ResetPanelTitleAction
|
|||
|
||||
export interface SetPanelTitleActionPayload {
|
||||
panelId: PanelId;
|
||||
title: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface SetPanelTitleAction
|
||||
|
|
|
@ -34,6 +34,7 @@ export enum ViewActionTypeKeys {
|
|||
UPDATE_TIME_RANGE = 'UPDATE_TIME_RANGE',
|
||||
UPDATE_FILTERS = 'UPDATE_FILTERS',
|
||||
UPDATE_QUERY = 'UPDATE_QUERY',
|
||||
CLOSE_CONTEXT_MENU = 'CLOSE_CONTEXT_MENU',
|
||||
}
|
||||
|
||||
export interface UpdateViewModeAction
|
||||
|
@ -42,6 +43,9 @@ export interface UpdateViewModeAction
|
|||
export interface SetVisibleContextMenuPanelIdAction
|
||||
extends KibanaAction<ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID, PanelId> {}
|
||||
|
||||
export interface CloseContextMenuAction
|
||||
extends KibanaAction<ViewActionTypeKeys.CLOSE_CONTEXT_MENU, undefined> {}
|
||||
|
||||
export interface MaximizePanelAction
|
||||
extends KibanaAction<ViewActionTypeKeys.MAXIMIZE_PANEl, PanelId> {}
|
||||
|
||||
|
@ -68,6 +72,7 @@ export interface UpdateQueryAction extends KibanaAction<ViewActionTypeKeys.UPDAT
|
|||
export type ViewActions =
|
||||
| UpdateViewModeAction
|
||||
| SetVisibleContextMenuPanelIdAction
|
||||
| CloseContextMenuAction
|
||||
| MaximizePanelAction
|
||||
| MinimizePanelAction
|
||||
| UpdateIsFullScreenModeAction
|
||||
|
@ -78,6 +83,7 @@ export type ViewActions =
|
|||
| UpdateQueryAction;
|
||||
|
||||
export const updateViewMode = createAction<string>(ViewActionTypeKeys.UPDATE_VIEW_MODE);
|
||||
export const closeContextMenu = createAction(ViewActionTypeKeys.CLOSE_CONTEXT_MENU);
|
||||
export const setVisibleContextMenuPanelId = createAction<PanelId>(
|
||||
ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID
|
||||
);
|
||||
|
|
|
@ -17,14 +17,18 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
|
||||
import _ from 'lodash';
|
||||
import { DashboardContextMenuPanel, DashboardPanelAction } from 'ui/dashboard_panel_actions';
|
||||
import { ContainerState, Embeddable } from 'ui/embeddable';
|
||||
import { PanelId } from '../../../selectors';
|
||||
|
||||
/**
|
||||
* Loops through allActions and extracts those that belong on the given contextMenuPanelId
|
||||
* @param {string} contextMenuPanelId
|
||||
* @param {Array.<DashboardPanelAction>} allActions
|
||||
*/
|
||||
function getActionsForPanel(contextMenuPanelId, allActions) {
|
||||
function getActionsForPanel(contextMenuPanelId: PanelId, allActions: DashboardPanelAction[]) {
|
||||
return allActions.filter(action => action.parentPanelId === contextMenuPanelId);
|
||||
}
|
||||
|
||||
|
@ -34,15 +38,25 @@ function getActionsForPanel(contextMenuPanelId, allActions) {
|
|||
* @param {Embeddable} embeddable
|
||||
* @param {ContainerState} containerState
|
||||
* @return {{
|
||||
* Array.<EuiContextMenuPanelItemShape> items - panel actions converted into the items expected to be on an
|
||||
* Array.<EuiContextMenuPanelItemDescriptor> items - panel actions converted into the items expected to be on an
|
||||
* EuiContextMenuPanel,
|
||||
* Array.<EuiContextMenuPanelShape> childPanels - extracted child panels, if any actions also open a panel. They
|
||||
* Array.<EuiContextMenuPanelDescriptor> childPanels - extracted child panels, if any actions also open a panel. They
|
||||
* need to be moved to the top level for EUI.
|
||||
* }}
|
||||
*/
|
||||
function buildEuiContextMenuPanelItemsAndChildPanels({ contextMenuPanelId, actions, embeddable, containerState }) {
|
||||
const items = [];
|
||||
const childPanels = [];
|
||||
function buildEuiContextMenuPanelItemsAndChildPanels({
|
||||
contextMenuPanelId,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState,
|
||||
}: {
|
||||
contextMenuPanelId: PanelId;
|
||||
actions: DashboardPanelAction[];
|
||||
embeddable?: Embeddable;
|
||||
containerState: ContainerState;
|
||||
}) {
|
||||
const items: EuiContextMenuPanelItemDescriptor[] = [];
|
||||
const childPanels: EuiContextMenuPanelDescriptor[] = [];
|
||||
const actionsForPanel = getActionsForPanel(contextMenuPanelId, actions);
|
||||
actionsForPanel.forEach(action => {
|
||||
const isVisible = action.isVisible({ embeddable, containerState });
|
||||
|
@ -56,16 +70,18 @@ function buildEuiContextMenuPanelItemsAndChildPanels({ contextMenuPanelId, actio
|
|||
contextMenuPanel: action.childContextMenuPanel,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState
|
||||
}));
|
||||
containerState,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
items.push(convertPanelActionToContextMenuItem(
|
||||
{
|
||||
items.push(
|
||||
convertPanelActionToContextMenuItem({
|
||||
action,
|
||||
containerState,
|
||||
embeddable
|
||||
}));
|
||||
embeddable,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return { items, childPanels };
|
||||
|
@ -78,16 +94,20 @@ function buildEuiContextMenuPanelItemsAndChildPanels({ contextMenuPanelId, actio
|
|||
* @param {Array.<DashboardPanelAction>} actions to build the context menu with
|
||||
* @param {Embeddable} embeddable
|
||||
* @param {ContainerState} containerState
|
||||
* @return {Object} An object that conforms to EuiContextMenuPanelShape in elastic/eui
|
||||
* @return {EuiContextMenuPanelDescriptor[]} An array of context menu panels to be used in the eui react component.
|
||||
*/
|
||||
export function buildEuiContextMenuPanels(
|
||||
{
|
||||
contextMenuPanel,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState
|
||||
}) {
|
||||
const euiContextMenuPanel = {
|
||||
export function buildEuiContextMenuPanels({
|
||||
contextMenuPanel,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState,
|
||||
}: {
|
||||
contextMenuPanel: DashboardContextMenuPanel;
|
||||
actions: DashboardPanelAction[];
|
||||
embeddable?: Embeddable;
|
||||
containerState: ContainerState;
|
||||
}): EuiContextMenuPanelDescriptor[] {
|
||||
const euiContextMenuPanel: EuiContextMenuPanelDescriptor = {
|
||||
id: contextMenuPanel.id,
|
||||
title: contextMenuPanel.title,
|
||||
items: [],
|
||||
|
@ -95,13 +115,12 @@ export function buildEuiContextMenuPanels(
|
|||
};
|
||||
const contextMenuPanels = [euiContextMenuPanel];
|
||||
|
||||
const { items, childPanels } =
|
||||
buildEuiContextMenuPanelItemsAndChildPanels({
|
||||
contextMenuPanelId: contextMenuPanel.id,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState
|
||||
});
|
||||
const { items, childPanels } = buildEuiContextMenuPanelItemsAndChildPanels({
|
||||
contextMenuPanelId: contextMenuPanel.id,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState,
|
||||
});
|
||||
|
||||
euiContextMenuPanel.items = items;
|
||||
return contextMenuPanels.concat(childPanels);
|
||||
|
@ -112,11 +131,18 @@ export function buildEuiContextMenuPanels(
|
|||
* @param {DashboardPanelAction} action
|
||||
* @param {ContainerState} containerState
|
||||
* @param {Embeddable} embeddable
|
||||
* @return {Object} See EuiContextMenuPanelItemShape in @elastic/eui
|
||||
* @return {EuiContextMenuPanelItemDescriptor}
|
||||
*/
|
||||
function convertPanelActionToContextMenuItem({ action, containerState, embeddable }) {
|
||||
function convertPanelActionToContextMenuItem({
|
||||
action,
|
||||
containerState,
|
||||
embeddable,
|
||||
}: {
|
||||
action: DashboardPanelAction;
|
||||
containerState: ContainerState;
|
||||
embeddable?: Embeddable;
|
||||
}): EuiContextMenuPanelItemDescriptor {
|
||||
return {
|
||||
id: action.id || action.displayName.replace(/\s/g, ''),
|
||||
name: action.displayName,
|
||||
icon: action.icon,
|
||||
panel: _.get(action, 'childContextMenuPanel.id'),
|
||||
|
@ -125,4 +151,3 @@ function convertPanelActionToContextMenuItem({ action, containerState, embeddabl
|
|||
'data-test-subj': `dashboardPanelAction-${action.id}`,
|
||||
};
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ export function getCustomizePanelAction({
|
|||
title,
|
||||
}: {
|
||||
onResetPanelTitle: () => void;
|
||||
onUpdatePanelTitle: (title: string | undefined) => void;
|
||||
onUpdatePanelTitle: (title: string) => void;
|
||||
closeContextMenu: () => void;
|
||||
title?: string;
|
||||
}): DashboardPanelAction {
|
||||
|
|
|
@ -41,7 +41,7 @@ export function getEditPanelAction() {
|
|||
!embeddable || !embeddable.metadata || !embeddable.metadata.editUrl,
|
||||
isVisible: ({ containerState }) => containerState.viewMode === DashboardViewMode.EDIT,
|
||||
onClick: ({ embeddable }) => {
|
||||
if (embeddable.metadata.editUrl) {
|
||||
if (embeddable && embeddable.metadata.editUrl) {
|
||||
window.location.href = embeddable.metadata.editUrl;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -46,9 +46,16 @@ export function getInspectorPanelAction({
|
|||
},
|
||||
{
|
||||
icon: <EuiIcon type="inspect" />,
|
||||
isVisible: ({ embeddable }) =>
|
||||
embeddable && Inspector.isAvailable(embeddable.getInspectorAdapters()),
|
||||
isVisible: ({ embeddable }) => {
|
||||
if (!embeddable) {
|
||||
return false;
|
||||
}
|
||||
return Inspector.isAvailable(embeddable.getInspectorAdapters());
|
||||
},
|
||||
onClick: ({ embeddable }) => {
|
||||
if (!embeddable) {
|
||||
return;
|
||||
}
|
||||
closeContextMenu();
|
||||
const adapters = embeddable.getInspectorAdapters();
|
||||
if (!adapters) {
|
||||
|
|
|
@ -18,11 +18,25 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { embeddableShape } from 'ui/embeddable';
|
||||
import { Embeddable } from 'ui/embeddable';
|
||||
import { PanelId } from '../../selectors';
|
||||
import { PanelOptionsMenuContainer } from './panel_options_menu_container';
|
||||
|
||||
export function PanelHeader({ title, panelId, embeddable, isViewOnlyMode, hidePanelTitles }) {
|
||||
export interface PanelHeaderProps {
|
||||
title?: string;
|
||||
panelId: PanelId;
|
||||
embeddable?: Embeddable;
|
||||
isViewOnlyMode: boolean;
|
||||
hidePanelTitles: boolean;
|
||||
}
|
||||
|
||||
export function PanelHeader({
|
||||
title,
|
||||
panelId,
|
||||
embeddable,
|
||||
isViewOnlyMode,
|
||||
hidePanelTitles,
|
||||
}: PanelHeaderProps) {
|
||||
if (isViewOnlyMode && (!title || hidePanelTitles)) {
|
||||
return (
|
||||
<div className="panel-heading-floater">
|
||||
|
@ -34,7 +48,10 @@ export function PanelHeader({ title, panelId, embeddable, isViewOnlyMode, hidePa
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="panel-heading" data-test-subj={`dashboardPanelHeading-${title.replace(/\s/g, '')}`}>
|
||||
<div
|
||||
className="panel-heading"
|
||||
data-test-subj={`dashboardPanelHeading-${(title || '').replace(/\s/g, '')}`}
|
||||
>
|
||||
<span
|
||||
data-test-subj="dashboardPanelTitle"
|
||||
className="panel-title"
|
||||
|
@ -50,11 +67,3 @@ export function PanelHeader({ title, panelId, embeddable, isViewOnlyMode, hidePa
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PanelHeader.propTypes = {
|
||||
isViewOnlyMode: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
hidePanelTitles: PropTypes.bool.isRequired,
|
||||
embeddable: embeddableShape,
|
||||
panelId: PropTypes.string.isRequired,
|
||||
};
|
|
@ -17,37 +17,57 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import _ from 'lodash';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { PanelHeaderContainer } from './panel_header_container';
|
||||
import { DashboardViewMode } from '../../dashboard_view_mode';
|
||||
import { store } from '../../../store';
|
||||
import {
|
||||
updateViewMode,
|
||||
setPanels,
|
||||
setPanelTitle,
|
||||
resetPanelTitle,
|
||||
embeddableIsInitialized,
|
||||
updateTimeRange,
|
||||
} from '../../actions';
|
||||
// TODO: remove this when EUI supports types for this.
|
||||
// @ts-ignore: implicit any for JS file
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
function getProps(props = {}) {
|
||||
import { store } from '../../../store';
|
||||
import {
|
||||
embeddableIsInitialized,
|
||||
resetPanelTitle,
|
||||
setPanels,
|
||||
setPanelTitle,
|
||||
updateTimeRange,
|
||||
updateViewMode,
|
||||
} from '../../actions';
|
||||
import { DashboardViewMode } from '../../dashboard_view_mode';
|
||||
import { PanelHeaderContainer, PanelHeaderContainerOwnProps } from './panel_header_container';
|
||||
|
||||
function getProps(props = {}): PanelHeaderContainerOwnProps {
|
||||
const defaultTestProps = {
|
||||
panelId: 'foo1',
|
||||
};
|
||||
return _.defaultsDeep(props, defaultTestProps);
|
||||
}
|
||||
|
||||
let component;
|
||||
let component: ReactWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' }));
|
||||
store.dispatch(updateViewMode(DashboardViewMode.EDIT));
|
||||
store.dispatch(setPanels({ 'foo1': { panelIndex: 'foo1' } }));
|
||||
store.dispatch(
|
||||
setPanels({
|
||||
foo1: {
|
||||
panelIndex: 'foo1',
|
||||
id: 'hi',
|
||||
version: '123',
|
||||
type: 'viz',
|
||||
embeddableConfig: {},
|
||||
gridData: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
w: 1,
|
||||
h: 1,
|
||||
id: 'hi',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
const metadata = { title: 'my embeddable title', editUrl: 'editme' };
|
||||
store.dispatch(embeddableIsInitialized({ metadata, panelId: 'foo1' }));
|
||||
});
|
||||
|
@ -57,7 +77,11 @@ afterAll(() => {
|
|||
});
|
||||
|
||||
test('Panel header shows embeddable title when nothing is set on the panel', () => {
|
||||
component = mount(<Provider store={store}><PanelHeaderContainer {...getProps()} /></Provider>);
|
||||
component = mount(
|
||||
<Provider store={store}>
|
||||
<PanelHeaderContainer {...getProps()} />
|
||||
</Provider>
|
||||
);
|
||||
expect(findTestSubject(component, 'dashboardPanelTitle').text()).toBe('my embeddable title');
|
||||
});
|
||||
|
|
@ -17,38 +17,53 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { embeddableShape } from 'ui/embeddable';
|
||||
import { PanelHeader } from './panel_header';
|
||||
import { Embeddable } from 'ui/embeddable';
|
||||
import { DashboardViewMode } from '../../dashboard_view_mode';
|
||||
import { PanelHeader } from './panel_header';
|
||||
|
||||
import { CoreKibanaState } from '../../../selectors';
|
||||
import {
|
||||
getPanel,
|
||||
getMaximizedPanelId,
|
||||
getEmbeddableTitle,
|
||||
getFullScreenMode,
|
||||
getViewMode,
|
||||
getHidePanelTitles,
|
||||
getEmbeddableTitle
|
||||
getMaximizedPanelId,
|
||||
getPanel,
|
||||
getViewMode,
|
||||
PanelId,
|
||||
} from '../../selectors';
|
||||
|
||||
const mapStateToProps = ({ dashboard }, { panelId }) => {
|
||||
export interface PanelHeaderContainerOwnProps {
|
||||
panelId: PanelId;
|
||||
embeddable?: Embeddable;
|
||||
}
|
||||
|
||||
interface PanelHeaderContainerStateProps {
|
||||
title?: string;
|
||||
isExpanded: boolean;
|
||||
isViewOnlyMode: boolean;
|
||||
hidePanelTitles: boolean;
|
||||
}
|
||||
|
||||
const mapStateToProps = (
|
||||
{ dashboard }: CoreKibanaState,
|
||||
{ panelId }: PanelHeaderContainerOwnProps
|
||||
) => {
|
||||
const panel = getPanel(dashboard, panelId);
|
||||
const embeddableTitle = getEmbeddableTitle(dashboard, panelId);
|
||||
return {
|
||||
title: panel.title === undefined ? embeddableTitle : panel.title,
|
||||
isExpanded: getMaximizedPanelId(dashboard) === panelId,
|
||||
isViewOnlyMode: getFullScreenMode(dashboard) || getViewMode(dashboard) === DashboardViewMode.VIEW,
|
||||
isViewOnlyMode:
|
||||
getFullScreenMode(dashboard) || getViewMode(dashboard) === DashboardViewMode.VIEW,
|
||||
hidePanelTitles: getHidePanelTitles(dashboard),
|
||||
};
|
||||
};
|
||||
|
||||
export const PanelHeaderContainer = connect(
|
||||
mapStateToProps,
|
||||
)(PanelHeader);
|
||||
|
||||
PanelHeaderContainer.propTypes = {
|
||||
panelId: PropTypes.string.isRequired,
|
||||
embeddable: embeddableShape,
|
||||
};
|
||||
export const PanelHeaderContainer = connect<
|
||||
PanelHeaderContainerStateProps,
|
||||
{},
|
||||
PanelHeaderContainerOwnProps,
|
||||
CoreKibanaState
|
||||
>(mapStateToProps)(PanelHeader);
|
|
@ -18,15 +18,29 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiContextMenu,
|
||||
EuiPopover,
|
||||
EuiButtonIcon,
|
||||
EuiContextMenu,
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export function PanelOptionsMenu({ toggleContextMenu, isPopoverOpen, closeContextMenu, panels, isViewMode }) {
|
||||
export interface PanelOptionsMenuProps {
|
||||
toggleContextMenu: () => void;
|
||||
isPopoverOpen: boolean;
|
||||
closeContextMenu: () => void;
|
||||
panels: EuiContextMenuPanelDescriptor[];
|
||||
isViewMode: boolean;
|
||||
}
|
||||
|
||||
export function PanelOptionsMenu({
|
||||
toggleContextMenu,
|
||||
isPopoverOpen,
|
||||
closeContextMenu,
|
||||
panels,
|
||||
isViewMode,
|
||||
}: PanelOptionsMenuProps) {
|
||||
const button = (
|
||||
<EuiButtonIcon
|
||||
iconType={isViewMode ? 'boxesHorizontal' : 'gear'}
|
||||
|
@ -49,18 +63,7 @@ export function PanelOptionsMenu({ toggleContextMenu, isPopoverOpen, closeContex
|
|||
anchorPosition="downRight"
|
||||
withTitle
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId="mainMenu"
|
||||
panels={panels}
|
||||
/>
|
||||
<EuiContextMenu initialPanelId="mainMenu" panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
PanelOptionsMenu.propTypes = {
|
||||
panels: PropTypes.array,
|
||||
toggleContextMenu: PropTypes.func,
|
||||
closeContextMenu: PropTypes.func,
|
||||
isPopoverOpen: PropTypes.bool,
|
||||
isViewMode: PropTypes.bool.isRequired,
|
||||
};
|
|
@ -17,21 +17,22 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiContextMenuPanelDescriptor } from '@elastic/eui';
|
||||
import * as Redux from 'react-redux';
|
||||
import { ContainerState, Embeddable } from 'ui/embeddable';
|
||||
import { panelActionsStore } from '../../store/panel_actions_store';
|
||||
import { embeddableShape } from 'ui/embeddable';
|
||||
import { PanelOptionsMenu } from './panel_options_menu';
|
||||
import {
|
||||
buildEuiContextMenuPanels,
|
||||
getCustomizePanelAction,
|
||||
getEditPanelAction,
|
||||
getInspectorPanelAction,
|
||||
getRemovePanelAction,
|
||||
getCustomizePanelAction,
|
||||
getToggleExpandPanelAction,
|
||||
} from './panel_actions';
|
||||
import { PanelOptionsMenu, PanelOptionsMenuProps } from './panel_options_menu';
|
||||
|
||||
import {
|
||||
closeContextMenu,
|
||||
deletePanel,
|
||||
maximizePanel,
|
||||
minimizePanel,
|
||||
|
@ -40,20 +41,49 @@ import {
|
|||
setVisibleContextMenuPanelId,
|
||||
} from '../../actions';
|
||||
|
||||
import { DashboardContextMenuPanel } from 'ui/dashboard_panel_actions';
|
||||
import { CoreKibanaState } from '../../../selectors';
|
||||
import { DashboardViewMode } from '../../dashboard_view_mode';
|
||||
import {
|
||||
getContainerState,
|
||||
getEmbeddable,
|
||||
getEmbeddableEditUrl,
|
||||
getEmbeddableTitle,
|
||||
getMaximizedPanelId,
|
||||
getPanel,
|
||||
getEmbeddableTitle,
|
||||
getContainerState,
|
||||
getVisibleContextMenuPanelId,
|
||||
getViewMode,
|
||||
getVisibleContextMenuPanelId,
|
||||
PanelId,
|
||||
} from '../../selectors';
|
||||
import { DashboardContextMenuPanel } from 'ui/dashboard_panel_actions';
|
||||
import { DashboardViewMode } from '../../dashboard_view_mode';
|
||||
|
||||
const mapStateToProps = ({ dashboard }, { panelId }) => {
|
||||
interface PanelOptionsMenuContainerDispatchProps {
|
||||
onDeletePanel: () => void;
|
||||
onCloseContextMenu: () => void;
|
||||
openContextMenu: () => void;
|
||||
onMaximizePanel: () => void;
|
||||
onMinimizePanel: () => void;
|
||||
onResetPanelTitle: () => void;
|
||||
onUpdatePanelTitle: (title: string) => void;
|
||||
}
|
||||
|
||||
interface PanelOptionsMenuContainerOwnProps {
|
||||
panelId: PanelId;
|
||||
embeddable?: Embeddable;
|
||||
}
|
||||
|
||||
interface PanelOptionsMenuContainerStateProps {
|
||||
panelTitle?: string;
|
||||
editUrl: string | null | undefined;
|
||||
isExpanded: boolean;
|
||||
containerState: ContainerState;
|
||||
visibleContextMenuPanelId: PanelId | undefined;
|
||||
isViewMode: boolean;
|
||||
}
|
||||
|
||||
const mapStateToProps = (
|
||||
{ dashboard }: CoreKibanaState,
|
||||
{ panelId }: PanelOptionsMenuContainerOwnProps
|
||||
) => {
|
||||
const embeddable = getEmbeddable(dashboard, panelId);
|
||||
const panel = getPanel(dashboard, panelId);
|
||||
const embeddableTitle = getEmbeddableTitle(dashboard, panelId);
|
||||
|
@ -75,20 +105,33 @@ const mapStateToProps = ({ dashboard }, { panelId }) => {
|
|||
* @param embeddableFactory {EmbeddableFactory}
|
||||
* @param panelId {string}
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, { panelId }) => ({
|
||||
const mapDispatchToProps = (
|
||||
dispatch: Redux.Dispatch<CoreKibanaState>,
|
||||
{ panelId }: PanelOptionsMenuContainerOwnProps
|
||||
) => ({
|
||||
onDeletePanel: () => {
|
||||
dispatch(deletePanel(panelId));
|
||||
},
|
||||
closeContextMenu: () => dispatch(setVisibleContextMenuPanelId()),
|
||||
onCloseContextMenu: () => dispatch(closeContextMenu()),
|
||||
openContextMenu: () => dispatch(setVisibleContextMenuPanelId(panelId)),
|
||||
onMaximizePanel: () => dispatch(maximizePanel(panelId)),
|
||||
onMinimizePanel: () => dispatch(minimizePanel()),
|
||||
onResetPanelTitle: () => dispatch(resetPanelTitle(panelId)),
|
||||
onUpdatePanelTitle: (newTitle) => dispatch(setPanelTitle({ title: newTitle, panelId: panelId })),
|
||||
onUpdatePanelTitle: (newTitle: string) => dispatch(setPanelTitle({ title: newTitle, panelId })),
|
||||
});
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { isExpanded, panelTitle, containerState, visibleContextMenuPanelId, isViewMode } = stateProps;
|
||||
const mergeProps = (
|
||||
stateProps: PanelOptionsMenuContainerStateProps,
|
||||
dispatchProps: PanelOptionsMenuContainerDispatchProps,
|
||||
ownProps: PanelOptionsMenuContainerOwnProps
|
||||
) => {
|
||||
const {
|
||||
isExpanded,
|
||||
panelTitle,
|
||||
containerState,
|
||||
visibleContextMenuPanelId,
|
||||
isViewMode,
|
||||
} = stateProps;
|
||||
const isPopoverOpen = visibleContextMenuPanelId === ownProps.panelId;
|
||||
const {
|
||||
onMaximizePanel,
|
||||
|
@ -96,16 +139,16 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||
onDeletePanel,
|
||||
onResetPanelTitle,
|
||||
onUpdatePanelTitle,
|
||||
closeContextMenu,
|
||||
onCloseContextMenu,
|
||||
openContextMenu,
|
||||
} = dispatchProps;
|
||||
const toggleContextMenu = () => isPopoverOpen ? closeContextMenu() : openContextMenu();
|
||||
const toggleContextMenu = () => (isPopoverOpen ? onCloseContextMenu() : openContextMenu());
|
||||
|
||||
// Outside click handlers will trigger for every closed context menu, we only want to react to clicks external to
|
||||
// the currently opened menu.
|
||||
const closeMyContextMenuPanel = () => {
|
||||
if (isPopoverOpen) {
|
||||
closeContextMenu();
|
||||
onCloseContextMenu();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -114,14 +157,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||
closeMyContextMenuPanel();
|
||||
};
|
||||
|
||||
let panels = [];
|
||||
let panels: EuiContextMenuPanelDescriptor[] = [];
|
||||
|
||||
// Don't build the panels if the pop over is not open, or this gets expensive - this function is called once for
|
||||
// every panel, every time any state changes.
|
||||
if (isPopoverOpen) {
|
||||
const contextMenuPanel = new DashboardContextMenuPanel({
|
||||
title: 'Options',
|
||||
id: 'mainMenu'
|
||||
id: 'mainMenu',
|
||||
});
|
||||
|
||||
const actions = [
|
||||
|
@ -134,13 +177,18 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||
onResetPanelTitle,
|
||||
onUpdatePanelTitle,
|
||||
title: panelTitle,
|
||||
closeContextMenu: closeMyContextMenuPanel
|
||||
closeContextMenu: closeMyContextMenuPanel,
|
||||
}),
|
||||
getToggleExpandPanelAction({ isExpanded, toggleExpandedPanel }),
|
||||
getRemovePanelAction(onDeletePanel),
|
||||
].concat(panelActionsStore.actions);
|
||||
|
||||
panels = buildEuiContextMenuPanels({ contextMenuPanel, actions, embeddable: ownProps.embeddable, containerState });
|
||||
panels = buildEuiContextMenuPanels({
|
||||
contextMenuPanel,
|
||||
actions,
|
||||
embeddable: ownProps.embeddable,
|
||||
containerState,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -152,13 +200,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const PanelOptionsMenuContainer = connect(
|
||||
export const PanelOptionsMenuContainer = Redux.connect<
|
||||
PanelOptionsMenuContainerStateProps,
|
||||
PanelOptionsMenuContainerDispatchProps,
|
||||
PanelOptionsMenuContainerOwnProps,
|
||||
PanelOptionsMenuProps,
|
||||
CoreKibanaState
|
||||
>(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mergeProps,
|
||||
mergeProps
|
||||
)(PanelOptionsMenu);
|
||||
|
||||
PanelOptionsMenuContainer.propTypes = {
|
||||
panelId: PropTypes.string.isRequired,
|
||||
embeddable: embeddableShape,
|
||||
};
|
|
@ -24,7 +24,7 @@ import { EuiButtonEmpty, EuiFieldText, EuiFormRow, keyCodes } from '@elastic/eui
|
|||
export interface PanelOptionsMenuFormProps {
|
||||
title?: string;
|
||||
onReset: () => void;
|
||||
onUpdatePanelTitle: (newPanelTitle?: string) => void;
|
||||
onUpdatePanelTitle: (newPanelTitle: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ import { QueryLanguageType } from 'ui/embeddable/types';
|
|||
import { DashboardViewMode } from '../dashboard_view_mode';
|
||||
import { PanelId, ViewState } from '../selectors';
|
||||
|
||||
const closeContextMenu = (view: ViewState) => ({
|
||||
...view,
|
||||
visibleContextMenuPanelId: undefined,
|
||||
});
|
||||
|
||||
const setVisibleContextMenuPanelId = (view: ViewState, panelId: PanelId) => ({
|
||||
...view,
|
||||
visibleContextMenuPanelId: panelId,
|
||||
|
@ -95,6 +100,8 @@ export const viewReducer: Reducer<ViewState> = (
|
|||
return maximizePanel(view, action.payload);
|
||||
case ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID:
|
||||
return setVisibleContextMenuPanelId(view, action.payload);
|
||||
case ViewActionTypeKeys.CLOSE_CONTEXT_MENU:
|
||||
return closeContextMenu(view);
|
||||
case ViewActionTypeKeys.UPDATE_HIDE_PANEL_TITLES:
|
||||
return updateHidePanelTitles(view, action.payload);
|
||||
case ViewActionTypeKeys.UPDATE_TIME_RANGE:
|
||||
|
|
|
@ -17,20 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { DashboardPanelAction } from 'ui/dashboard_panel_actions';
|
||||
|
||||
class PanelActionsStore {
|
||||
constructor() {
|
||||
/**
|
||||
*
|
||||
* @type {Array.<DashboardPanelAction>}
|
||||
*/
|
||||
this.actions = [];
|
||||
}
|
||||
public actions: DashboardPanelAction[] = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {IndexedArray} panelActionsRegistry
|
||||
*/
|
||||
initializeFromRegistry(panelActionsRegistry) {
|
||||
public initializeFromRegistry(panelActionsRegistry: DashboardPanelAction[]) {
|
||||
panelActionsRegistry.forEach(panelAction => {
|
||||
this.actions.push(panelAction);
|
||||
});
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ReactElement } from 'react';
|
||||
import { PanelActionAPI } from './types';
|
||||
|
||||
interface DashboardContextMenuPanelOptions {
|
||||
getContent?: (panelActionAPI: PanelActionAPI) => JSX.Element | Element | undefined;
|
||||
getContent?: (panelActionAPI: PanelActionAPI) => ReactElement<any> | HTMLElement | undefined;
|
||||
}
|
||||
|
||||
interface DashboardContextMenuPanelConfig {
|
||||
|
@ -47,7 +48,7 @@ export class DashboardContextMenuPanel {
|
|||
/**
|
||||
* Optional, could be composed of actions instead of content.
|
||||
*/
|
||||
public getContent(panelActionAPI: PanelActionAPI): JSX.Element | Element | undefined {
|
||||
public getContent(panelActionAPI: PanelActionAPI): ReactElement<any> | HTMLElement | undefined {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { EuiContextMenuItemIcon } from '@elastic/eui';
|
||||
import { DashboardContextMenuPanel } from './dashboard_context_menu_panel';
|
||||
import { PanelActionAPI } from './types';
|
||||
|
||||
|
@ -54,7 +54,7 @@ interface DashboardPanelActionOptions {
|
|||
/**
|
||||
* Optional icon to display to the left of the action.
|
||||
*/
|
||||
icon?: Element | JSX.Element;
|
||||
icon?: EuiContextMenuItemIcon;
|
||||
}
|
||||
|
||||
interface DashboardPanelActionsConfig {
|
||||
|
@ -77,7 +77,7 @@ export class DashboardPanelAction {
|
|||
/**
|
||||
* Optional icon to display to the left of the action.
|
||||
*/
|
||||
public readonly icon?: Element | JSX.Element;
|
||||
public readonly icon?: EuiContextMenuItemIcon;
|
||||
|
||||
/**
|
||||
* Display name of the action in the menu
|
||||
|
|
|
@ -24,9 +24,10 @@ import { ContainerState, Embeddable } from 'ui/embeddable';
|
|||
*/
|
||||
export interface PanelActionAPI {
|
||||
/**
|
||||
* The embeddable that resides inside this action.
|
||||
* The embeddable that resides inside this action. It's possible it's undefined if the embeddable has not been returned from
|
||||
* the EmbeddableFactory yet.
|
||||
*/
|
||||
embeddable: Embeddable;
|
||||
embeddable?: Embeddable;
|
||||
|
||||
/**
|
||||
* Information about the current state of the panel and dashboard.
|
||||
|
|
|
@ -69,7 +69,7 @@ export interface ContainerState {
|
|||
/**
|
||||
* A way to override the underlying embeddable title and supply a title at the panel level.
|
||||
*/
|
||||
customTitle: string | undefined;
|
||||
customTitle?: string;
|
||||
}
|
||||
|
||||
export interface EmbeddableState {
|
||||
|
|
|
@ -494,6 +494,13 @@
|
|||
"@types/node" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^5.0.6":
|
||||
version "5.0.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-5.0.21.tgz#98a3a371dfc22c894889f660d7515717639d20f4"
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
redux "^3.6.0"
|
||||
|
||||
"@types/react@*", "@types/react@^16.3.14":
|
||||
version "16.3.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.3.14.tgz#f90ac6834de172e13ecca430dcb6814744225d36"
|
||||
|
@ -11502,7 +11509,7 @@ redux-thunk@2.3.0:
|
|||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
|
||||
redux@3.7.2:
|
||||
redux@3.7.2, redux@^3.6.0:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
|
||||
dependencies:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue