Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a feature for custom panel titles #14831

Merged
9 changes: 9 additions & 0 deletions src/core_plugins/kibana/public/dashboard/actions/panels.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { createAction } from 'redux-actions';
export const deletePanel = createAction('DELETE_PANEL');

export const updatePanel = createAction('UPDATE_PANEL');
export const resetPanelTitle = createAction('RESET_PANEl_TITLE');
export const setPanelTitle = createAction('SET_PANEl_TITLE',
/**
* @param title {string}
* @param panelIndex {string}
*/
(title, panelIndex) => ({ title, panelIndex })
);


function panelArrayToMap(panels) {
const panelsMap = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ exports[`DashboardPanel matches snapshot 1`] = `
class="kuiMicroButtonGroup"
>
<div
class="kuiPopover kuiPopover--anchorRight dashboardPanelPopOver"
class="kuiPopover kuiPopover--anchorRight dashboardPanelPopOver kuiPopover--withTitle"
>
<span
aria-label="Panel options"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { DashboardPanel } from './dashboard_panel';
import { DashboardViewMode } from '../dashboard_view_mode';
import { PanelError } from '../panel/panel_error';
import { store } from '../../store';
import { updateViewMode } from '../actions';
import {
updateViewMode,
setPanels,
} from '../actions';
import { Provider } from 'react-redux';
import { getEmbeddableFactoryMock } from '../__tests__/get_embeddable_factories_mock';

Expand All @@ -26,6 +29,7 @@ function getProps(props = {}) {

beforeAll(() => {
store.dispatch(updateViewMode(DashboardViewMode.EDIT));
store.dispatch(setPanels([{ panelIndex: 'foo1' }]));
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this doing? Why foo1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's setting up redux so that the dashboard will be rendered with a single panel with a panel id/index of foo1 . foo1 could be anything as long as it matches the defaultTestProps panel above. Without this call it'll break because you will be passing in props that say render panel foo1 but that panel won't exist in the store, so I'll get an error like Cannot read property 'title' of undefined.

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry, I did not see this was a test file. Disregard the questions

});

test('DashboardPanel matches snapshot', () => {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';

export function PanelHeader({ title, actions }) {
export function PanelHeader({ title, actions, isViewOnlyMode }) {
if (isViewOnlyMode && !title) {
return (
<div className="panel-heading-floater">
<div className="kuiMicroButtonGroup">
{actions}
</div>
</div>
);
}

return (
<div className="panel-heading">
<span
Expand All @@ -21,6 +31,7 @@ export function PanelHeader({ title, actions }) {
}

PanelHeader.propTypes = {
isViewOnlyMode: PropTypes.bool,
title: PropTypes.string,
actions: PropTypes.node,
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ import {

import {
getEmbeddable,
getEmbeddableTitle,
getPanel,
getMaximizedPanelId,
getFullScreenMode,
getViewMode
} from '../../selectors';

const mapStateToProps = ({ dashboard }, { panelId }) => {
const embeddable = getEmbeddable(dashboard, panelId);
const panel = getPanel(dashboard, panelId);
const embeddableTitle = embeddable ? embeddable.title : '';
return {
title: embeddable ? getEmbeddableTitle(dashboard, panelId) : '',
title: panel.title === undefined ? embeddableTitle : panel.title,
isExpanded: getMaximizedPanelId(dashboard) === panelId,
isViewOnlyMode: getFullScreenMode(dashboard) || getViewMode(dashboard) === DashboardViewMode.VIEW,
};
Expand Down Expand Up @@ -57,6 +59,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {
title,
actions,
isViewOnlyMode,
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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,
embeddableRenderFinished,
} from '../../actions';
import { getEmbeddableFactoryMock } from '../../__tests__/get_embeddable_factories_mock';
import {
TestSubjects,
} from 'ui_framework/src/test';

function getProps(props = {}) {
const defaultTestProps = {
panelId: 'foo1',
embeddableFactory: getEmbeddableFactoryMock(),
};
return _.defaultsDeep(props, defaultTestProps);
}

let component;

beforeAll(() => {
store.dispatch(updateViewMode(DashboardViewMode.EDIT));
store.dispatch(setPanels([{ panelIndex: 'foo1' }]));
store.dispatch(embeddableRenderFinished('foo1', { title: 'my embeddable title', editUrl: 'editme' }));
});

afterAll(() => {
component.unmount();
});

test('Panel header shows embeddable title when nothing is set on the panel', () => {
component = mount(<Provider store={store}><PanelHeaderContainer {...getProps()} /></Provider>);
expect(TestSubjects.getText(component, 'dashboardPanelTitle')).toBe('my embeddable title');
});

test('Panel header shows panel title when it is set on the panel', () => {
store.dispatch(setPanelTitle('my custom panel title', 'foo1'));
expect(TestSubjects.getText(component, 'dashboardPanelTitle')).toBe('my custom panel title');
});

test('Panel header shows no panel title when it is set to an empty string on the panel', () => {
store.dispatch(setPanelTitle('', 'foo1'));
expect(TestSubjects.getText(component, 'dashboardPanelTitle')).toBe('');
});

test('Panel header shows embeddable title when the panel title is reset', () => {
store.dispatch(resetPanelTitle('foo1'));
expect(TestSubjects.getText(component, 'dashboardPanelTitle')).toBe('my embeddable title');
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
KuiPopover,
KuiContextMenuPanel,
KuiContextMenu,
KuiKeyboardAccessible,
} from 'ui_framework/components';

import { EditMenuItem } from './edit_menu_item';
import { DeleteMenuItem } from './delete_menu_item';
import { ExpandOrCollapseMenuItem } from './expand_or_collapse_menu_item';
import { PanelOptionsMenuForm } from './panel_options_menu_form';

export class PanelOptionsMenu extends React.Component {
state = {
Expand All @@ -35,23 +33,74 @@ export class PanelOptionsMenu extends React.Component {
this.props.toggleExpandedPanel();
};

renderItems() {
const items = [
<EditMenuItem
key="0"
onEditPanel={this.onEditPanel}
/>,
<ExpandOrCollapseMenuItem
key="2"
onToggleExpand={this.onToggleExpandPanel}
isExpanded={this.props.isExpanded}
/>
buildMainMenuPanel() {
const { isExpanded } = this.props;
const mainPanelMenuItems = [
{
name: 'Edit visualization',
'data-test-subj': 'dashboardPanelEditLink',
icon: <span
aria-hidden="true"
className="kuiButton__icon kuiIcon fa-edit"
/>,
onClick: this.onEditPanel,
},
{
name: 'Customize panel',
'data-test-subj': 'dashboardPanelOptionsSubMenuLink',
icon: <span
aria-hidden="true"
className="kuiButton__icon kuiIcon fa-edit"
/>,
panel: 'panelSubOptionsMenu',
},
{
name: isExpanded ? 'Minimize' : 'Full screen',
'data-test-subj': 'dashboardPanelExpandIcon',
icon: <span
aria-hidden="true"
className={`kuiButton__icon kuiIcon ${isExpanded ? 'fa-compress' : 'fa-expand'}`}
/>,
onClick: this.onToggleExpandPanel,
}
];
if (!this.props.isExpanded) {
items.push(<DeleteMenuItem key="3" onDeletePanel={this.onDeletePanel} />);
mainPanelMenuItems.push({
name: 'Delete from dashboard',
'data-test-subj': 'dashboardPanelRemoveIcon',
icon: <span
aria-hidden="true"
className="kuiButton__icon kuiIcon fa-trash"
/>,
onClick: this.onDeletePanel,
});
}

return items;
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() {
Expand All @@ -74,17 +123,21 @@ export class PanelOptionsMenu extends React.Component {
closePopover={this.closePopover}
panelPaddingSize="none"
anchorPosition="right"
withTitle
>
<KuiContextMenuPanel
onClose={this.closePopover}
items={this.renderItems()}
<KuiContextMenu
initialPanelId="mainMenu"
panels={this.renderPanels()}
/>
</KuiPopover>
);
}
}

PanelOptionsMenu.propTypes = {
panelTitle: PropTypes.string,
onUpdatePanelTitle: PropTypes.func.isRequired,
onResetPanelTitle: PropTypes.func.isRequired,
editUrl: PropTypes.string.isRequired,
toggleExpandedPanel: PropTypes.func.isRequired,
isExpanded: PropTypes.bool.isRequired,
Expand Down
Loading