mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* Allow pluggable panel actions * Need to register it as being used in kibana * Some cleanup * update snapshots to match new EUI versions, set time range * Use newer panelActions service * add missing await * More clean up and fixes * bring back window reload * Show actions in view mode too * delete now unused files * Use toggle action to determin if context menu is open * Fix tests that assume the toggle is hidden in view mode. * Add some debug logs * Fix up assumptions * Previous failing test was legit - we don't want to show remove option when panel is expanded * Embeddable can be empty before the panel is loaded * Should look for either visualize or discover page * Address code comments * address code review comments * whoops, get rid of childPanelToOpenOnClick entirely
This commit is contained in:
parent
73173f11c3
commit
225f541abb
42 changed files with 700 additions and 377 deletions
|
@ -1,6 +1,7 @@
|
|||
import { createAction } from 'redux-actions';
|
||||
|
||||
export const updateViewMode = createAction('UPDATE_VIEW_MODE');
|
||||
export const setVisibleContextMenuPanelId = createAction('SET_VISIBLE_CONTEXT_MENU_PANEL_ID');
|
||||
export const maximizePanel = createAction('MAXIMIZE_PANEl');
|
||||
export const minimizePanel = createAction('MINIMIZE_PANEL');
|
||||
export const updateIsFullScreenMode = createAction('UPDATE_IS_FULL_SCREEN_MODE');
|
||||
|
|
|
@ -7,6 +7,8 @@ import { toastNotifications } from 'ui/notify';
|
|||
|
||||
import 'ui/query_bar';
|
||||
|
||||
import { panelActionsStore } from './store/panel_actions_store';
|
||||
|
||||
import { getDashboardTitle } from './dashboard_strings';
|
||||
import { DashboardViewMode } from './dashboard_view_mode';
|
||||
import { TopNavIds } from './top_nav/top_nav_ids';
|
||||
|
@ -24,6 +26,7 @@ import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
|
|||
import * as filterActions from 'ui/doc_table/actions/filter';
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
import { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_factories_registry';
|
||||
import { DashboardPanelActionsRegistryProvider } from 'ui/dashboard_panel_actions/dashboard_panel_actions_registry';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
|
||||
|
@ -62,6 +65,10 @@ app.directive('dashboardApp', function ($injector) {
|
|||
const docTitle = Private(DocTitleProvider);
|
||||
const notify = new Notifier({ location: 'Dashboard' });
|
||||
const embeddableFactories = Private(EmbeddableFactoriesRegistryProvider);
|
||||
const panelActionsRegistry = Private(DashboardPanelActionsRegistryProvider);
|
||||
|
||||
panelActionsStore.initializeFromRegistry(panelActionsRegistry);
|
||||
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
const visTypes = Private(VisTypesRegistryProvider);
|
||||
$scope.getEmbeddableFactory = panelType => embeddableFactories.byName[panelType];
|
||||
|
|
|
@ -25,7 +25,7 @@ exports[`DashboardPanel matches snapshot 1`] = `
|
|||
>
|
||||
<div
|
||||
class="euiPopover euiPopover--anchorDownRight dashboardPanelPopOver euiPopover--withTitle"
|
||||
id="panelContextMenu"
|
||||
id="dashboardPanelContextMenu"
|
||||
>
|
||||
<button
|
||||
aria-label="Panel options"
|
||||
|
|
|
@ -121,6 +121,7 @@ export class DashboardPanel extends React.Component {
|
|||
>
|
||||
<PanelHeader
|
||||
panelId={panel.panelIndex}
|
||||
embeddable={this.embeddable}
|
||||
/>
|
||||
|
||||
{this.renderContent()}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* Loops through allActions and extracts those that belong on the given contextMenuPanelId
|
||||
* @param {string} contextMenuPanelId
|
||||
* @param {Array.<DashboardPanelAction>} allActions
|
||||
*/
|
||||
function getActionsForPanel(contextMenuPanelId, allActions) {
|
||||
return allActions.filter(action => action.parentPanelId === contextMenuPanelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} contextMenuPanelId
|
||||
* @param {Array.<DashboardPanelAction>} actions
|
||||
* @param {Embeddable} embeddable
|
||||
* @param {ContainerState} containerState
|
||||
* @return {{
|
||||
* Array.<EuiContextMenuPanelItemShape> 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
|
||||
* need to be moved to the top level for EUI.
|
||||
* }}
|
||||
*/
|
||||
function buildEuiContextMenuPanelItemsAndChildPanels({ contextMenuPanelId, actions, embeddable, containerState }) {
|
||||
const items = [];
|
||||
const childPanels = [];
|
||||
const actionsForPanel = getActionsForPanel(contextMenuPanelId, actions);
|
||||
actionsForPanel.forEach(action => {
|
||||
const isVisible = action.isVisible({ embeddable, containerState });
|
||||
if (!isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.childContextMenuPanel) {
|
||||
childPanels.push(
|
||||
...buildEuiContextMenuPanels({
|
||||
contextMenuPanel: action.childContextMenuPanel,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState
|
||||
}));
|
||||
}
|
||||
|
||||
items.push(convertPanelActionToContextMenuItem(
|
||||
{
|
||||
action,
|
||||
containerState,
|
||||
embeddable
|
||||
}));
|
||||
});
|
||||
|
||||
return { items, childPanels };
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a DashboardContextMenuPanel to the shape EuiContextMenuPanel expects, inserting any registered pluggable
|
||||
* panel actions.
|
||||
* @param {DashboardContextMenuPanel} contextMenuPanel
|
||||
* @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
|
||||
*/
|
||||
export function buildEuiContextMenuPanels(
|
||||
{
|
||||
contextMenuPanel,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState
|
||||
}) {
|
||||
const euiContextMenuPanel = {
|
||||
id: contextMenuPanel.id,
|
||||
title: contextMenuPanel.title,
|
||||
items: [],
|
||||
content: contextMenuPanel.getContent({ embeddable, containerState }),
|
||||
};
|
||||
const contextMenuPanels = [euiContextMenuPanel];
|
||||
|
||||
const { items, childPanels } =
|
||||
buildEuiContextMenuPanelItemsAndChildPanels({
|
||||
contextMenuPanelId: contextMenuPanel.id,
|
||||
actions,
|
||||
embeddable,
|
||||
containerState
|
||||
});
|
||||
|
||||
euiContextMenuPanel.items = items;
|
||||
return contextMenuPanels.concat(childPanels);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DashboardPanelAction} action
|
||||
* @param {ContainerState} containerState
|
||||
* @param {Embeddable} embeddable
|
||||
* @return {Object} See EuiContextMenuPanelItemShape in @elastic/eui
|
||||
*/
|
||||
function convertPanelActionToContextMenuItem({ action, containerState, embeddable }) {
|
||||
return {
|
||||
id: action.id || action.displayName.replace(/\s/g, ''),
|
||||
name: action.displayName,
|
||||
icon: action.icon,
|
||||
panel: _.get(action, 'childContextMenuPanel.id'),
|
||||
onClick: () => action.onClick({ containerState, embeddable }),
|
||||
disabled: action.isDisabled({ containerState, embeddable }),
|
||||
'data-test-subj': `dashboardPanelAction-${action.id}`,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { PanelOptionsMenuForm } from '../panel_options_menu_form';
|
||||
import { DashboardPanelAction, DashboardContextMenuPanel } from 'ui/dashboard_panel_actions';
|
||||
import { DashboardViewMode } from '../../../dashboard_view_mode';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {function} onResetPanelTitle
|
||||
* @param {function} onUpdatePanelTitle
|
||||
* @param {string} title
|
||||
* @param {function} closeContextMenu
|
||||
* @return {DashboardPanelAction}
|
||||
*/
|
||||
export function getCustomizePanelAction({ onResetPanelTitle, onUpdatePanelTitle, title, closeContextMenu }) {
|
||||
return new DashboardPanelAction({
|
||||
displayName: 'Customize panel',
|
||||
id: 'customizePanel',
|
||||
parentPanelId: 'mainMenu',
|
||||
icon: <EuiIcon type="pencil" />,
|
||||
isVisible: ({ containerState }) => (containerState.viewMode === DashboardViewMode.EDIT),
|
||||
childContextMenuPanel: new DashboardContextMenuPanel({
|
||||
id: 'panelSubOptionsMenu',
|
||||
title: 'Customize panel',
|
||||
getContent: () => (<PanelOptionsMenuForm
|
||||
onReset={onResetPanelTitle}
|
||||
onUpdatePanelTitle={onUpdatePanelTitle}
|
||||
title={title}
|
||||
onClose={closeContextMenu}
|
||||
/>),
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { DashboardPanelAction } from 'ui/dashboard_panel_actions';
|
||||
import { DashboardViewMode } from '../../../dashboard_view_mode';
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {DashboardPanelAction}
|
||||
*/
|
||||
export function getEditPanelAction() {
|
||||
return new DashboardPanelAction({
|
||||
displayName: 'Edit visualization',
|
||||
id: 'editPanel',
|
||||
icon: <EuiIcon type="pencil" />,
|
||||
parentPanelId: 'mainMenu',
|
||||
onClick: ({ embeddable }) => { window.location = embeddable.metadata.editUrl; },
|
||||
isVisible: ({ containerState }) => (containerState.viewMode === DashboardViewMode.EDIT),
|
||||
isDisabled: ({ embeddable }) => (!embeddable || !embeddable.metadata || !embeddable.metadata.editUrl),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { DashboardPanelAction } from 'ui/dashboard_panel_actions';
|
||||
import { DashboardViewMode } from '../../../dashboard_view_mode';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {function} onDeletePanel
|
||||
* @return {DashboardPanelAction}
|
||||
*/
|
||||
export function getRemovePanelAction(onDeletePanel) {
|
||||
return new DashboardPanelAction({
|
||||
displayName: 'Delete from dashboard',
|
||||
id: 'deletePanel',
|
||||
parentPanelId: 'mainMenu',
|
||||
icon: <EuiIcon type="trash" />,
|
||||
isVisible: ({ containerState }) => (
|
||||
containerState.viewMode === DashboardViewMode.EDIT && !containerState.isPanelExpanded
|
||||
),
|
||||
onClick: onDeletePanel,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { DashboardPanelAction } from 'ui/dashboard_panel_actions';
|
||||
|
||||
/**
|
||||
* Returns an action that toggles the panel into maximized or minimized state.
|
||||
* @param {boolean} isExpanded
|
||||
* @param {function} toggleExpandedPanel
|
||||
* @return {DashboardPanelAction}
|
||||
*/
|
||||
export function getToggleExpandPanelAction({ isExpanded, toggleExpandedPanel }) {
|
||||
return new DashboardPanelAction({
|
||||
displayName: isExpanded ? 'Minimize' : 'Full screen',
|
||||
id: 'togglePanel',
|
||||
parentPanelId: 'mainMenu',
|
||||
// TODO: Update to minimize icon when https://github.com/elastic/eui/issues/837 is complete.
|
||||
icon: <EuiIcon type={isExpanded ? 'expand' : 'expand'} />,
|
||||
onClick: toggleExpandedPanel,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export { getEditPanelAction } from './get_edit_panel_action';
|
||||
export { getRemovePanelAction } from './get_remove_panel_action';
|
||||
export { buildEuiContextMenuPanels } from './build_context_menu';
|
||||
export { getCustomizePanelAction } from './get_customize_panel_action';
|
||||
export { getToggleExpandPanelAction } from './get_toggle_expand_panel_action';
|
|
@ -1,12 +1,14 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { embeddableShape } from 'ui/embeddable';
|
||||
import { PanelOptionsMenuContainer } from './panel_options_menu_container';
|
||||
|
||||
export function PanelHeader({ title, actions, isViewOnlyMode, hidePanelTitles }) {
|
||||
export function PanelHeader({ title, panelId, embeddable, isViewOnlyMode, hidePanelTitles }) {
|
||||
if (isViewOnlyMode && (!title || hidePanelTitles)) {
|
||||
return (
|
||||
<div className="panel-heading-floater">
|
||||
<div className="kuiMicroButtonGroup">
|
||||
{actions}
|
||||
<PanelOptionsMenuContainer panelId={panelId} embeddable={embeddable} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -24,7 +26,7 @@ export function PanelHeader({ title, actions, isViewOnlyMode, hidePanelTitles })
|
|||
</span>
|
||||
|
||||
<div className="kuiMicroButtonGroup">
|
||||
{actions}
|
||||
<PanelOptionsMenuContainer panelId={panelId} embeddable={embeddable} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -33,6 +35,7 @@ export function PanelHeader({ title, actions, isViewOnlyMode, hidePanelTitles })
|
|||
PanelHeader.propTypes = {
|
||||
isViewOnlyMode: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
actions: PropTypes.node,
|
||||
hidePanelTitles: PropTypes.bool.isRequired,
|
||||
embeddable: embeddableShape,
|
||||
panelId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { embeddableShape } from 'ui/embeddable';
|
||||
import { PanelHeader } from './panel_header';
|
||||
import { PanelOptionsMenuContainer } from './panel_options_menu_container';
|
||||
import { PanelMaximizeIcon } from './panel_maximize_icon';
|
||||
import { PanelMinimizeIcon } from './panel_minimize_icon';
|
||||
import { DashboardViewMode } from '../../dashboard_view_mode';
|
||||
|
||||
import {
|
||||
maximizePanel,
|
||||
minimizePanel,
|
||||
} from '../../actions';
|
||||
|
||||
import {
|
||||
getPanel,
|
||||
getMaximizedPanelId,
|
||||
|
@ -33,39 +25,11 @@ const mapStateToProps = ({ dashboard }, { panelId }) => {
|
|||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { panelId }) => ({
|
||||
onMaximizePanel: () => dispatch(maximizePanel(panelId)),
|
||||
onMinimizePanel: () => dispatch(minimizePanel()),
|
||||
});
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { isExpanded, isViewOnlyMode, title, hidePanelTitles } = stateProps;
|
||||
const { onMaximizePanel, onMinimizePanel } = dispatchProps;
|
||||
const { panelId } = ownProps;
|
||||
let actions;
|
||||
|
||||
if (isViewOnlyMode) {
|
||||
actions = isExpanded ?
|
||||
<PanelMinimizeIcon onMinimize={onMinimizePanel} /> :
|
||||
<PanelMaximizeIcon onMaximize={onMaximizePanel} />;
|
||||
} else {
|
||||
actions = <PanelOptionsMenuContainer panelId={panelId} />;
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
actions,
|
||||
isViewOnlyMode,
|
||||
hidePanelTitles,
|
||||
};
|
||||
};
|
||||
|
||||
export const PanelHeaderContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mergeProps,
|
||||
)(PanelHeader);
|
||||
|
||||
PanelHeaderContainer.propTypes = {
|
||||
panelId: PropTypes.string.isRequired,
|
||||
embeddable: embeddableShape,
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
setPanelTitle,
|
||||
resetPanelTitle,
|
||||
embeddableIsInitialized,
|
||||
updateTimeRange,
|
||||
} from '../../actions';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
|
@ -25,6 +26,7 @@ function getProps(props = {}) {
|
|||
let component;
|
||||
|
||||
beforeAll(() => {
|
||||
store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' }));
|
||||
store.dispatch(updateViewMode(DashboardViewMode.EDIT));
|
||||
store.dispatch(setPanels({ 'foo1': { panelIndex: 'foo1' } }));
|
||||
const metadata = { title: 'my embeddable title', editUrl: 'editme' };
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export function PanelMaximizeIcon({ onMaximize }) {
|
||||
return (
|
||||
<button
|
||||
className="kuiMicroButton viewModeExpandPanelToggle"
|
||||
aria-label="Maximize panel"
|
||||
data-test-subj="dashboardPanelExpandIcon"
|
||||
onClick={onMaximize}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="kuiIcon fa-expand"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
PanelMaximizeIcon.propTypes = {
|
||||
onMaximize: PropTypes.func.isRequired
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export function PanelMinimizeIcon({ onMinimize }) {
|
||||
return (
|
||||
<button
|
||||
className="kuiMicroButton viewModeExpandPanelToggle"
|
||||
aria-label="Minimize panel"
|
||||
data-test-subj="dashboardPanelExpandIcon"
|
||||
onClick={onMinimize}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="kuiIcon fa-compress"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
PanelMinimizeIcon.propTypes = {
|
||||
onMinimize: PropTypes.func.isRequired
|
||||
};
|
|
@ -1,143 +1,44 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { PanelOptionsMenuForm } from './panel_options_menu_form';
|
||||
|
||||
import {
|
||||
EuiContextMenu,
|
||||
EuiPopover,
|
||||
EuiIcon,
|
||||
EuiButtonIcon,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export class PanelOptionsMenu extends React.Component {
|
||||
state = {
|
||||
isPopoverOpen: false
|
||||
};
|
||||
export function PanelOptionsMenu({ toggleContextMenu, isPopoverOpen, closeContextMenu, panels }) {
|
||||
const button = (
|
||||
<EuiButtonIcon
|
||||
iconType="gear"
|
||||
aria-label="Panel options"
|
||||
data-test-subj="dashboardPanelToggleMenuIcon"
|
||||
onClick={toggleContextMenu}
|
||||
/>
|
||||
);
|
||||
|
||||
toggleMenu = () => {
|
||||
this.setState({ isPopoverOpen: !this.state.isPopoverOpen });
|
||||
};
|
||||
closePopover = () => this.setState({ isPopoverOpen: false });
|
||||
|
||||
onEditPanel = () => {
|
||||
window.location = this.props.editUrl;
|
||||
};
|
||||
|
||||
onDeletePanel = () => {
|
||||
if (this.props.onDeletePanel) {
|
||||
this.props.onDeletePanel();
|
||||
}
|
||||
};
|
||||
|
||||
onToggleExpandPanel = () => {
|
||||
this.closePopover();
|
||||
this.props.toggleExpandedPanel();
|
||||
};
|
||||
|
||||
buildMainMenuPanel() {
|
||||
const { isExpanded } = this.props;
|
||||
const mainPanelMenuItems = [
|
||||
{
|
||||
name: 'Edit visualization',
|
||||
'data-test-subj': 'dashboardPanelEditLink',
|
||||
icon: <EuiIcon
|
||||
type="pencil"
|
||||
/>,
|
||||
onClick: this.onEditPanel,
|
||||
disabled: !this.props.editUrl,
|
||||
},
|
||||
{
|
||||
name: 'Customize panel',
|
||||
'data-test-subj': 'dashboardPanelOptionsSubMenuLink',
|
||||
icon: <EuiIcon
|
||||
type="pencil"
|
||||
/>,
|
||||
panel: 'panelSubOptionsMenu',
|
||||
},
|
||||
{
|
||||
name: isExpanded ? 'Minimize' : 'Full screen',
|
||||
'data-test-subj': 'dashboardPanelExpandIcon',
|
||||
icon: <EuiIcon
|
||||
type={isExpanded ? 'expand' : 'expand'}
|
||||
/>,
|
||||
onClick: this.onToggleExpandPanel,
|
||||
}
|
||||
];
|
||||
if (!this.props.isExpanded) {
|
||||
mainPanelMenuItems.push({
|
||||
name: 'Delete from dashboard',
|
||||
'data-test-subj': 'dashboardPanelRemoveIcon',
|
||||
icon: <EuiIcon
|
||||
type="trash"
|
||||
/>,
|
||||
onClick: this.onDeletePanel,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
title: 'Options',
|
||||
id: 'mainMenu',
|
||||
items: mainPanelMenuItems,
|
||||
};
|
||||
}
|
||||
|
||||
buildPanelOptionsSubMenu() {
|
||||
return {
|
||||
title: 'Customize panel',
|
||||
id: 'panelSubOptionsMenu',
|
||||
content: <PanelOptionsMenuForm
|
||||
onReset={this.props.onResetPanelTitle}
|
||||
onUpdatePanelTitle={this.props.onUpdatePanelTitle}
|
||||
title={this.props.panelTitle}
|
||||
onClose={this.closePopover}
|
||||
/>,
|
||||
};
|
||||
}
|
||||
|
||||
renderPanels() {
|
||||
return [
|
||||
this.buildMainMenuPanel(),
|
||||
this.buildPanelOptionsSubMenu(),
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<EuiButtonIcon
|
||||
iconType="gear"
|
||||
aria-label="Panel options"
|
||||
data-test-subj="dashboardPanelToggleMenuIcon"
|
||||
onClick={this.toggleMenu}
|
||||
return (
|
||||
<EuiPopover
|
||||
id="dashboardPanelContextMenu"
|
||||
className="dashboardPanelPopOver"
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closeContextMenu}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downRight"
|
||||
withTitle
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId="mainMenu"
|
||||
panels={panels}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="panelContextMenu"
|
||||
className="dashboardPanelPopOver"
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downRight"
|
||||
withTitle
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId="mainMenu"
|
||||
panels={this.renderPanels()}
|
||||
/>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
PanelOptionsMenu.propTypes = {
|
||||
panelTitle: PropTypes.string,
|
||||
onUpdatePanelTitle: PropTypes.func.isRequired,
|
||||
onResetPanelTitle: PropTypes.func.isRequired,
|
||||
editUrl: PropTypes.string, // May be empty if the embeddable is still loading
|
||||
toggleExpandedPanel: PropTypes.func.isRequired,
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
onDeletePanel: PropTypes.func, // Not available when the panel is expanded.
|
||||
panels: PropTypes.array,
|
||||
toggleContextMenu: PropTypes.func,
|
||||
closeContextMenu: PropTypes.func,
|
||||
isPopoverOpen: PropTypes.bool,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { panelActionsStore } from '../../store/panel_actions_store';
|
||||
import { embeddableShape } from 'ui/embeddable';
|
||||
import { PanelOptionsMenu } from './panel_options_menu';
|
||||
import {
|
||||
buildEuiContextMenuPanels,
|
||||
getEditPanelAction,
|
||||
getRemovePanelAction,
|
||||
getCustomizePanelAction,
|
||||
getToggleExpandPanelAction,
|
||||
} from './panel_actions';
|
||||
|
||||
import {
|
||||
deletePanel,
|
||||
|
@ -9,6 +17,7 @@ import {
|
|||
minimizePanel,
|
||||
resetPanelTitle,
|
||||
setPanelTitle,
|
||||
setVisibleContextMenuPanelId,
|
||||
} from '../../actions';
|
||||
|
||||
import {
|
||||
|
@ -16,17 +25,24 @@ import {
|
|||
getEmbeddableEditUrl,
|
||||
getMaximizedPanelId,
|
||||
getPanel,
|
||||
getEmbeddableTitle
|
||||
getEmbeddableTitle,
|
||||
getContainerState,
|
||||
getVisibleContextMenuPanelId,
|
||||
} from '../../selectors';
|
||||
import { DashboardContextMenuPanel } from 'ui/dashboard_panel_actions';
|
||||
|
||||
const mapStateToProps = ({ dashboard }, { panelId }) => {
|
||||
const embeddable = getEmbeddable(dashboard, panelId);
|
||||
const panel = getPanel(dashboard, panelId);
|
||||
const embeddableTitle = getEmbeddableTitle(dashboard, panelId);
|
||||
const containerState = getContainerState(dashboard, panelId);
|
||||
const visibleContextMenuPanelId = getVisibleContextMenuPanelId(dashboard);
|
||||
return {
|
||||
panelTitle: panel.title === undefined ? embeddableTitle : panel.title,
|
||||
editUrl: embeddable ? getEmbeddableEditUrl(dashboard, panelId) : null,
|
||||
isExpanded: getMaximizedPanelId(dashboard) === panelId,
|
||||
containerState,
|
||||
visibleContextMenuPanelId,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -39,23 +55,71 @@ const mapDispatchToProps = (dispatch, { panelId }) => ({
|
|||
onDeletePanel: () => {
|
||||
dispatch(deletePanel(panelId));
|
||||
},
|
||||
closeContextMenu: () => dispatch(setVisibleContextMenuPanelId()),
|
||||
openContextMenu: () => dispatch(setVisibleContextMenuPanelId(panelId)),
|
||||
onMaximizePanel: () => dispatch(maximizePanel(panelId)),
|
||||
onMinimizePanel: () => dispatch(minimizePanel()),
|
||||
onResetPanelTitle: () => dispatch(resetPanelTitle(panelId)),
|
||||
onUpdatePanelTitle: (newTitle) => dispatch(setPanelTitle(newTitle, panelId)),
|
||||
});
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps) => {
|
||||
const { isExpanded, editUrl, panelTitle } = stateProps;
|
||||
const { onMaximizePanel, onMinimizePanel, ...dispatchers } = dispatchProps;
|
||||
const toggleExpandedPanel = () => isExpanded ? onMinimizePanel() : onMaximizePanel();
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { isExpanded, panelTitle, containerState, visibleContextMenuPanelId } = stateProps;
|
||||
const isPopoverOpen = visibleContextMenuPanelId === ownProps.panelId;
|
||||
const {
|
||||
onMaximizePanel,
|
||||
onMinimizePanel,
|
||||
onDeletePanel,
|
||||
onResetPanelTitle,
|
||||
onUpdatePanelTitle,
|
||||
closeContextMenu,
|
||||
openContextMenu,
|
||||
} = dispatchProps;
|
||||
const toggleContextMenu = () => isPopoverOpen ? closeContextMenu() : 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();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpandedPanel = () => {
|
||||
isExpanded ? onMinimizePanel() : onMaximizePanel();
|
||||
closeMyContextMenuPanel();
|
||||
};
|
||||
|
||||
let panels = [];
|
||||
|
||||
// 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'
|
||||
});
|
||||
|
||||
const actions = [
|
||||
getEditPanelAction(),
|
||||
getCustomizePanelAction({
|
||||
onResetPanelTitle,
|
||||
onUpdatePanelTitle,
|
||||
title: panelTitle,
|
||||
closeContextMenu: closeMyContextMenuPanel
|
||||
}),
|
||||
getToggleExpandPanelAction({ isExpanded, toggleExpandedPanel }),
|
||||
getRemovePanelAction(onDeletePanel),
|
||||
].concat(panelActionsStore.actions);
|
||||
|
||||
panels = buildEuiContextMenuPanels({ contextMenuPanel, actions, embeddable: ownProps.embeddable, containerState });
|
||||
}
|
||||
|
||||
return {
|
||||
panelTitle,
|
||||
toggleExpandedPanel,
|
||||
isExpanded,
|
||||
editUrl,
|
||||
...dispatchers,
|
||||
panels,
|
||||
toggleContextMenu,
|
||||
closeContextMenu: closeMyContextMenuPanel,
|
||||
isPopoverOpen,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -67,4 +131,5 @@ export const PanelOptionsMenuContainer = connect(
|
|||
|
||||
PanelOptionsMenuContainer.propTypes = {
|
||||
panelId: PropTypes.string.isRequired,
|
||||
embeddable: embeddableShape,
|
||||
};
|
||||
|
|
|
@ -7,11 +7,17 @@ import {
|
|||
updateHidePanelTitles,
|
||||
updateIsFullScreenMode,
|
||||
updateTimeRange,
|
||||
setVisibleContextMenuPanelId,
|
||||
} from '../actions';
|
||||
|
||||
import { DashboardViewMode } from '../dashboard_view_mode';
|
||||
|
||||
export const view = handleActions({
|
||||
[setVisibleContextMenuPanelId]: (state, { payload }) => ({
|
||||
...state,
|
||||
visibleContextMenuPanelId: payload
|
||||
}),
|
||||
|
||||
[updateViewMode]: (state, { payload }) => ({
|
||||
...state,
|
||||
viewMode: payload
|
||||
|
|
|
@ -5,6 +5,7 @@ import _ from 'lodash';
|
|||
* @property {DashboardViewMode} viewMode
|
||||
* @property {boolean} isFullScreenMode
|
||||
* @property {string|undefined} maximizedPanelId
|
||||
* @property {string|undefined} getVisibleContextMenuPanelId
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -103,6 +104,9 @@ export const getEmbeddableEditUrl = (dashboard, panelId) => {
|
|||
return embeddable && embeddable.initialized ? embeddable.metadata.editUrl : '';
|
||||
};
|
||||
|
||||
|
||||
export const getVisibleContextMenuPanelId = dashboard => dashboard.view.visibleContextMenuPanelId;
|
||||
|
||||
/**
|
||||
* @param dashboard {DashboardState}
|
||||
* @return {boolean}
|
||||
|
@ -161,10 +165,12 @@ export const getDescription = dashboard => dashboard.metadata.description;
|
|||
* This state object is specifically for communicating to embeddables and it's structure is not tied to
|
||||
* the redux tree structure.
|
||||
* @typedef {Object} ContainerState
|
||||
* @property {DashboardViewMode} viewMode - edit or view mode.
|
||||
* @property {String} timeRange.to - either an absolute time range in utc format or a relative one (e.g. now-15m)
|
||||
* @property {String} timeRange.from - either an absolute time range in utc format or a relative one (e.g. now-15m)
|
||||
* @property {Object} embeddableCustomization
|
||||
* @property {boolean} hidePanelTitles
|
||||
* @property {boolean} isPanelExpanded
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -183,6 +189,8 @@ export const getContainerState = (dashboard, panelId) => {
|
|||
embeddableCustomization: _.cloneDeep(getEmbeddableCustomization(dashboard, panelId) || {}),
|
||||
hidePanelTitles: getHidePanelTitles(dashboard),
|
||||
customTitle: getPanel(dashboard, panelId).title,
|
||||
viewMode: getViewMode(dashboard),
|
||||
isPanelExpanded: getMaximizedPanelId(dashboard) === panelId,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
class PanelActionsStore {
|
||||
constructor() {
|
||||
/**
|
||||
*
|
||||
* @type {Array.<DashboardPanelAction>}
|
||||
*/
|
||||
this.actions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {IndexedArray} panelActionsRegistry
|
||||
*/
|
||||
initializeFromRegistry(panelActionsRegistry) {
|
||||
panelActionsRegistry.forEach(panelAction => {
|
||||
this.actions.push(panelAction);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const panelActionsStore = new PanelActionsStore();
|
|
@ -16,6 +16,7 @@ import 'uiExports/spyModes';
|
|||
import 'uiExports/fieldFormats';
|
||||
import 'uiExports/fieldFormatEditors';
|
||||
import 'uiExports/navbarExtensions';
|
||||
import 'uiExports/dashboardPanelActions';
|
||||
import 'uiExports/managementSections';
|
||||
import 'uiExports/devTools';
|
||||
import 'uiExports/docViews';
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
export class DashboardContextMenuPanel {
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {string} title
|
||||
* @param {function} getContent
|
||||
*/
|
||||
constructor({ id, title, getContent }) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
|
||||
if (getContent) {
|
||||
this.getContent = getContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional, could be composed of actions instead of content.
|
||||
* @param {Embeddable} embeddable
|
||||
* @param {ContainerState} containerState
|
||||
*/
|
||||
getContent(/*{ embeddable, containerState }*/) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
export class DashboardPanelAction {
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} displayName
|
||||
* @param {function} onClick
|
||||
* @param {DashboardContextMenuPanel} childContextMenuPanel - optional child panel to open when clicked.
|
||||
* @param {function} isDisabled - optionally set a custom disabled function
|
||||
* @param {function} isVisible - optionally set a custom isVisible function
|
||||
* @param {string} parentPanelId - set if this action belongs on a nested child panel
|
||||
* @param {Element} icon
|
||||
*/
|
||||
constructor(
|
||||
{
|
||||
id,
|
||||
displayName,
|
||||
onClick,
|
||||
childContextMenuPanel,
|
||||
isDisabled,
|
||||
isVisible,
|
||||
parentPanelId,
|
||||
icon,
|
||||
} = {}) {
|
||||
this.id = id;
|
||||
this.icon = icon;
|
||||
this.displayName = displayName;
|
||||
this.childContextMenuPanel = childContextMenuPanel;
|
||||
this.parentPanelId = parentPanelId;
|
||||
|
||||
if (onClick) {
|
||||
this.onClick = onClick;
|
||||
}
|
||||
|
||||
if (isDisabled) {
|
||||
this.isDisabled = isDisabled;
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
this.isVisible = isVisible;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Embeddable} embeddable
|
||||
* @param ContainerState} containerState
|
||||
*/
|
||||
onClick(/*{ embeddable, containerState }*/) {}
|
||||
|
||||
/**
|
||||
* Defaults to always visible.
|
||||
* @param {Embeddable} embeddable
|
||||
* @param ContainerState} containerState
|
||||
* @return {boolean}
|
||||
*/
|
||||
isVisible(/*{ embeddable, containerState }*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to always enabled.
|
||||
* @param {Embeddable} embeddable
|
||||
* @param {ContainerState} containerState
|
||||
*/
|
||||
isDisabled(/*{ embeddable, containerState }*/) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { uiRegistry } from 'ui/registry/_registry';
|
||||
|
||||
export const DashboardPanelActionsRegistryProvider = uiRegistry({
|
||||
name: 'dashboardPanelActions',
|
||||
index: ['name'],
|
||||
});
|
3
src/ui/public/dashboard_panel_actions/index.js
Normal file
3
src/ui/public/dashboard_panel_actions/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { DashboardPanelAction } from './dashboard_panel_action';
|
||||
export { DashboardPanelActionsRegistryProvider } from './dashboard_panel_actions_registry';
|
||||
export { DashboardContextMenuPanel } from './dashboard_context_menu_panel';
|
|
@ -1,3 +1,5 @@
|
|||
import { PropTypes } from 'prop-types';
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbeddableMetadata - data that does not change over the course of the embeddables life span.
|
||||
* @property {string} title
|
||||
|
@ -5,6 +7,12 @@
|
|||
* @property {IndexPattern} indexPattern
|
||||
*/
|
||||
|
||||
export const embeddableShape = PropTypes.shape({
|
||||
metadata: PropTypes.object.isRequired,
|
||||
onContainerStateChanged: PropTypes.func.isRequired,
|
||||
render: PropTypes.func.isRequired,
|
||||
destroy: PropTypes.func.isRequired,
|
||||
});
|
||||
|
||||
export class Embeddable {
|
||||
/**
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export { EmbeddableFactory } from './embeddable_factory';
|
||||
export { Embeddable } from './embeddable';
|
||||
export * from './embeddable';
|
||||
export { EmbeddableFactoriesRegistryProvider } from './embeddable_factories_registry';
|
||||
|
|
|
@ -24,6 +24,7 @@ export {
|
|||
spyModes,
|
||||
chromeNavControls,
|
||||
navbarExtensions,
|
||||
dashboardPanelActions,
|
||||
managementSections,
|
||||
devTools,
|
||||
docViews,
|
||||
|
|
|
@ -20,6 +20,7 @@ export const visRequestHandlers = appExtension;
|
|||
export const visEditorTypes = appExtension;
|
||||
export const savedObjectTypes = appExtension;
|
||||
export const embeddableFactories = appExtension;
|
||||
export const dashboardPanelActions = appExtension;
|
||||
export const fieldFormats = appExtension;
|
||||
export const fieldFormatEditors = appExtension;
|
||||
export const spyModes = appExtension;
|
||||
|
|
|
@ -10,6 +10,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const filterBar = getService('filterBar');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects(['dashboard', 'header', 'visualize']);
|
||||
|
||||
describe('dashboard filtering', async () => {
|
||||
|
@ -153,9 +154,10 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await dashboardExpect.pieSliceCount(5);
|
||||
|
||||
await PageObjects.dashboard.clickEditVisualization();
|
||||
await dashboardPanelActions.clickEdit();
|
||||
await queryBar.setQuery('weightLbs:>50');
|
||||
await queryBar.submitQuery();
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await dashboardExpect.pieSliceCount(3);
|
||||
|
@ -168,7 +170,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('Nested visualization filter pills filters data as expected', async () => {
|
||||
await PageObjects.dashboard.clickEditVisualization();
|
||||
await dashboardPanelActions.clickEdit();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await PageObjects.dashboard.filterOnPieSlice('grr');
|
||||
|
|
|
@ -2,6 +2,7 @@ import expect from 'expect.js';
|
|||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const remote = getService('remote');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects(['dashboard']);
|
||||
|
||||
describe('dashboard grid', () => {
|
||||
|
@ -15,7 +16,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
// Specific test after https://github.com/elastic/kibana/issues/14764 fix
|
||||
it('Can move panel from bottom to top row', async () => {
|
||||
const lastVisTitle = 'Rendering Test: datatable';
|
||||
const panelTitleBeforeMove = await PageObjects.dashboard.getPanelHeading(lastVisTitle);
|
||||
const panelTitleBeforeMove = await dashboardPanelActions.getPanelHeading(lastVisTitle);
|
||||
const position1 = await panelTitleBeforeMove.getPosition();
|
||||
|
||||
remote
|
||||
|
@ -24,7 +25,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
.moveMouseTo(null, -20, -450)
|
||||
.releaseMouseButton();
|
||||
|
||||
const panelTitleAfterMove = await PageObjects.dashboard.getPanelHeading(lastVisTitle);
|
||||
const panelTitleAfterMove = await dashboardPanelActions.getPanelHeading(lastVisTitle);
|
||||
const position2 = await panelTitleAfterMove.getPosition();
|
||||
|
||||
expect(position1.y).to.be.greaterThan(position2.y);
|
||||
|
|
|
@ -8,6 +8,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
|
|||
const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'common']);
|
||||
const screenshot = getService('screenshots');
|
||||
const remote = getService('remote');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('dashboard snapshots', function describeIndexTests() {
|
||||
|
@ -38,7 +39,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
|
|||
await testSubjects.click('saveDashboardSuccess toastCloseButton');
|
||||
|
||||
await PageObjects.dashboard.clickFullScreenMode();
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
const percentSimilar = await screenshot.compareAgainstBaseline('tsvb_dashboard', updateBaselines);
|
||||
|
@ -59,7 +60,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
|
|||
await testSubjects.click('saveDashboardSuccess toastCloseButton');
|
||||
|
||||
await PageObjects.dashboard.clickFullScreenMode();
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
// The need for this should have been removed with https://github.com/elastic/kibana/pull/15574 but the
|
||||
|
|
|
@ -11,6 +11,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
const remote = getService('remote');
|
||||
const queryBar = getService('queryBar');
|
||||
const retry = getService('retry');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
|
||||
describe('dashboard state', function describeIndexTests() {
|
||||
|
@ -112,7 +113,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.visualize.closeSpyPanel();
|
||||
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.dashboard.clickEditVisualization();
|
||||
await dashboardPanelActions.clickEdit();
|
||||
|
||||
await PageObjects.visualize.clickMapZoomIn();
|
||||
await PageObjects.visualize.clickMapZoomIn();
|
||||
|
|
|
@ -2,6 +2,7 @@ import expect from 'expect.js';
|
|||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const retry = getService('retry');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects(['dashboard']);
|
||||
|
||||
describe('dashboard data-shared attributes', function describeIndexTests() {
|
||||
|
@ -37,7 +38,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('data-shared-item title should update a viz when using a custom panel title', async () => {
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
const CUSTOM_VIS_TITLE = 'ima custom title for a vis!';
|
||||
await PageObjects.dashboard.setCustomPanelTitle(CUSTOM_VIS_TITLE);
|
||||
await dashboardPanelActions.setCustomPanelTitle(CUSTOM_VIS_TITLE);
|
||||
await retry.try(async () => {
|
||||
const sharedData = await PageObjects.dashboard.getPanelSharedItemData();
|
||||
const foundSharedItemTitle = !!sharedData.find(item => {
|
||||
|
@ -48,7 +49,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('data-shared-item title is cleared with an empty panel title string', async () => {
|
||||
await PageObjects.dashboard.setCustomPanelTitle('h\b');
|
||||
await dashboardPanelActions.setCustomPanelTitle('h\b');
|
||||
await retry.try(async () => {
|
||||
const sharedData = await PageObjects.dashboard.getPanelSharedItemData();
|
||||
const foundSharedItemTitle = !!sharedData.find(item => {
|
||||
|
@ -59,7 +60,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('data-shared-item title can be reset', async () => {
|
||||
await PageObjects.dashboard.resetCustomPanelTitle();
|
||||
await dashboardPanelActions.resetCustomPanelTitle();
|
||||
await retry.try(async () => {
|
||||
const sharedData = await PageObjects.dashboard.getPanelSharedItemData();
|
||||
const foundOriginalSharedItemTitle = !!sharedData.find(item => {
|
||||
|
@ -71,7 +72,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('data-shared-item title should update a saved search when using a custom panel title', async () => {
|
||||
const CUSTOM_SEARCH_TITLE = 'ima custom title for a search!';
|
||||
await PageObjects.dashboard.setCustomPanelTitle(CUSTOM_SEARCH_TITLE, 'Rendering Test: saved search');
|
||||
await dashboardPanelActions.setCustomPanelTitle(CUSTOM_SEARCH_TITLE, 'Rendering Test: saved search');
|
||||
await retry.try(async () => {
|
||||
const sharedData = await PageObjects.dashboard.getPanelSharedItemData();
|
||||
const foundSharedItemTitle = !!sharedData.find(item => {
|
||||
|
|
|
@ -3,6 +3,7 @@ import expect from 'expect.js';
|
|||
export default function ({ getService, getPageObjects }) {
|
||||
const retry = getService('retry');
|
||||
const remote = getService('remote');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects(['dashboard', 'common']);
|
||||
|
||||
describe('full screen mode', async () => {
|
||||
|
@ -40,7 +41,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('displays exit full screen logo button when panel is expanded', async () => {
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
|
||||
const exists = await PageObjects.dashboard.exitFullScreenTextButtonExists();
|
||||
expect(exists).to.be(true);
|
||||
|
|
|
@ -6,9 +6,9 @@ import {
|
|||
} from '../../../../src/core_plugins/kibana/public/visualize/visualize_constants';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const remote = getService('remote');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'discover']);
|
||||
const dashboardName = 'Dashboard Panel Controls Test';
|
||||
|
@ -40,18 +40,24 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('are hidden in view mode', async function () {
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
const panelToggleMenu = await testSubjects.exists('dashboardPanelToggleMenuIcon');
|
||||
expect(panelToggleMenu).to.equal(false);
|
||||
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
const editLinkExists = await dashboardPanelActions.editPanelActionExists();
|
||||
const removeExists = await dashboardPanelActions.removePanelActionExists();
|
||||
|
||||
expect(editLinkExists).to.equal(false);
|
||||
expect(removeExists).to.equal(false);
|
||||
});
|
||||
|
||||
it('are shown in edit mode', async function () {
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
|
||||
const panelToggleMenu = await testSubjects.exists('dashboardPanelToggleMenuIcon');
|
||||
expect(panelToggleMenu).to.equal(true);
|
||||
await testSubjects.click('dashboardPanelToggleMenuIcon');
|
||||
const editLinkExists = await testSubjects.exists('dashboardPanelEditLink');
|
||||
const removeExists = await testSubjects.exists('dashboardPanelRemoveIcon');
|
||||
const isContextMenuIconVisible = await dashboardPanelActions.isContextMenuIconVisible();
|
||||
expect(isContextMenuIconVisible).to.equal(true);
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
|
||||
const editLinkExists = await dashboardPanelActions.editPanelActionExists();
|
||||
const removeExists = await dashboardPanelActions.removePanelActionExists();
|
||||
|
||||
expect(editLinkExists).to.equal(true);
|
||||
expect(removeExists).to.equal(true);
|
||||
|
@ -65,11 +71,11 @@ export default function ({ getService, getPageObjects }) {
|
|||
await remote.get(currentUrl.toString(), true);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await PageObjects.dashboard.showPanelEditControlsDropdownMenu();
|
||||
const editLinkExists = await testSubjects.exists('dashboardPanelEditLink');
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
const editLinkExists = await dashboardPanelActions.editPanelActionExists();
|
||||
expect(editLinkExists).to.equal(true);
|
||||
|
||||
const removeExists = await testSubjects.exists('dashboardPanelRemoveIcon');
|
||||
const removeExists = await dashboardPanelActions.removePanelActionExists();
|
||||
expect(removeExists).to.equal(true);
|
||||
|
||||
// Get rid of the timestamp in the url.
|
||||
|
@ -79,32 +85,33 @@ export default function ({ getService, getPageObjects }) {
|
|||
describe('on an expanded panel', function () {
|
||||
it('are hidden in view mode', async function () {
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
|
||||
const panelToggleMenu = await testSubjects.exists('dashboardPanelToggleMenuIcon');
|
||||
expect(panelToggleMenu).to.equal(false);
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
const editLinkExists = await dashboardPanelActions.editPanelActionExists();
|
||||
const removeExists = await dashboardPanelActions.removePanelActionExists();
|
||||
|
||||
expect(editLinkExists).to.equal(false);
|
||||
expect(removeExists).to.equal(false);
|
||||
});
|
||||
|
||||
it('in edit mode hides remove icons ', async function () {
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
|
||||
const panelToggleMenu = await testSubjects.exists('dashboardPanelToggleMenuIcon');
|
||||
expect(panelToggleMenu).to.equal(true);
|
||||
await testSubjects.click('dashboardPanelToggleMenuIcon');
|
||||
const editLinkExists = await testSubjects.exists('dashboardPanelEditLink');
|
||||
const removeExists = await testSubjects.exists('dashboardPanelRemoveIcon');
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
const editLinkExists = await dashboardPanelActions.editPanelActionExists();
|
||||
const removeExists = await dashboardPanelActions.removePanelActionExists();
|
||||
|
||||
expect(editLinkExists).to.equal(true);
|
||||
expect(removeExists).to.equal(false);
|
||||
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
});
|
||||
});
|
||||
|
||||
describe('visualization object edit menu', () => {
|
||||
it('opens a visualization when edit link is clicked', async () => {
|
||||
await testSubjects.click('dashboardPanelToggleMenuIcon');
|
||||
await PageObjects.dashboard.clickDashboardPanelEditLink();
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
await dashboardPanelActions.clickEdit();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const currentUrl = await remote.getCurrentUrl();
|
||||
expect(currentUrl).to.contain(VisualizeConstants.EDIT_PATH);
|
||||
|
@ -113,7 +120,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('deletes the visualization when delete link is clicked', async () => {
|
||||
await PageObjects.header.clickDashboard();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.dashboard.clickDashboardPanelRemoveIcon();
|
||||
await dashboardPanelActions.removePanel();
|
||||
|
||||
const panelCount = await PageObjects.dashboard.getPanelCount();
|
||||
expect(panelCount).to.be(0);
|
||||
|
@ -134,7 +141,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('opens a saved search when edit link is clicked', async () => {
|
||||
await PageObjects.dashboard.clickDashboardPanelEditLink();
|
||||
await dashboardPanelActions.clickEdit();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const queryName = await PageObjects.discover.getCurrentQueryName();
|
||||
expect(queryName).to.be('my search');
|
||||
|
@ -143,7 +150,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('deletes the saved search when delete link is clicked', async () => {
|
||||
await PageObjects.header.clickDashboard();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.dashboard.clickDashboardPanelRemoveIcon();
|
||||
await dashboardPanelActions.removePanel();
|
||||
|
||||
const panelCount = await PageObjects.dashboard.getPanelCount();
|
||||
expect(panelCount).to.be(0);
|
||||
|
@ -155,8 +162,8 @@ export default function ({ getService, getPageObjects }) {
|
|||
describe('panel expand control', function () {
|
||||
it('shown in edit mode', async function () {
|
||||
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);
|
||||
await testSubjects.click('dashboardPanelToggleMenuIcon');
|
||||
const expandExists = await testSubjects.exists('dashboardPanelExpandIcon');
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
const expandExists = await dashboardPanelActions.toggleExpandActionExists();
|
||||
expect(expandExists).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ import expect from 'expect.js';
|
|||
export default function ({ getService, getPageObjects }) {
|
||||
const retry = getService('retry');
|
||||
const remote = getService('remote');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects(['dashboard', 'visualize', 'header']);
|
||||
|
||||
describe('expanding a panel', () => {
|
||||
|
@ -11,7 +12,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('hides other panels', async () => {
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
await retry.try(async () => {
|
||||
const panelCount = await PageObjects.dashboard.getPanelCount();
|
||||
expect(panelCount).to.eql(1);
|
||||
|
@ -52,8 +53,9 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('shows other panels after being minimized', async () => {
|
||||
const panelCount = await PageObjects.dashboard.getPanelCount();
|
||||
// Panels are all minimized on a fresh open of a dashboard, so we need to re-expand in order to then minimize.
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
await dashboardPanelActions.toggleExpandPanel();
|
||||
|
||||
|
||||
// Add a retry to fix https://github.com/elastic/kibana/issues/14574. Perhaps the recent changes to this
|
||||
// being a CSS update is causing the UI to change slower than grabbing the panels?
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
FailureDebuggingProvider,
|
||||
VisualizeListingTableProvider,
|
||||
DashboardAddPanelProvider,
|
||||
DashboardPanelActionsProvider,
|
||||
} from './services';
|
||||
|
||||
export default async function ({ readConfigFile }) {
|
||||
|
@ -80,6 +81,7 @@ export default async function ({ readConfigFile }) {
|
|||
failureDebugging: FailureDebuggingProvider,
|
||||
visualizeListingTable: VisualizeListingTableProvider,
|
||||
dashboardAddPanel: DashboardAddPanelProvider,
|
||||
dashboardPanelActions: DashboardPanelActionsProvider,
|
||||
},
|
||||
servers: commonConfig.get('servers'),
|
||||
|
||||
|
|
|
@ -53,20 +53,6 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
await PageObjects.settings.clickDefaultIndexButton();
|
||||
}
|
||||
|
||||
async clickEditVisualization() {
|
||||
log.debug('clickEditVisualization');
|
||||
|
||||
// Edit link may sometimes be disabled if the embeddable isn't rendered yet.
|
||||
await retry.try(async () => {
|
||||
await this.showPanelEditControlsDropdownMenu();
|
||||
await testSubjects.click('dashboardPanelEditLink');
|
||||
const current = await remote.getCurrentUrl();
|
||||
if (current.indexOf('visualize') < 0) {
|
||||
throw new Error('not on visualize page');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async clickFullScreenMode() {
|
||||
log.debug(`clickFullScreenMode`);
|
||||
await testSubjects.click('dashboardFullScreenMode');
|
||||
|
@ -396,10 +382,6 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
return Promise.all(getTitlePromises);
|
||||
}
|
||||
|
||||
async getPanelHeading(title) {
|
||||
return await testSubjects.find(`dashboardPanelHeading-${title.replace(/\s/g, '')}`);
|
||||
}
|
||||
|
||||
async getPanelDimensions() {
|
||||
const panels = await find.allByCssSelector('.react-grid-item'); // These are gridster-defined elements and classes
|
||||
async function getPanelDimensions(panel) {
|
||||
|
@ -440,34 +422,10 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
return this.getTestVisualizations().map(visualization => visualization.description);
|
||||
}
|
||||
|
||||
async showPanelEditControlsDropdownMenu() {
|
||||
log.debug('showPanelEditControlsDropdownMenu');
|
||||
const editLinkExists = await testSubjects.exists('dashboardPanelEditLink');
|
||||
if (editLinkExists) return;
|
||||
|
||||
await retry.try(async () => {
|
||||
await testSubjects.click('dashboardPanelToggleMenuIcon');
|
||||
const editLinkExists = await testSubjects.exists('dashboardPanelEditLink');
|
||||
if (!editLinkExists) {
|
||||
throw new Error('No edit link exists, toggle menu not open. Try again.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getDashboardPanels() {
|
||||
return await testSubjects.findAll('dashboardPanel');
|
||||
}
|
||||
|
||||
async clickDashboardPanelEditLink() {
|
||||
await this.showPanelEditControlsDropdownMenu();
|
||||
await testSubjects.click('dashboardPanelEditLink');
|
||||
}
|
||||
|
||||
async clickDashboardPanelRemoveIcon() {
|
||||
await this.showPanelEditControlsDropdownMenu();
|
||||
await testSubjects.click('dashboardPanelRemoveIcon');
|
||||
}
|
||||
|
||||
async addVisualizations(visualizations) {
|
||||
await dashboardAddPanel.addVisualizations(visualizations);
|
||||
}
|
||||
|
@ -536,63 +494,6 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
}
|
||||
}
|
||||
|
||||
async arePanelMainMenuOptionsOpen(parent) {
|
||||
log.debug('arePanelMainMenuOptionsOpen');
|
||||
// Sub menu used arbitrarily - any option on the main menu panel would do.
|
||||
return parent ?
|
||||
await testSubjects.descendantExists('dashboardPanelOptionsSubMenuLink', parent) :
|
||||
await testSubjects.exists('dashboardPanelOptionsSubMenuLink');
|
||||
}
|
||||
|
||||
async openPanelOptions(parent) {
|
||||
log.debug('openPanelOptions');
|
||||
const panelOpen = await this.arePanelMainMenuOptionsOpen(parent);
|
||||
if (!panelOpen) {
|
||||
await retry.try(async () => {
|
||||
await (parent ? remote.moveMouseTo(parent) : testSubjects.moveMouseTo('dashboardPanelTitle'));
|
||||
const toggleMenuItem = parent ?
|
||||
await testSubjects.findDescendant('dashboardPanelToggleMenuIcon', parent) :
|
||||
await testSubjects.find('dashboardPanelToggleMenuIcon');
|
||||
await toggleMenuItem.click();
|
||||
const panelOpen = await this.arePanelMainMenuOptionsOpen(parent);
|
||||
if (!panelOpen) { throw new Error('Panel menu still not open'); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async toggleExpandPanel(parent) {
|
||||
await (parent ? remote.moveMouseTo(parent) : testSubjects.moveMouseTo('dashboardPanelTitle'));
|
||||
const expandShown = await testSubjects.exists('dashboardPanelExpandIcon');
|
||||
if (!expandShown) {
|
||||
await this.openPanelOptions(parent);
|
||||
}
|
||||
await testSubjects.click('dashboardPanelExpandIcon');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param customTitle
|
||||
* @param originalTitle - optional to specify which panel to change the title on.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async setCustomPanelTitle(customTitle, originalTitle) {
|
||||
log.debug(`setCustomPanelTitle(${customTitle}, ${originalTitle})`);
|
||||
let panelOptions = null;
|
||||
if (originalTitle) {
|
||||
panelOptions = await this.getPanelHeading(originalTitle);
|
||||
}
|
||||
await this.openPanelOptions(panelOptions);
|
||||
await testSubjects.click('dashboardPanelOptionsSubMenuLink');
|
||||
await testSubjects.setValue('customDashboardPanelTitleInput', customTitle);
|
||||
}
|
||||
|
||||
async resetCustomPanelTitle(panel) {
|
||||
log.debug('resetCustomPanelTitle');
|
||||
await this.openPanelOptions(panel);
|
||||
await testSubjects.click('dashboardPanelOptionsSubMenuLink');
|
||||
await testSubjects.click('resetCustomDashboardPanelTitle');
|
||||
}
|
||||
|
||||
async getSharedItemsCount() {
|
||||
log.debug('in getSharedItemsCount');
|
||||
const attributeName = 'data-shared-items-count';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export { DashboardVisualizationProvider } from './visualizations';
|
||||
export { DashboardExpectProvider } from './expectations';
|
||||
export { DashboardAddPanelProvider } from './add_panel';
|
||||
export { DashboardPanelActionsProvider } from './panel_actions';
|
||||
|
|
140
test/functional/services/dashboard/panel_actions.js
Normal file
140
test/functional/services/dashboard/panel_actions.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
const REMOVE_PANEL_DATA_TEST_SUBJ = 'dashboardPanelAction-deletePanel';
|
||||
const EDIT_PANEL_DATA_TEST_SUBJ = 'dashboardPanelAction-editPanel';
|
||||
const TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ = 'dashboardPanelAction-togglePanel';
|
||||
const CUSTOMIZE_PANEL_DATA_TEST_SUBJ = 'dashboardPanelAction-customizePanel';
|
||||
const OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ = 'dashboardPanelToggleMenuIcon';
|
||||
|
||||
export function DashboardPanelActionsProvider({ getService, getPageObjects }) {
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const remote = getService('remote');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['header', 'common']);
|
||||
|
||||
return new class DashboardPanelActions {
|
||||
|
||||
async isContextMenuOpen(parent) {
|
||||
log.debug('isContextMenuOpen');
|
||||
// Full screen toggle was chosen because it's available in both view and edit mode.
|
||||
return this.toggleExpandActionExists(parent);
|
||||
}
|
||||
|
||||
async findContextMenu(parent) {
|
||||
return parent ?
|
||||
await testSubjects.findDescendant(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ, parent) :
|
||||
await testSubjects.find(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async isContextMenuIconVisible() {
|
||||
log.debug('isContextMenuIconVisible');
|
||||
return await testSubjects.exists(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async openContextMenu(parent) {
|
||||
log.debug('openContextMenu');
|
||||
const panelOpen = await this.isContextMenuOpen(parent);
|
||||
if (!panelOpen) {
|
||||
await retry.try(async () => {
|
||||
await (parent ? remote.moveMouseTo(parent) : testSubjects.moveMouseTo('dashboardPanelTitle'));
|
||||
const toggleMenuItem = await this.findContextMenu(parent);
|
||||
await toggleMenuItem.click();
|
||||
const panelOpen = await this.isContextMenuOpen(parent);
|
||||
if (!panelOpen) { throw new Error('Context menu still not open'); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async toggleExpandPanel(parent) {
|
||||
log.debug('toggleExpandPanel');
|
||||
await (parent ? remote.moveMouseTo(parent) : testSubjects.moveMouseTo('dashboardPanelTitle'));
|
||||
const expandShown = await this.toggleExpandActionExists();
|
||||
if (!expandShown) {
|
||||
await this.openContextMenu(parent);
|
||||
}
|
||||
await this.toggleExpandPanel();
|
||||
}
|
||||
|
||||
async clickEdit() {
|
||||
log.debug('clickEdit');
|
||||
await this.openContextMenu();
|
||||
|
||||
// Edit link may sometimes be disabled if the embeddable isn't rendered yet.
|
||||
await retry.try(async () => {
|
||||
const editExists = await this.editPanelActionExists();
|
||||
if (editExists) {
|
||||
await testSubjects.click(EDIT_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.common.waitForTopNavToBeVisible();
|
||||
const current = await remote.getCurrentUrl();
|
||||
if (current.indexOf('dashboard') >= 0) {
|
||||
throw new Error('Still on dashboard');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async toggleExpandPanel() {
|
||||
log.debug('toggleExpandPanel');
|
||||
await this.openContextMenu();
|
||||
await testSubjects.click(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async removePanel() {
|
||||
log.debug('removePanel');
|
||||
await this.openContextMenu();
|
||||
await testSubjects.click(REMOVE_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async customizePanel(parent) {
|
||||
await this.openContextMenu(parent);
|
||||
await testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async removePanelActionExists() {
|
||||
log.debug('removePanelActionExists');
|
||||
return await testSubjects.exists(REMOVE_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async editPanelActionExists() {
|
||||
log.debug('editPanelActionExists');
|
||||
return await testSubjects.exists(EDIT_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async toggleExpandActionExists() {
|
||||
log.debug('toggleExpandActionExists');
|
||||
return await testSubjects.exists(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async customizePanelActionExists(parent) {
|
||||
return parent ?
|
||||
await testSubjects.descendantExists(CUSTOMIZE_PANEL_DATA_TEST_SUBJ, parent) :
|
||||
await testSubjects.exists(CUSTOMIZE_PANEL_DATA_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async getPanelHeading(title) {
|
||||
return await testSubjects.find(`dashboardPanelHeading-${title.replace(/\s/g, '')}`);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param customTitle
|
||||
* @param originalTitle - optional to specify which panel to change the title on.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async setCustomPanelTitle(customTitle, originalTitle) {
|
||||
log.debug(`setCustomPanelTitle(${customTitle}, ${originalTitle})`);
|
||||
let panelOptions = null;
|
||||
if (originalTitle) {
|
||||
panelOptions = await this.getPanelHeading(originalTitle);
|
||||
}
|
||||
await this.customizePanel(panelOptions);
|
||||
await testSubjects.setValue('customDashboardPanelTitleInput', customTitle);
|
||||
}
|
||||
|
||||
async resetCustomPanelTitle(panel) {
|
||||
log.debug('resetCustomPanelTitle');
|
||||
await this.customizePanel(panel);
|
||||
await testSubjects.click('resetCustomDashboardPanelTitle');
|
||||
}
|
||||
};
|
||||
}
|
|
@ -13,6 +13,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
const log = getService('log');
|
||||
const find = getService('find');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects([
|
||||
'security',
|
||||
'common',
|
||||
|
@ -163,17 +164,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('does not show the visualization edit icon', async () => {
|
||||
const editIconExists = await testSubjects.exists('dashboardPanelEditLink');
|
||||
expect(editIconExists).to.be(false);
|
||||
});
|
||||
|
||||
it('does not show the visualization move icon', async () => {
|
||||
const moveIconExists = await testSubjects.exists('dashboardPanelMoveIcon');
|
||||
expect(moveIconExists).to.be(false);
|
||||
const editLinkExists = await dashboardPanelActions.editPanelActionExists();
|
||||
expect(editLinkExists).to.be(false);
|
||||
});
|
||||
|
||||
it('does not show the visualization delete icon', async () => {
|
||||
const deleteIconExists = await testSubjects.exists('dashboardPanelRemoveIcon');
|
||||
const deleteIconExists = await dashboardPanelActions.removePanelActionExists();
|
||||
expect(deleteIconExists).to.be(false);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue