mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Dashboard] Editing toolbar update (#154966)
This commit is contained in:
parent
2049683cf0
commit
112a0f9abf
11 changed files with 52 additions and 114 deletions
|
@ -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()}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -42,6 +42,8 @@ export interface Props {
|
|||
legend: EuiButtonGroupProps['legend'];
|
||||
/** Array of `IconButton` */
|
||||
buttons: IconButton[];
|
||||
/** Button size */
|
||||
buttonSize?: EuiButtonGroupProps['buttonSize'];
|
||||
}
|
||||
|
||||
type Option = EuiButtonGroupOptionProps & Omit<IconButton, 'label'>;
|
||||
|
@ -49,7 +51,7 @@ 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);
|
||||
|
||||
|
@ -71,7 +73,7 @@ export const IconButtonGroup = ({ buttons, legend }: Props) => {
|
|||
|
||||
return (
|
||||
<EuiButtonGroup
|
||||
buttonSize="m"
|
||||
buttonSize={buttonSize}
|
||||
legend={legend}
|
||||
options={buttonGroupOptions}
|
||||
onChange={onChangeIconsMulti}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -27,6 +30,7 @@ export const ToolbarButton: React.FunctionComponent<Props> = ({
|
|||
label,
|
||||
type = 'empty',
|
||||
iconSide = 'left',
|
||||
size = 'm',
|
||||
...rest
|
||||
}) => {
|
||||
const euiTheme = useEuiTheme();
|
||||
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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',
|
||||
children,
|
||||
...popover
|
||||
}: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const onButtonClick = () => setIsOpen((status) => !status);
|
||||
|
@ -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' }}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -43,7 +43,7 @@ export const AddTimeSliderControlButton = ({ closePopover, controlGroup, ...rest
|
|||
return (
|
||||
<EuiContextMenuItem
|
||||
{...rest}
|
||||
icon="plusInCircle"
|
||||
icon="timeslider"
|
||||
onClick={async () => {
|
||||
await controlGroup.addTimeSliderControl();
|
||||
dashboard.scrollToTop();
|
||||
|
|
|
@ -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 }) => (
|
||||
|
|
|
@ -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 { useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
AddFromLibraryButton,
|
||||
IconButton,
|
||||
IconButtonGroup,
|
||||
Toolbar,
|
||||
ToolbarButton,
|
||||
} from '@kbn/shared-ux-button-toolbar';
|
||||
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';
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
/>,
|
||||
];
|
||||
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -239,7 +243,6 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) =>
|
|||
'data-test-subj': `dashboardEditorAggBasedMenuItem`,
|
||||
});
|
||||
}
|
||||
initialPanelItems.push(...toolVisTypes.map(getVisTypeMenuItem));
|
||||
|
||||
return [
|
||||
{
|
||||
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
@ -36,7 +37,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should create an image embeddable', async () => {
|
||||
// create an image embeddable
|
||||
await testSubjects.click(`dashboardQuickButtonimage`);
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await dashboardAddPanel.clickAddNewEmbeddableLink('image');
|
||||
await testSubjects.exists(`createImageEmbeddableFlyout`);
|
||||
await PageObjects.common.setFileInputPath(require.resolve('./elastic_logo.png'));
|
||||
await testSubjects.clickWhenNotDisabled(`imageEmbeddableEditorSave`);
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue