Dashboard/add panel flow (#59918) (#60995)

Added an emphasize prop to the top nav menu item and used it for a new 'Create new' button which redirects to the 'new visualization' modal.

Co-authored-by: Ryan Keairns <rkeairns@chef.io>

Co-authored-by: Ryan Keairns <rkeairns@chef.io>
This commit is contained in:
Devon Thomson 2020-03-24 10:01:28 -04:00 committed by GitHub
parent 51c9b97124
commit 1faabde190
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 152 additions and 34 deletions

View file

@ -754,7 +754,7 @@ export class DashboardAppController {
* When de-angularizing this code, please call the underlaying action function
* directly and not via the top nav object.
**/
navActions[TopNavIds.ADD]();
navActions[TopNavIds.ADD_EXISTING]();
};
$scope.enterEditMode = () => {
dashboardStateManager.setFullScreenMode(false);
@ -847,7 +847,8 @@ export class DashboardAppController {
showCloneModal(onClone, currentTitle);
};
navActions[TopNavIds.ADD] = () => {
navActions[TopNavIds.ADD_EXISTING] = () => {
if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
openAddPanelFlyout({
embeddable: dashboardContainer,

View file

@ -48,9 +48,10 @@ export function getTopNavConfig(
];
case ViewMode.EDIT:
return [
getCreateNewConfig(actions[TopNavIds.VISUALIZE]),
getSaveConfig(actions[TopNavIds.SAVE]),
getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]),
getAddConfig(actions[TopNavIds.ADD]),
getAddConfig(actions[TopNavIds.ADD_EXISTING]),
getOptionsConfig(actions[TopNavIds.OPTIONS]),
getShareConfig(actions[TopNavIds.SHARE]),
];
@ -161,6 +162,25 @@ function getAddConfig(action: NavAction) {
};
}
/**
* @returns {kbnTopNavConfig}
*/
function getCreateNewConfig(action: NavAction) {
return {
emphasize: true,
iconType: 'plusInCircle',
id: 'addNew',
label: i18n.translate('kbn.dashboard.topNave.addNewButtonAriaLabel', {
defaultMessage: 'Create new',
}),
description: i18n.translate('kbn.dashboard.topNave.addNewConfigDescription', {
defaultMessage: 'Create a new panel on this dashboard',
}),
testId: 'dashboardAddNewPanelButton',
run: action,
};
}
/**
* @returns {kbnTopNavConfig}
*/

View file

@ -18,7 +18,6 @@
*/
export const TopNavIds = {
ADD: 'add',
SHARE: 'share',
OPTIONS: 'options',
SAVE: 'save',
@ -27,4 +26,5 @@ export const TopNavIds = {
CLONE: 'clone',
FULL_SCREEN: 'fullScreenMode',
VISUALIZE: 'visualize',
ADD_EXISTING: 'addExisting',
};

View file

@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TopNavMenu Should render emphasized item which should be clickable 1`] = `
<EuiButton
fill={true}
iconSide="right"
iconType="beaker"
isDisabled={false}
onClick={[Function]}
size="s"
style={
Object {
"fontSize": "smaller",
}
}
>
Test
</EuiButton>
`;

View file

@ -1,7 +1,11 @@
.kbnTopNavMenu__wrapper {
z-index: 5;
.kbnTopNavMenu {
padding: $euiSizeS 0px $euiSizeXS;
.kbnTopNavMenu {
padding: $euiSizeS 0;
.kbnTopNavItemEmphasized {
padding: 0 $euiSizeS;
}
}
}

View file

@ -46,7 +46,11 @@ export function TopNavMenu(props: TopNavMenuProps) {
if (!config) return;
return config.map((menuItem: TopNavMenuData, i: number) => {
return (
<EuiFlexItem grow={false} key={`nav-menu-${i}`}>
<EuiFlexItem
grow={false}
key={`nav-menu-${i}`}
className={menuItem.emphasize ? 'kbnTopNavItemEmphasized' : ''}
>
<TopNavMenuItem {...menuItem} />
</EuiFlexItem>
);
@ -66,6 +70,7 @@ export function TopNavMenu(props: TopNavMenuProps) {
<EuiFlexGroup
data-test-subj="top-nav"
justifyContent="flexStart"
alignItems="center"
gutterSize="none"
className="kbnTopNavMenu"
responsive={false}

View file

@ -17,6 +17,8 @@
* under the License.
*/
import { ButtonIconSide } from '@elastic/eui';
export type TopNavMenuAction = (anchorElement: EventTarget) => void;
export interface TopNavMenuData {
@ -28,6 +30,9 @@ export interface TopNavMenuData {
className?: string;
disableButton?: boolean | (() => boolean);
tooltip?: string | (() => string);
emphasize?: boolean;
iconType?: string;
iconSide?: ButtonIconSide;
}
export interface RegisteredTopNavMenuData extends TopNavMenuData {

View file

@ -23,6 +23,15 @@ import { TopNavMenuData } from './top_nav_menu_data';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
describe('TopNavMenu', () => {
const ensureMenuItemDisabled = (data: TopNavMenuData) => {
const component = shallowWithIntl(<TopNavMenuItem {...data} />);
expect(component.prop('isDisabled')).toEqual(true);
const event = { currentTarget: { value: 'a' } };
component.simulate('click', event);
expect(data.run).toHaveBeenCalledTimes(0);
};
it('Should render and click an item', () => {
const data: TopNavMenuData = {
id: 'test',
@ -60,35 +69,62 @@ describe('TopNavMenu', () => {
expect(data.run).toHaveBeenCalled();
});
it('Should render disabled item and it shouldnt be clickable', () => {
it('Should render emphasized item which should be clickable', () => {
const data: TopNavMenuData = {
id: 'test',
label: 'test',
iconType: 'beaker',
iconSide: 'right',
emphasize: true,
run: jest.fn(),
};
const component = shallowWithIntl(<TopNavMenuItem {...data} />);
const event = { currentTarget: { value: 'a' } };
component.simulate('click', event);
expect(data.run).toHaveBeenCalledTimes(1);
expect(component).toMatchSnapshot();
});
it('Should render disabled item and it shouldnt be clickable', () => {
ensureMenuItemDisabled({
id: 'test',
label: 'test',
disableButton: true,
run: jest.fn(),
};
const component = shallowWithIntl(<TopNavMenuItem {...data} />);
expect(component.prop('isDisabled')).toEqual(true);
const event = { currentTarget: { value: 'a' } };
component.simulate('click', event);
expect(data.run).toHaveBeenCalledTimes(0);
});
});
it('Should render item with disable function and it shouldnt be clickable', () => {
const data: TopNavMenuData = {
ensureMenuItemDisabled({
id: 'test',
label: 'test',
disableButton: () => true,
run: jest.fn(),
};
});
});
const component = shallowWithIntl(<TopNavMenuItem {...data} />);
expect(component.prop('isDisabled')).toEqual(true);
it('Should render disabled emphasized item which shouldnt be clickable', () => {
ensureMenuItemDisabled({
id: 'test',
label: 'test',
iconType: 'beaker',
iconSide: 'right',
emphasize: true,
disableButton: true,
run: jest.fn(),
});
});
const event = { currentTarget: { value: 'a' } };
component.simulate('click', event);
expect(data.run).toHaveBeenCalledTimes(0);
it('Should render emphasized item with disable function and it shouldnt be clickable', () => {
ensureMenuItemDisabled({
id: 'test',
label: 'test',
iconType: 'beaker',
iconSide: 'right',
emphasize: true,
disableButton: () => true,
run: jest.fn(),
});
});
});

View file

@ -21,6 +21,7 @@ import { capitalize, isFunction } from 'lodash';
import React, { MouseEvent } from 'react';
import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
import { EuiButton } from '@elastic/eui';
import { TopNavMenuData } from './top_nav_menu_data';
export function TopNavMenuItem(props: TopNavMenuData) {
@ -39,14 +40,20 @@ export function TopNavMenuItem(props: TopNavMenuData) {
props.run(e.currentTarget);
}
const btn = (
<EuiButtonEmpty
size="xs"
isDisabled={isDisabled()}
onClick={handleClick}
data-test-subj={props.testId}
className={props.className}
>
const commonButtonProps = {
isDisabled: isDisabled(),
onClick: handleClick,
iconType: props.iconType,
iconSide: props.iconSide,
'data-test-subj': props.testId,
};
const btn = props.emphasize ? (
<EuiButton {...commonButtonProps} size="s" fill style={{ fontSize: 'smaller' }}>
{capitalize(props.label || props.id!)}
</EuiButton>
) : (
<EuiButtonEmpty {...commonButtonProps} size="xs">
{capitalize(props.label || props.id!)}
</EuiButtonEmpty>
);
@ -54,9 +61,8 @@ export function TopNavMenuItem(props: TopNavMenuData) {
const tooltip = getTooltip();
if (tooltip) {
return <EuiToolTip content={tooltip}>{btn}</EuiToolTip>;
} else {
return btn;
}
return btn;
}
TopNavMenuItem.defaultProps = {

View file

@ -41,9 +41,24 @@ export default function({ getService, getPageObjects }) {
});
describe('add new visualization link', () => {
it('adds a new visualization', async () => {
it('adds new visualiztion via the top nav link', async () => {
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await PageObjects.dashboard.switchToEditMode();
await dashboardAddPanel.clickCreateNewLink();
await PageObjects.visualize.clickAreaChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.visualize.saveVisualizationExpectSuccess(
'visualization from top nav add new panel'
);
await retry.try(async () => {
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(originalPanelCount + 1);
});
await PageObjects.dashboard.waitForRenderComplete();
});
it('adds a new visualization', async () => {
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await dashboardAddPanel.ensureAddPanelIsShowing();
await dashboardAddPanel.clickAddNewEmbeddableLink('visualization');
await PageObjects.visualize.clickAreaChart();

View file

@ -30,6 +30,13 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) {
await testSubjects.click('dashboardAddPanelButton');
}
async clickCreateNewLink() {
log.debug('DashboardAddPanel.clickAddNewPanelButton');
await testSubjects.click('dashboardAddNewPanelButton');
// Give some time for the animation to complete
await PageObjects.common.sleep(500);
}
async clickAddNewEmbeddableLink(type) {
await testSubjects.click('createNew');
await testSubjects.click(`createNew-${type}`);