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

[Dashboard] Editing toolbar update #154966

Merged
merged 13 commits into from Jul 5, 2023
Expand Up @@ -22,12 +22,13 @@ const label = {
/**
* A button that acts to add an item from the library to a solution, typically through a modal.
*/
export const AddFromLibraryButton = ({ onClick, ...rest }: Props) => (
export const AddFromLibraryButton = ({ onClick, size = 'm', ...rest }: Props) => (
<ToolbarButton
{...rest}
type="empty"
onClick={onClick}
iconType="folderOpen"
size={size}
label={label.getLibraryButtonLabel()}
/>
);
Expand Up @@ -42,14 +42,16 @@ export interface Props {
legend: EuiButtonGroupProps['legend'];
/** Array of `IconButton` */
buttons: IconButton[];
/** Button size */
buttonSize?: EuiButtonGroupProps['buttonSize'];
}

type Option = EuiButtonGroupOptionProps & Omit<IconButton, 'label'>;

/**
* A group of buttons each performing an action, represented by an icon.
*/
export const IconButtonGroup = ({ buttons, legend }: Props) => {
export const IconButtonGroup = ({ buttons, legend, buttonSize = 'm' }: Props) => {
const euiTheme = useEuiTheme();
const iconButtonGroupStyles = IconButtonGroupStyles(euiTheme);

Expand All @@ -71,7 +73,7 @@ export const IconButtonGroup = ({ buttons, legend }: Props) => {

return (
<EuiButtonGroup
buttonSize="m"
buttonSize={buttonSize}
legend={legend}
options={buttonGroupOptions}
onChange={onChangeIconsMulti}
Expand Down
Expand Up @@ -18,7 +18,10 @@ type ToolbarButtonTypes = 'primary' | 'empty';
* Props for `PrimaryButton`.
*/
export interface Props
extends Pick<EuiButtonPropsForButton, 'onClick' | 'iconType' | 'iconSide' | 'data-test-subj'> {
extends Pick<
EuiButtonPropsForButton,
'onClick' | 'iconType' | 'iconSide' | 'size' | 'data-test-subj'
> {
label: string;
type?: ToolbarButtonTypes;
}
Expand All @@ -27,6 +30,7 @@ export const ToolbarButton: React.FunctionComponent<Props> = ({
label,
type = 'empty',
iconSide = 'left',
size = 'm',
...rest
}) => {
const euiTheme = useEuiTheme();
Expand All @@ -36,7 +40,7 @@ export const ToolbarButton: React.FunctionComponent<Props> = ({
: { color: 'text', css: ToolbarButtonStyles(euiTheme).emptyButton };

return (
<EuiButton size="m" {...toolbarButtonStyleProps} {...{ iconSide, ...rest }}>
<EuiButton size={size} {...toolbarButtonStyleProps} {...{ iconSide, ...rest }}>
{label}
</EuiButton>
);
Expand Down
10 changes: 9 additions & 1 deletion packages/shared-ux/button_toolbar/src/popover/popover.tsx
Expand Up @@ -29,7 +29,14 @@ export type Props = AllowedButtonProps &
/**
* A button which opens a popover of additional actions within the toolbar.
*/
export const ToolbarPopover = ({ type, label, iconType, children, ...popover }: Props) => {
export const ToolbarPopover = ({
type,
label,
iconType,
size = 'm',
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice call to have the size here default to medium. That way we don't have to change Canvas at all.

children,
...popover
}: Props) => {
const [isOpen, setIsOpen] = useState(false);

const onButtonClick = () => setIsOpen((status) => !status);
Expand All @@ -38,6 +45,7 @@ export const ToolbarPopover = ({ type, label, iconType, children, ...popover }:
const button = (
<ToolbarButton
onClick={onButtonClick}
size={size}
{...{ type, label, iconType: iconType || 'arrowDown', iconSide: iconType ? 'left' : 'right' }}
/>
);
Expand Down
Expand Up @@ -43,7 +43,7 @@ export const AddTimeSliderControlButton = ({ closePopover, controlGroup, ...rest
return (
<EuiContextMenuItem
{...rest}
icon="plusInCircle"
icon="timeslider"
onClick={async () => {
await controlGroup.addTimeSliderControl();
dashboard.scrollToTop();
Expand Down
Expand Up @@ -27,6 +27,8 @@ export function ControlsToolbarButton({ controlGroup }: { controlGroup: ControlG
panelPaddingSize="none"
label={getControlButtonTitle()}
zIndex={Number(euiTheme.levels.header) - 1}
size="s"
iconType="controlsHorizontal"
data-test-subj="dashboard-controls-menu-button"
>
{({ closePopover }: { closePopover: () => void }) => (
Expand Down
Expand Up @@ -9,22 +9,13 @@
import { css } from '@emotion/react';
import React, { useCallback } from 'react';
import { METRIC_TYPE } from '@kbn/analytics';
import { IconType, useEuiTheme } from '@elastic/eui';

import {
AddFromLibraryButton,
IconButton,
IconButtonGroup,
Toolbar,
ToolbarButton,
} from '@kbn/shared-ux-button-toolbar';
import { useEuiTheme } from '@elastic/eui';

import { AddFromLibraryButton, Toolbar, ToolbarButton } from '@kbn/shared-ux-button-toolbar';
import { EmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public';

import {
getCreateVisualizationButtonTitle,
getQuickCreateButtonGroupLegend,
} from '../_dashboard_app_strings';
import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings';
import { EditorMenu } from './editor_menu';
import { useDashboardAPI } from '../dashboard_app';
import { pluginServices } from '../../services/plugin_services';
Expand All @@ -37,8 +28,8 @@ export function DashboardEditingToolbar() {
usageCollection,
data: { search },
notifications: { toasts },
embeddable: { getStateTransfer, getEmbeddableFactory },
visualizations: { get: getVisualization, getAliases: getVisTypeAliases },
embeddable: { getStateTransfer },
visualizations: { getAliases: getVisTypeAliases },
} = pluginServices.getServices();
const { euiTheme } = useEuiTheme();

Expand All @@ -47,13 +38,6 @@ export function DashboardEditingToolbar() {
const stateTransferService = getStateTransfer();

const lensAlias = getVisTypeAliases().find(({ name }) => name === 'lens');
const quickButtonVisTypes: Array<
{ type: 'vis'; visType: string } | { type: 'embeddable'; embeddableType: string }
> = [
{ type: 'vis', visType: 'markdown' },
{ type: 'embeddable', embeddableType: 'image' },
{ type: 'vis', visType: 'maps' },
];

const trackUiMetric = usageCollection.reportUiCounter?.bind(
usageCollection,
Expand Down Expand Up @@ -121,61 +105,11 @@ export function DashboardEditingToolbar() {
[trackUiMetric, dashboard, toasts]
);

const getVisTypeQuickButton = (
quickButtonForType: typeof quickButtonVisTypes[0]
): IconButton | undefined => {
if (quickButtonForType.type === 'vis') {
const visTypeName = quickButtonForType.visType;
const visType =
getVisualization(visTypeName) ||
getVisTypeAliases().find(({ name }) => name === visTypeName);

if (visType) {
if ('aliasPath' in visType) {
const { name, icon, title } = visType as VisTypeAlias;
return {
label: title,
iconType: icon,
onClick: createNewVisType(visType as VisTypeAlias),
'data-test-subj': `dashboardQuickButton${name}`,
};
} else {
const { name, icon, title, titleInWizard } = visType as BaseVisType & { icon: IconType };
return {
label: titleInWizard || title,
iconType: icon,
onClick: createNewVisType(visType as BaseVisType),
'data-test-subj': `dashboardQuickButton${name}`,
};
}
}
} else {
const embeddableType = quickButtonForType.embeddableType;
const embeddableFactory = getEmbeddableFactory(embeddableType);
if (embeddableFactory) {
return {
label: embeddableFactory.getDisplayName(),
iconType: embeddableFactory.getIconType(),
onClick: () => {
if (embeddableFactory) {
createNewEmbeddable(embeddableFactory);
}
},
'data-test-subj': `dashboardQuickButton${embeddableType}`,
};
}
}
};

const quickButtons: IconButton[] = quickButtonVisTypes.reduce((accumulator, type) => {
const button = getVisTypeQuickButton(type);
return button ? [...accumulator, button] : accumulator;
}, [] as IconButton[]);

const extraButtons = [
<EditorMenu createNewVisType={createNewVisType} createNewEmbeddable={createNewEmbeddable} />,
<AddFromLibraryButton
onClick={() => dashboard.addFromLibrary()}
size="s"
data-test-subj="dashboardAddPanelButton"
/>,
];
Expand All @@ -195,14 +129,12 @@ export function DashboardEditingToolbar() {
<ToolbarButton
type="primary"
iconType="lensApp"
size="s"
onClick={createNewVisType(lensAlias)}
label={getCreateVisualizationButtonTitle()}
data-test-subj="dashboardAddNewPanelButton"
/>
),
iconButtonGroup: (
<IconButtonGroup buttons={quickButtons} legend={getQuickCreateButtonGroupLegend()} />
),
extraButtons,
}}
</Toolbar>
Expand Down
Expand Up @@ -87,16 +87,18 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) =>

const getSortedVisTypesByGroup = (group: VisGroups) =>
getVisTypesByGroup(group)
.sort(({ name: a }: BaseVisType | VisTypeAlias, { name: b }: BaseVisType | VisTypeAlias) => {
if (a < b) {
.sort((a: BaseVisType | VisTypeAlias, b: BaseVisType | VisTypeAlias) => {
const labelA = 'titleInWizard' in a ? a.titleInWizard || a.title : a.title;
const labelB = 'titleInWizard' in b ? b.titleInWizard || a.title : a.title;
if (labelA < labelB) {
return -1;
}
if (a > b) {
if (labelA > labelB) {
return 1;
}
return 0;
})
.filter(({ disableCreate, stage }: BaseVisType) => !disableCreate);
.filter(({ disableCreate }: BaseVisType) => !disableCreate);

const promotedVisTypes = getSortedVisTypesByGroup(VisGroups.PROMOTED);
const aggsBasedVisTypes = getSortedVisTypesByGroup(VisGroups.AGGBASED);
Expand Down Expand Up @@ -220,15 +222,17 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) =>
const getEditorMenuPanels = (closePopover: () => void) => {
const initialPanelItems = [
...visTypeAliases.map(getVisTypeAliasMenuItem),
...toolVisTypes.map(getVisTypeMenuItem),
...ungroupedFactories.map((factory) => {
return getEmbeddableFactoryMenuItem(factory, closePopover);
}),
...Object.values(factoryGroupMap).map(({ id, appName, icon, panelId }) => ({
name: appName,
icon,
panel: panelId,
'data-test-subj': `dashboardEditorMenu-${id}Group`,
})),
...ungroupedFactories.map((factory) => {
return getEmbeddableFactoryMenuItem(factory, closePopover);
}),

...promotedVisTypes.map(getVisTypeMenuItem),
];
if (aggsBasedVisTypes.length > 0) {
Expand All @@ -239,7 +243,6 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) =>
'data-test-subj': `dashboardEditorAggBasedMenuItem`,
});
}
initialPanelItems.push(...toolVisTypes.map(getVisTypeMenuItem));

return [
{
Expand Down Expand Up @@ -268,8 +271,10 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) =>
repositionOnScroll
ownFocus
label={i18n.translate('dashboard.solutionToolbar.editorMenuButtonLabel', {
defaultMessage: 'Select type',
defaultMessage: 'Add panel',
})}
size="s"
iconType="plusInCircle"
panelPaddingSize="none"
data-test-subj="dashboardEditorMenuButton"
>
Expand Down
Expand Up @@ -103,21 +103,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.dashboard.waitForRenderComplete();
});

it('adds a markdown visualization via the quick button', async () => {
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await dashboardAddPanel.clickMarkdownQuickButton();
await PageObjects.visualize.saveVisualizationExpectSuccess(
'visualization from markdown quick button',
{ redirectToOrigin: true }
);

await retry.try(async () => {
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(originalPanelCount + 1);
});
await PageObjects.dashboard.waitForRenderComplete();
});

it('saves the listing page instead of the visualization to the app link', async () => {
await PageObjects.header.clickVisualize(true);
const currentUrl = await browser.getCurrentUrl();
Expand Down
Expand Up @@ -12,6 +12,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard', 'discover', 'header']);
const testSubjects = getService('testSubjects');
const dashboardAddPanel = getService('dashboardAddPanel');
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');

Expand All @@ -36,6 +37,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {

it('should create an image embeddable', async () => {
// create an image embeddable
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAddNewEmbeddableLink('image');
await testSubjects.click(`dashboardQuickButtonimage`);
await testSubjects.exists(`createImageEmbeddableFlyout`);
await PageObjects.common.setFileInputPath(require.resolve('./elastic_logo.png'));
Expand Down
11 changes: 4 additions & 7 deletions test/functional/services/dashboard/add_panel.ts
Expand Up @@ -38,17 +38,14 @@ export class DashboardAddPanelService extends FtrService {
});
}

async clickQuickButton(visType: string) {
this.log.debug(`DashboardAddPanel.clickQuickButton${visType}`);
await this.testSubjects.click(`dashboardQuickButton${visType}`);
}

async clickMarkdownQuickButton() {
await this.clickQuickButton('markdown');
await this.clickEditorMenuButton();
await this.clickVisType('markdown');
}

async clickMapQuickButton() {
await this.clickQuickButton('map');
await this.clickEditorMenuButton();
await this.clickVisType('map');
}

async clickEditorMenuButton() {
Expand Down