mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Dashboard Usability] Unified dashboard settings (#153862)
## Summary Adds flyout for changing individual dashboard settings such as title, description, tags, and save time with dashboard. This also moves the existing dashboard options (show panel titles, sync colors, use margins, sync cursor, and sync tooltips) into the flyout. Fixes #144532 [Flaky test runner](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2055) ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
7ebfc6e6cd
commit
b692e347f4
24 changed files with 791 additions and 324 deletions
|
@ -363,7 +363,7 @@ Apply a set of design options to the entire dashboard.
|
|||
|
||||
. If you're in view mode, click *Edit* in the toolbar.
|
||||
|
||||
. In the toolbar, *Options*, then use the following options:
|
||||
. In the toolbar, click *Settings*, to open the *Dashboard settings* flyout, then use the following options:
|
||||
|
||||
* *Use margins between panels* — Adds a margin of space between each panel.
|
||||
|
||||
|
|
|
@ -323,12 +323,12 @@ export const topNavStrings = {
|
|||
defaultMessage: 'Share Dashboard',
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
label: i18n.translate('dashboard.topNave.optionsButtonAriaLabel', {
|
||||
defaultMessage: 'options',
|
||||
settings: {
|
||||
label: i18n.translate('dashboard.topNave.settingsButtonAriaLabel', {
|
||||
defaultMessage: 'settings',
|
||||
}),
|
||||
description: i18n.translate('dashboard.topNave.optionsConfigDescription', {
|
||||
defaultMessage: 'Options',
|
||||
description: i18n.translate('dashboard.topNave.settingsConfigDescription', {
|
||||
defaultMessage: 'Open dashboard settings',
|
||||
}),
|
||||
},
|
||||
clone: {
|
||||
|
|
|
@ -55,6 +55,7 @@ export const useDashboardMenuItems = ({
|
|||
const dispatch = useEmbeddableDispatch();
|
||||
|
||||
const hasUnsavedChanges = select((state) => state.componentState.hasUnsavedChanges);
|
||||
const hasOverlays = select((state) => state.componentState.hasOverlays);
|
||||
const lastSavedId = select((state) => state.componentState.lastSavedId);
|
||||
const dashboardTitle = select((state) => state.explicitInput.title);
|
||||
|
||||
|
@ -169,13 +170,13 @@ export const useDashboardMenuItems = ({
|
|||
emphasize: true,
|
||||
isLoading: isSaveInProgress,
|
||||
testId: 'dashboardQuickSaveMenuItem',
|
||||
disableButton: !hasUnsavedChanges || isSaveInProgress,
|
||||
disableButton: !hasUnsavedChanges || isSaveInProgress || hasOverlays,
|
||||
run: () => quickSaveDashboard(),
|
||||
} as TopNavMenuData,
|
||||
|
||||
saveAs: {
|
||||
description: topNavStrings.saveAs.description,
|
||||
disableButton: isSaveInProgress,
|
||||
disableButton: isSaveInProgress || hasOverlays,
|
||||
id: 'save',
|
||||
emphasize: !Boolean(lastSavedId),
|
||||
testId: 'dashboardSaveMenuItem',
|
||||
|
@ -187,7 +188,7 @@ export const useDashboardMenuItems = ({
|
|||
switchToViewMode: {
|
||||
...topNavStrings.switchToViewMode,
|
||||
id: 'cancel',
|
||||
disableButton: isSaveInProgress || !lastSavedId,
|
||||
disableButton: isSaveInProgress || !lastSavedId || hasOverlays,
|
||||
testId: 'dashboardViewOnlyMode',
|
||||
run: () => returnToViewMode(),
|
||||
} as TopNavMenuData,
|
||||
|
@ -196,16 +197,16 @@ export const useDashboardMenuItems = ({
|
|||
...topNavStrings.share,
|
||||
id: 'share',
|
||||
testId: 'shareTopNavButton',
|
||||
disableButton: isSaveInProgress,
|
||||
disableButton: isSaveInProgress || hasOverlays,
|
||||
run: showShare,
|
||||
} as TopNavMenuData,
|
||||
|
||||
options: {
|
||||
...topNavStrings.options,
|
||||
id: 'options',
|
||||
testId: 'dashboardOptionsButton',
|
||||
disableButton: isSaveInProgress,
|
||||
run: (anchor) => dashboardContainer.showOptions(anchor),
|
||||
settings: {
|
||||
...topNavStrings.settings,
|
||||
id: 'settings',
|
||||
testId: 'dashboardSettingsButton',
|
||||
disableButton: isSaveInProgress || hasOverlays,
|
||||
run: () => dashboardContainer.showSettings(),
|
||||
} as TopNavMenuData,
|
||||
|
||||
clone: {
|
||||
|
@ -220,6 +221,7 @@ export const useDashboardMenuItems = ({
|
|||
quickSaveDashboard,
|
||||
dashboardContainer,
|
||||
hasUnsavedChanges,
|
||||
hasOverlays,
|
||||
setFullScreenMode,
|
||||
isSaveInProgress,
|
||||
returnToViewMode,
|
||||
|
@ -252,7 +254,7 @@ export const useDashboardMenuItems = ({
|
|||
} else {
|
||||
editModeItems.push(menuItems.switchToViewMode, menuItems.saveAs);
|
||||
}
|
||||
return [...labsMenuItem, menuItems.options, ...shareMenuItem, ...editModeItems];
|
||||
return [...labsMenuItem, menuItems.settings, ...shareMenuItem, ...editModeItems];
|
||||
}, [lastSavedId, menuItems, share, isLabsEnabled]);
|
||||
|
||||
return { viewModeTopNavConfig, editModeTopNavConfig };
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { DashboardSettings } from './settings_flyout';
|
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiFieldText,
|
||||
EuiTextArea,
|
||||
EuiForm,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiCallOut,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DashboardContainerByValueInput } from '../../../../common';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { useDashboardContainerContext } from '../../dashboard_container_context';
|
||||
|
||||
interface DashboardSettingsProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const DUPLICATE_TITLE_CALLOUT_ID = 'duplicateTitleCallout';
|
||||
|
||||
export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => {
|
||||
const {
|
||||
savedObjectsTagging: { components },
|
||||
dashboardSavedObject: { checkForDuplicateDashboardTitle },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
useEmbeddableDispatch,
|
||||
useEmbeddableSelector: select,
|
||||
actions: { setStateFromSettingsFlyout },
|
||||
embeddableInstance: dashboardContainer,
|
||||
} = useDashboardContainerContext();
|
||||
|
||||
const [dashboardSettingsState, setDashboardSettingsState] = useState({
|
||||
...dashboardContainer.getInputAsValueType(),
|
||||
});
|
||||
|
||||
const [isTitleDuplicate, setIsTitleDuplicate] = useState(false);
|
||||
const [isTitleDuplicateConfirmed, setIsTitleDuplicateConfirmed] = useState(false);
|
||||
const [isApplying, setIsApplying] = useState(false);
|
||||
|
||||
const lastSavedId = select((state) => state.componentState.lastSavedId);
|
||||
const lastSavedTitle = select((state) => state.explicitInput.title);
|
||||
|
||||
const isMounted = useMountedState();
|
||||
|
||||
const onTitleDuplicate = () => {
|
||||
if (!isMounted()) return;
|
||||
setIsTitleDuplicate(true);
|
||||
setIsTitleDuplicateConfirmed(true);
|
||||
};
|
||||
|
||||
const onApply = async () => {
|
||||
setIsApplying(true);
|
||||
const validTitle = await checkForDuplicateDashboardTitle({
|
||||
title: dashboardSettingsState.title,
|
||||
copyOnSave: false,
|
||||
lastSavedTitle,
|
||||
onTitleDuplicate,
|
||||
isTitleDuplicateConfirmed,
|
||||
});
|
||||
|
||||
if (!isMounted()) return;
|
||||
|
||||
setIsApplying(false);
|
||||
|
||||
if (validTitle) {
|
||||
dispatch(setStateFromSettingsFlyout({ lastSavedId, ...dashboardSettingsState }));
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const updateDashboardSetting = useCallback(
|
||||
(newSettings: Partial<DashboardContainerByValueInput>) => {
|
||||
setDashboardSettingsState((prevDashboardSettingsState) => {
|
||||
return {
|
||||
...prevDashboardSettingsState,
|
||||
...newSettings,
|
||||
};
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const dispatch = useEmbeddableDispatch();
|
||||
|
||||
const renderDuplicateTitleCallout = () => {
|
||||
if (!isTitleDuplicate) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.form.duplicateTitleLabel"
|
||||
defaultMessage="This dashboard already exists"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
data-test-subj="duplicateTitleWarningMessage"
|
||||
id={DUPLICATE_TITLE_CALLOUT_ID}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.form.duplicateTitleDescription"
|
||||
defaultMessage="Saving '{title}' creates a duplicate title."
|
||||
values={{
|
||||
title: dashboardSettingsState.title,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTagSelector = () => {
|
||||
if (!components) return;
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.form.tagsFormRowLabel"
|
||||
defaultMessage="Tags"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<components.TagSelector
|
||||
selected={dashboardSettingsState.tags}
|
||||
onTagsSelected={(selectedTags) => updateDashboardSetting({ tags: selectedTags })}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.title"
|
||||
defaultMessage="Dashboard settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
{renderDuplicateTitleCallout()}
|
||||
<EuiForm data-test-subj="dashboardSettingsPanel">
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.form.panelTitleFormRowLabel"
|
||||
defaultMessage="Title"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
id="dashboardTitleInput"
|
||||
className="dashboardTitleInputText"
|
||||
data-test-subj="dashboardTitleInput"
|
||||
name="title"
|
||||
type="text"
|
||||
value={dashboardSettingsState.title}
|
||||
onChange={(event) => {
|
||||
setIsTitleDuplicate(false);
|
||||
setIsTitleDuplicateConfirmed(false);
|
||||
updateDashboardSetting({ title: event.target.value });
|
||||
}}
|
||||
aria-label={i18n.translate(
|
||||
'dashboard.embeddableApi.showSettings.flyout.form.panelTitleInputAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Change the dashboard title',
|
||||
}
|
||||
)}
|
||||
aria-describedby={isTitleDuplicate ? DUPLICATE_TITLE_CALLOUT_ID : undefined}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.form.panelDescriptionFormRowLabel"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiTextArea
|
||||
id="dashboardDescriptionInput"
|
||||
className="dashboardDescriptionInputText"
|
||||
data-test-subj="dashboardDescriptionInput"
|
||||
name="description"
|
||||
value={dashboardSettingsState.description ?? ''}
|
||||
onChange={(event) => updateDashboardSetting({ description: event.target.value })}
|
||||
aria-label={i18n.translate(
|
||||
'dashboard.embeddableApi.showSettings.flyout.form.panelDescriptionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Change the dashboard description',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{renderTagSelector()}
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.form.storeTimeWithDashboardFormRowHelpText"
|
||||
defaultMessage="This changes the time filter to the currently selected time each time this dashboard is loaded."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
data-test-subj="storeTimeWithDashboard"
|
||||
checked={dashboardSettingsState.timeRestore}
|
||||
onChange={(event) => updateDashboardSetting({ timeRestore: event.target.checked })}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.form.storeTimeWithDashboardFormRowLabel"
|
||||
defaultMessage="Store time with dashboard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Use margins between panels',
|
||||
}
|
||||
)}
|
||||
checked={dashboardSettingsState.useMargins}
|
||||
onChange={(event) => updateDashboardSetting({ useMargins: event.target.checked })}
|
||||
data-test-subj="dashboardMarginsCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'dashboard.embeddableApi.showSettings.flyout.form.hideAllPanelTitlesSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Show panel titles',
|
||||
}
|
||||
)}
|
||||
checked={!dashboardSettingsState.hidePanelTitles}
|
||||
onChange={(event) =>
|
||||
updateDashboardSetting({ hidePanelTitles: !event.target.checked })
|
||||
}
|
||||
data-test-subj="dashboardPanelTitlesCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow label="Sync across panels">
|
||||
<>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Sync color palettes across panels',
|
||||
}
|
||||
)}
|
||||
checked={dashboardSettingsState.syncColors}
|
||||
onChange={(event) => updateDashboardSetting({ syncColors: event.target.checked })}
|
||||
data-test-subj="dashboardSyncColorsCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Sync cursor across panels',
|
||||
}
|
||||
)}
|
||||
checked={dashboardSettingsState.syncCursor}
|
||||
onChange={(event) => {
|
||||
const syncCursor = event.target.checked;
|
||||
if (!syncCursor && dashboardSettingsState.syncTooltips) {
|
||||
updateDashboardSetting({ syncCursor, syncTooltips: false });
|
||||
} else {
|
||||
updateDashboardSetting({ syncCursor });
|
||||
}
|
||||
}}
|
||||
data-test-subj="dashboardSyncCursorCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Sync tooltips across panels',
|
||||
}
|
||||
)}
|
||||
checked={dashboardSettingsState.syncTooltips}
|
||||
disabled={!Boolean(dashboardSettingsState.syncCursor)}
|
||||
onChange={(event) =>
|
||||
updateDashboardSetting({ syncTooltips: event.target.checked })
|
||||
}
|
||||
data-test-subj="dashboardSyncTooltipsCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty data-test-subj="cancelCustomizeDashboardButton" onClick={onClose}>
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.cancelButtonTitle"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="applyCustomizeDashboardButton"
|
||||
onClick={onApply}
|
||||
fill
|
||||
aria-describedby={isTitleDuplicate ? DUPLICATE_TITLE_CALLOUT_ID : undefined}
|
||||
isLoading={isApplying}
|
||||
>
|
||||
{isTitleDuplicate ? (
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.confirmApplyButtonTitle"
|
||||
defaultMessage="Confirm and apply"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="dashboard.embeddableApi.showSettings.flyout.applyButtonTitle"
|
||||
defaultMessage="Apply"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { showOptions } from './show_options_popover';
|
||||
export { showSettings } from './show_settings';
|
||||
export { addFromLibrary } from './add_panel_from_library';
|
||||
export { runSaveAs, runQuickSave, runClone } from './run_save_functions';
|
||||
export { addOrUpdateEmbeddable, replacePanel, showPlaceholderUntil } from './panel_management';
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiForm, EuiFormRow, EuiSwitch } from '@elastic/eui';
|
||||
import { useDashboardContainerContext } from '../../../dashboard_container_context';
|
||||
|
||||
export const DashboardOptions = () => {
|
||||
const {
|
||||
useEmbeddableDispatch,
|
||||
useEmbeddableSelector: select,
|
||||
actions: { setUseMargins, setSyncCursor, setSyncColors, setSyncTooltips, setHidePanelTitles },
|
||||
} = useDashboardContainerContext();
|
||||
const dispatch = useEmbeddableDispatch();
|
||||
|
||||
const useMargins = select((state) => state.explicitInput.useMargins);
|
||||
const syncColors = select((state) => state.explicitInput.syncColors);
|
||||
const syncCursor = select((state) => state.explicitInput.syncCursor);
|
||||
const syncTooltips = select((state) => state.explicitInput.syncTooltips);
|
||||
const hidePanelTitles = select((state) => state.explicitInput.hidePanelTitles);
|
||||
|
||||
return (
|
||||
<EuiForm data-test-subj="dashboardOptionsMenu">
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel', {
|
||||
defaultMessage: 'Use margins between panels',
|
||||
})}
|
||||
checked={useMargins}
|
||||
onChange={(event) => dispatch(setUseMargins(event.target.checked))}
|
||||
data-test-subj="dashboardMarginsCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('dashboard.topNav.options.hideAllPanelTitlesSwitchLabel', {
|
||||
defaultMessage: 'Show panel titles',
|
||||
})}
|
||||
checked={!hidePanelTitles}
|
||||
onChange={(event) => dispatch(setHidePanelTitles(!event.target.checked))}
|
||||
data-test-subj="dashboardPanelTitlesCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow label="Sync across panels">
|
||||
<>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('dashboard.topNav.options.syncColorsBetweenPanelsSwitchLabel', {
|
||||
defaultMessage: 'Sync color palettes across panels',
|
||||
})}
|
||||
checked={syncColors}
|
||||
onChange={(event) => dispatch(setSyncColors(event.target.checked))}
|
||||
data-test-subj="dashboardSyncColorsCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('dashboard.topNav.options.syncCursorBetweenPanelsSwitchLabel', {
|
||||
defaultMessage: 'Sync cursor across panels',
|
||||
})}
|
||||
checked={syncCursor}
|
||||
onChange={(event) => dispatch(setSyncCursor(event.target.checked))}
|
||||
data-test-subj="dashboardSyncCursorCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'dashboard.topNav.options.syncTooltipsBetweenPanelsSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Sync tooltips across panels',
|
||||
}
|
||||
)}
|
||||
checked={syncTooltips}
|
||||
disabled={!Boolean(syncCursor)}
|
||||
onChange={(event) => dispatch(setSyncTooltips(event.target.checked))}
|
||||
data-test-subj="dashboardSyncTooltipsCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { EuiWrappingPopover } from '@elastic/eui';
|
||||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { DashboardOptions } from './overlays/options';
|
||||
import { DashboardContainer } from '../dashboard_container';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
const container = document.createElement('div');
|
||||
const onClose = () => {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
isOpen = false;
|
||||
};
|
||||
|
||||
export function showOptions(this: DashboardContainer, anchorElement: HTMLElement) {
|
||||
const {
|
||||
settings: {
|
||||
theme: { theme$ },
|
||||
},
|
||||
} = pluginServices.getServices();
|
||||
|
||||
if (isOpen) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
isOpen = true;
|
||||
const { Wrapper: DashboardReduxWrapper } = this.getReduxEmbeddableTools();
|
||||
|
||||
document.body.appendChild(container);
|
||||
const element = (
|
||||
<I18nProvider>
|
||||
<KibanaThemeProvider theme$={theme$}>
|
||||
<DashboardReduxWrapper>
|
||||
<EuiWrappingPopover
|
||||
id="popover"
|
||||
button={anchorElement}
|
||||
isOpen={true}
|
||||
closePopover={onClose}
|
||||
>
|
||||
<DashboardOptions />
|
||||
</EuiWrappingPopover>
|
||||
</DashboardReduxWrapper>
|
||||
</KibanaThemeProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { DashboardSettings } from '../../component/settings/settings_flyout';
|
||||
import { DashboardContainer } from '../dashboard_container';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
|
||||
export function showSettings(this: DashboardContainer) {
|
||||
const {
|
||||
settings: {
|
||||
theme: { theme$ },
|
||||
},
|
||||
overlays,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
dispatch,
|
||||
Wrapper: DashboardReduxWrapper,
|
||||
actions: { setHasOverlays },
|
||||
} = this.getReduxEmbeddableTools();
|
||||
|
||||
// TODO Move this action into DashboardContainer.openOverlay
|
||||
dispatch(setHasOverlays(true));
|
||||
|
||||
this.openOverlay(
|
||||
overlays.openFlyout(
|
||||
toMountPoint(
|
||||
<DashboardReduxWrapper>
|
||||
<DashboardSettings
|
||||
onClose={() => {
|
||||
dispatch(setHasOverlays(false));
|
||||
this.clearOverlays();
|
||||
}}
|
||||
/>
|
||||
</DashboardReduxWrapper>,
|
||||
{ theme$ }
|
||||
),
|
||||
{
|
||||
size: 's',
|
||||
'data-test-subj': 'dashboardSettingsFlyout',
|
||||
onClose: (flyout) => {
|
||||
this.clearOverlays();
|
||||
dispatch(setHasOverlays(false));
|
||||
flyout.close();
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
|
@ -37,7 +37,7 @@ import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-f
|
|||
import {
|
||||
runClone,
|
||||
runSaveAs,
|
||||
showOptions,
|
||||
showSettings,
|
||||
runQuickSave,
|
||||
replacePanel,
|
||||
addFromLibrary,
|
||||
|
@ -521,7 +521,7 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
public runSaveAs = runSaveAs;
|
||||
public runQuickSave = runQuickSave;
|
||||
|
||||
public showOptions = showOptions;
|
||||
public showSettings = showSettings;
|
||||
public addFromLibrary = addFromLibrary;
|
||||
|
||||
public replacePanel = replacePanel;
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
*/
|
||||
|
||||
import { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { DashboardPublicState, DashboardReduxState, DashboardStateFromSaveModal } from '../types';
|
||||
import {
|
||||
DashboardPublicState,
|
||||
DashboardReduxState,
|
||||
DashboardStateFromSaveModal,
|
||||
DashboardStateFromSettingsFlyout,
|
||||
} from '../types';
|
||||
import { DashboardContainerByValueInput } from '../../../common';
|
||||
|
||||
export const dashboardContainerReducers = {
|
||||
|
@ -50,6 +55,24 @@ export const dashboardContainerReducers = {
|
|||
}
|
||||
},
|
||||
|
||||
setStateFromSettingsFlyout: (
|
||||
state: DashboardReduxState,
|
||||
action: PayloadAction<DashboardStateFromSettingsFlyout>
|
||||
) => {
|
||||
state.componentState.lastSavedId = action.payload.lastSavedId;
|
||||
|
||||
state.explicitInput.tags = action.payload.tags;
|
||||
state.explicitInput.title = action.payload.title;
|
||||
state.explicitInput.description = action.payload.description;
|
||||
state.explicitInput.timeRestore = action.payload.timeRestore;
|
||||
|
||||
state.explicitInput.useMargins = action.payload.useMargins;
|
||||
state.explicitInput.syncColors = action.payload.syncColors;
|
||||
state.explicitInput.syncCursor = action.payload.syncCursor;
|
||||
state.explicitInput.syncTooltips = action.payload.syncTooltips;
|
||||
state.explicitInput.hidePanelTitles = action.payload.hidePanelTitles;
|
||||
},
|
||||
|
||||
setDescription: (
|
||||
state: DashboardReduxState,
|
||||
action: PayloadAction<DashboardContainerByValueInput['description']>
|
||||
|
@ -106,29 +129,6 @@ export const dashboardContainerReducers = {
|
|||
state.explicitInput = state.componentState.lastSavedInput;
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Options Reducers
|
||||
// ------------------------------------------------------------------------------
|
||||
setUseMargins: (state: DashboardReduxState, action: PayloadAction<boolean>) => {
|
||||
state.explicitInput.useMargins = action.payload;
|
||||
},
|
||||
|
||||
setSyncCursor: (state: DashboardReduxState, action: PayloadAction<boolean>) => {
|
||||
state.explicitInput.syncCursor = action.payload;
|
||||
},
|
||||
|
||||
setSyncColors: (state: DashboardReduxState, action: PayloadAction<boolean>) => {
|
||||
state.explicitInput.syncColors = action.payload;
|
||||
},
|
||||
|
||||
setSyncTooltips: (state: DashboardReduxState, action: PayloadAction<boolean>) => {
|
||||
state.explicitInput.syncTooltips = action.payload;
|
||||
},
|
||||
|
||||
setHidePanelTitles: (state: DashboardReduxState, action: PayloadAction<boolean>) => {
|
||||
state.explicitInput.hidePanelTitles = action.payload;
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Filtering Reducers
|
||||
// ------------------------------------------------------------------------------
|
||||
|
@ -200,4 +200,12 @@ export const dashboardContainerReducers = {
|
|||
setFullScreenMode: (state: DashboardReduxState, action: PayloadAction<boolean>) => {
|
||||
state.componentState.fullScreenMode = action.payload;
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Component state reducers
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
setHasOverlays: (state: DashboardReduxState, action: PayloadAction<boolean>) => {
|
||||
state.componentState.hasOverlays = action.payload;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import type { ContainerOutput } from '@kbn/embeddable-plugin/public';
|
||||
import type { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public';
|
||||
import type { DashboardContainerByValueInput } from '../../common/dashboard_container/types';
|
||||
import type { DashboardContainerByValueInput, DashboardOptions } from '../../common';
|
||||
|
||||
export type DashboardReduxState = ReduxEmbeddableState<
|
||||
DashboardContainerByValueInput,
|
||||
|
@ -22,10 +22,13 @@ export type DashboardStateFromSaveModal = Pick<
|
|||
> &
|
||||
Pick<DashboardPublicState, 'lastSavedId'>;
|
||||
|
||||
export type DashboardStateFromSettingsFlyout = DashboardStateFromSaveModal & DashboardOptions;
|
||||
|
||||
export interface DashboardPublicState {
|
||||
lastSavedInput: DashboardContainerByValueInput;
|
||||
isEmbeddedExternally?: boolean;
|
||||
hasUnsavedChanges?: boolean;
|
||||
hasOverlays?: boolean;
|
||||
expandedPanelId?: string;
|
||||
fullScreenMode?: boolean;
|
||||
savedQueryId?: string;
|
||||
|
|
|
@ -12,6 +12,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home', 'settings']);
|
||||
const a11y = getService('a11y');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const dashboardSettings = getService('dashboardSettings');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const listingTable = getService('listingTable');
|
||||
|
||||
|
@ -65,18 +66,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('open options menu', async () => {
|
||||
await PageObjects.dashboard.openOptions();
|
||||
it('open settings flyout', async () => {
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('Should be able to hide panel titles', async () => {
|
||||
await testSubjects.click('dashboardPanelTitlesCheckbox');
|
||||
await dashboardSettings.toggleShowPanelTitles(false);
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('Should be able display panels without margins', async () => {
|
||||
await testSubjects.click('dashboardMarginsCheckbox');
|
||||
await dashboardSettings.toggleUseMarginsBetweenPanels(true);
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('close settings flyout', async () => {
|
||||
await dashboardSettings.clickCancelButton();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const PageObjects = getPageObjects(['common', 'dashboard']);
|
||||
|
||||
describe('dashboard options', () => {
|
||||
let originalTitles: string[] = [];
|
||||
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.load(
|
||||
'test/functional/fixtures/kbn_archiver/dashboard/current/kibana'
|
||||
);
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
|
||||
});
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.preserveCrossAppState();
|
||||
await PageObjects.dashboard.loadSavedDashboard('few panels');
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
originalTitles = await PageObjects.dashboard.getPanelTitles();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
it('should be able to hide all panel titles', async () => {
|
||||
await PageObjects.dashboard.checkHideTitle();
|
||||
await retry.try(async () => {
|
||||
const titles = await PageObjects.dashboard.getPanelTitles();
|
||||
expect(titles[0]).to.eql('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to unhide all panel titles', async () => {
|
||||
await PageObjects.dashboard.checkHideTitle();
|
||||
await retry.try(async () => {
|
||||
const titles = await PageObjects.dashboard.getPanelTitles();
|
||||
expect(titles[0]).to.eql(originalTitles[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
114
test/functional/apps/dashboard/group5/dashboard_settings.ts
Normal file
114
test/functional/apps/dashboard/group5/dashboard_settings.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const globalNav = getService('globalNav');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const dashboardSettings = getService('dashboardSettings');
|
||||
const PageObjects = getPageObjects(['common', 'dashboard']);
|
||||
|
||||
describe('dashboard settings', () => {
|
||||
let originalTitles: string[] = [];
|
||||
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.load(
|
||||
'test/functional/fixtures/kbn_archiver/dashboard/current/kibana'
|
||||
);
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
|
||||
});
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.preserveCrossAppState();
|
||||
await PageObjects.dashboard.loadSavedDashboard('few panels');
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
originalTitles = await PageObjects.dashboard.getPanelTitles();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
it('should be able to hide all panel titles', async () => {
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.toggleShowPanelTitles(false);
|
||||
await dashboardSettings.clickApplyButton();
|
||||
await retry.try(async () => {
|
||||
const titles = await PageObjects.dashboard.getPanelTitles();
|
||||
expect(titles[0]).to.eql('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to unhide all panel titles', async () => {
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.toggleShowPanelTitles(true);
|
||||
await dashboardSettings.clickApplyButton();
|
||||
await retry.try(async () => {
|
||||
const titles = await PageObjects.dashboard.getPanelTitles();
|
||||
expect(titles[0]).to.eql(originalTitles[0]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the title of the dashboard', async () => {
|
||||
const newTitle = 'My awesome dashboard!!1';
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.setCustomPanelTitle(newTitle);
|
||||
await dashboardSettings.clickApplyButton();
|
||||
await retry.try(async () => {
|
||||
expect((await globalNav.getLastBreadcrumb()) === newTitle);
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable quick save when the settings are open', async () => {
|
||||
await PageObjects.dashboard.expectQuickSaveButtonEnabled();
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await retry.try(async () => {
|
||||
await PageObjects.dashboard.expectQuickSaveButtonDisabled();
|
||||
});
|
||||
await dashboardSettings.clickCancelButton();
|
||||
});
|
||||
|
||||
it('should enable quick save when the settings flyout is closed', async () => {
|
||||
await PageObjects.dashboard.expectQuickSaveButtonEnabled();
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.clickCloseFlyoutButton();
|
||||
await retry.try(async () => {
|
||||
await PageObjects.dashboard.expectQuickSaveButtonEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn when creating a duplicate title', async () => {
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.setCustomPanelTitle('couple panels');
|
||||
await dashboardSettings.clickApplyButton(false);
|
||||
await retry.try(async () => {
|
||||
await dashboardSettings.expectDuplicateTitleWarningDisplayed();
|
||||
});
|
||||
await dashboardSettings.clickCancelButton();
|
||||
});
|
||||
|
||||
it('should allow duplicate title if warned once', async () => {
|
||||
const newTitle = 'couple panels';
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.setCustomPanelTitle(newTitle);
|
||||
await dashboardSettings.clickApplyButton(false);
|
||||
await retry.try(async () => {
|
||||
await dashboardSettings.expectDuplicateTitleWarningDisplayed();
|
||||
});
|
||||
await dashboardSettings.clickApplyButton();
|
||||
await retry.try(async () => {
|
||||
expect((await globalNav.getLastBreadcrumb()) === newTitle);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -29,7 +29,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
// This has to be first since the other tests create some embeddables as side affects and our counting assumes
|
||||
// a fresh index.
|
||||
loadTestFile(require.resolve('./empty_dashboard'));
|
||||
loadTestFile(require.resolve('./dashboard_options'));
|
||||
loadTestFile(require.resolve('./dashboard_settings'));
|
||||
loadTestFile(require.resolve('./data_shared_attributes'));
|
||||
loadTestFile(require.resolve('./share'));
|
||||
loadTestFile(require.resolve('./embed_mode'));
|
||||
|
|
|
@ -393,16 +393,16 @@ export class DashboardPageObject extends FtrService {
|
|||
return this.testSubjects.exists('emptyListPrompt');
|
||||
}
|
||||
|
||||
public async isOptionsOpen() {
|
||||
this.log.debug('isOptionsOpen');
|
||||
return await this.testSubjects.exists('dashboardOptionsMenu');
|
||||
public async isSettingsOpen() {
|
||||
this.log.debug('isSettingsOpen');
|
||||
return await this.testSubjects.exists('dashboardSettingsMenu');
|
||||
}
|
||||
|
||||
public async openOptions() {
|
||||
this.log.debug('openOptions');
|
||||
const isOpen = await this.isOptionsOpen();
|
||||
public async openSettingsFlyout() {
|
||||
this.log.debug('openSettingsFlyout');
|
||||
const isOpen = await this.isSettingsOpen();
|
||||
if (!isOpen) {
|
||||
return await this.testSubjects.click('dashboardOptionsButton');
|
||||
return await this.testSubjects.click('dashboardSettingsButton');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,34 +414,6 @@ export class DashboardPageObject extends FtrService {
|
|||
await this.gotoDashboardLandingPage();
|
||||
}
|
||||
|
||||
public async isMarginsOn() {
|
||||
this.log.debug('isMarginsOn');
|
||||
await this.openOptions();
|
||||
return await this.testSubjects.getAttribute('dashboardMarginsCheckbox', 'checked');
|
||||
}
|
||||
|
||||
public async useMargins(on = true) {
|
||||
await this.openOptions();
|
||||
const isMarginsOn = await this.isMarginsOn();
|
||||
if (isMarginsOn !== 'on') {
|
||||
return await this.testSubjects.click('dashboardMarginsCheckbox');
|
||||
}
|
||||
}
|
||||
|
||||
public async isColorSyncOn() {
|
||||
this.log.debug('isColorSyncOn');
|
||||
await this.openOptions();
|
||||
return await this.testSubjects.getAttribute('dashboardSyncColorsCheckbox', 'checked');
|
||||
}
|
||||
|
||||
public async useColorSync(on = true) {
|
||||
await this.openOptions();
|
||||
const isColorSyncOn = await this.isColorSyncOn();
|
||||
if (isColorSyncOn !== 'on') {
|
||||
return await this.testSubjects.click('dashboardSyncColorsCheckbox');
|
||||
}
|
||||
}
|
||||
|
||||
public async gotoDashboardEditMode(dashboardName: string) {
|
||||
await this.loadSavedDashboard(dashboardName);
|
||||
await this.switchToEditMode();
|
||||
|
@ -751,12 +723,6 @@ export class DashboardPageObject extends FtrService {
|
|||
});
|
||||
}
|
||||
|
||||
public async checkHideTitle() {
|
||||
this.log.debug('ensure that you can click on hide title checkbox');
|
||||
await this.openOptions();
|
||||
return await this.testSubjects.click('dashboardPanelTitlesCheckbox');
|
||||
}
|
||||
|
||||
public async expectMissingSaveOption() {
|
||||
await this.testSubjects.missingOrFail('dashboardSaveMenuItem');
|
||||
}
|
||||
|
@ -777,6 +743,15 @@ export class DashboardPageObject extends FtrService {
|
|||
}
|
||||
}
|
||||
|
||||
public async expectQuickSaveButtonDisabled() {
|
||||
this.log.debug('expectQuickSaveButtonDisabled');
|
||||
const quickSaveButton = await this.testSubjects.find('dashboardQuickSaveMenuItem');
|
||||
const isDisabled = await quickSaveButton.getAttribute('disabled');
|
||||
if (!isDisabled) {
|
||||
throw new Error('Quick save button not disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public async getNotLoadedVisualizations(vizList: string[]) {
|
||||
const checkList = [];
|
||||
for (const name of vizList) {
|
||||
|
|
136
test/functional/services/dashboard/dashboard_settings.ts
Normal file
136
test/functional/services/dashboard/dashboard_settings.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function DashboardSettingsProvider({ getService }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const toasts = getService('toasts');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return new (class DashboardSettingsPanel {
|
||||
public readonly FLYOUT_TEST_SUBJ = 'dashboardSettingsFlyout';
|
||||
public readonly SYNC_TOOLTIPS_DATA_SUBJ = 'dashboardSyncTooltipsCheckbox';
|
||||
|
||||
async expectDashboardSettingsFlyoutOpen() {
|
||||
log.debug('expectDashboardSettingsFlyoutOpen');
|
||||
await testSubjects.existOrFail(this.FLYOUT_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async expectDashboardSettingsFlyoutClosed() {
|
||||
log.debug('expectDashboardSettingsFlyoutClosed');
|
||||
await testSubjects.missingOrFail(this.FLYOUT_TEST_SUBJ);
|
||||
}
|
||||
|
||||
async expectDuplicateTitleWarningDisplayed() {
|
||||
log.debug('expectDuplicateTitleWarningDisplayed');
|
||||
await testSubjects.existOrFail('duplicateTitleWarningMessage');
|
||||
}
|
||||
|
||||
async findFlyout() {
|
||||
log.debug('findFlyout');
|
||||
return await testSubjects.find(this.FLYOUT_TEST_SUBJ);
|
||||
}
|
||||
|
||||
public async findFlyoutTestSubject(testSubject: string) {
|
||||
log.debug(`findFlyoutTestSubject::${testSubject}`);
|
||||
const flyout = await this.findFlyout();
|
||||
return await flyout.findByCssSelector(`[data-test-subj="${testSubject}"]`);
|
||||
}
|
||||
|
||||
public async setCustomPanelTitle(customTitle: string) {
|
||||
log.debug(`setCustomPanelTitle::${customTitle}`);
|
||||
await testSubjects.setValue('dashboardTitleInput', customTitle, {
|
||||
clearWithKeyboard: customTitle === '', // if clearing the title using the empty string as the new value, 'clearWithKeyboard' must be true; otherwise, false
|
||||
});
|
||||
}
|
||||
|
||||
public async setCustomPanelDescription(customDescription: string) {
|
||||
log.debug(`setCustomPanelDescription::${customDescription}`);
|
||||
await testSubjects.setValue('dashboardDescriptionInput', customDescription, {
|
||||
clearWithKeyboard: customDescription === '', // if clearing the description using the empty string as the new value, 'clearWithKeyboard' must be true; otherwise, false
|
||||
});
|
||||
}
|
||||
|
||||
public async toggleStoreTimeWithDashboard(value: boolean) {
|
||||
const status = value ? 'check' : 'uncheck';
|
||||
log.debug(`toggleStoreTimeWithDashboard::${status}`);
|
||||
await testSubjects.setEuiSwitch('storeTimeWithDashboard', status);
|
||||
}
|
||||
|
||||
public async toggleUseMarginsBetweenPanels(value: boolean) {
|
||||
const status = value ? 'check' : 'uncheck';
|
||||
log.debug(`toggleUseMarginsBetweenPanels::${status}`);
|
||||
await testSubjects.setEuiSwitch('dashboardMarginsCheckbox', status);
|
||||
}
|
||||
|
||||
public async toggleShowPanelTitles(value: boolean) {
|
||||
const status = value ? 'check' : 'uncheck';
|
||||
log.debug(`toggleShowPanelTitles::${status}`);
|
||||
await testSubjects.setEuiSwitch('dashboardPanelTitlesCheckbox', status);
|
||||
}
|
||||
|
||||
public async toggleSyncColors(value: boolean) {
|
||||
const status = value ? 'check' : 'uncheck';
|
||||
log.debug(`toggleSyncColors::${status}`);
|
||||
await testSubjects.setEuiSwitch('dashboardSyncColorsCheckbox', status);
|
||||
}
|
||||
|
||||
public async toggleSyncCursor(value: boolean) {
|
||||
const status = value ? 'check' : 'uncheck';
|
||||
log.debug(`toggleSyncCursor::${status}`);
|
||||
await testSubjects.setEuiSwitch('dashboardSyncCursorCheckbox', status);
|
||||
}
|
||||
|
||||
public async isSyncTooltipsEnabled() {
|
||||
log.debug('isSyncTooltipsEnabled');
|
||||
return await testSubjects.isEuiSwitchChecked(this.SYNC_TOOLTIPS_DATA_SUBJ);
|
||||
}
|
||||
|
||||
public async toggleSyncTooltips(value: boolean) {
|
||||
const status = value ? 'check' : 'uncheck';
|
||||
log.debug(`toggleSyncTooltips::${status}`);
|
||||
if (await this.isSyncTooltipsEnabled) {
|
||||
await testSubjects.setEuiSwitch(this.SYNC_TOOLTIPS_DATA_SUBJ, status);
|
||||
}
|
||||
}
|
||||
|
||||
public async isShowingDuplicateTitleWarning() {
|
||||
log.debug('isShowingDuplicateTitleWarning');
|
||||
await testSubjects.exists('duplicateTitleWarningMessage');
|
||||
}
|
||||
|
||||
public async clickApplyButton(shouldClose: boolean = true) {
|
||||
log.debug('clickApplyButton');
|
||||
await retry.try(async () => {
|
||||
await toasts.dismissAllToasts();
|
||||
await testSubjects.click('applyCustomizeDashboardButton');
|
||||
if (shouldClose) await this.expectDashboardSettingsFlyoutClosed();
|
||||
});
|
||||
}
|
||||
|
||||
public async clickCancelButton() {
|
||||
log.debug('clickCancelButton');
|
||||
await retry.try(async () => {
|
||||
await toasts.dismissAllToasts();
|
||||
await testSubjects.click('cancelCustomizeDashboardButton');
|
||||
await this.expectDashboardSettingsFlyoutClosed();
|
||||
});
|
||||
}
|
||||
|
||||
public async clickCloseFlyoutButton() {
|
||||
log.debug();
|
||||
await retry.try(async () => {
|
||||
await toasts.dismissAllToasts();
|
||||
await (await this.findFlyoutTestSubject('euiFlyoutCloseButton')).click();
|
||||
await this.expectDashboardSettingsFlyoutClosed();
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
|
@ -56,6 +56,7 @@ import { MenuToggleService } from './menu_toggle';
|
|||
import { MonacoEditorService } from './monaco_editor';
|
||||
import { UsageCollectionService } from './usage_collection';
|
||||
import { SavedObjectsFinderService } from './saved_objects_finder';
|
||||
import { DashboardSettingsProvider } from './dashboard/dashboard_settings';
|
||||
|
||||
export const services = {
|
||||
...commonServiceProviders,
|
||||
|
@ -80,6 +81,7 @@ export const services = {
|
|||
dashboardBadgeActions: DashboardBadgeActionsProvider,
|
||||
dashboardDrilldownPanelActions: DashboardDrilldownPanelActionsProvider,
|
||||
dashboardDrilldownsManage: DashboardDrilldownsManageProvider,
|
||||
dashboardSettings: DashboardSettingsProvider,
|
||||
flyout: FlyoutService,
|
||||
comboBox: ComboBoxService,
|
||||
dataGrid: DataGridService,
|
||||
|
|
|
@ -1203,11 +1203,11 @@
|
|||
"dashboard.topNav.cloneModal.enterNewNameForDashboardDescription": "Veuillez saisir un autre nom pour votre tableau de bord.",
|
||||
"dashboard.topNav.labsButtonAriaLabel": "ateliers",
|
||||
"dashboard.topNav.labsConfigDescription": "Ateliers",
|
||||
"dashboard.topNav.options.hideAllPanelTitlesSwitchLabel": "Afficher les titres de panneau",
|
||||
"dashboard.topNav.options.syncColorsBetweenPanelsSwitchLabel": "Synchroniser les palettes de couleur de tous les panneaux",
|
||||
"dashboard.topNav.options.syncCursorBetweenPanelsSwitchLabel": "Synchroniser le curseur de tous les panneaux",
|
||||
"dashboard.topNav.options.syncTooltipsBetweenPanelsSwitchLabel": "Synchroniser les infobulles de tous les panneaux",
|
||||
"dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel": "Utiliser des marges entre les panneaux",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.hideAllPanelTitlesSwitchLabel": "Afficher les titres de panneau",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel": "Synchroniser les palettes de couleur de tous les panneaux",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel": "Synchroniser le curseur de tous les panneaux",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel": "Synchroniser les infobulles de tous les panneaux",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel": "Utiliser des marges entre les panneaux",
|
||||
"dashboard.topNav.saveModal.descriptionFormRowLabel": "Description",
|
||||
"dashboard.topNav.saveModal.objectType": "tableau de bord",
|
||||
"dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText": "Le filtre temporel est défini sur l’option sélectionnée chaque fois que ce tableau de bord est chargé.",
|
||||
|
@ -1219,8 +1219,6 @@
|
|||
"dashboard.topNave.editConfigDescription": "Basculer en mode Édition",
|
||||
"dashboard.topNave.fullScreenButtonAriaLabel": "plein écran",
|
||||
"dashboard.topNave.fullScreenConfigDescription": "Mode Plein écran",
|
||||
"dashboard.topNave.optionsButtonAriaLabel": "options",
|
||||
"dashboard.topNave.optionsConfigDescription": "Options",
|
||||
"dashboard.topNave.saveAsButtonAriaLabel": "enregistrer sous",
|
||||
"dashboard.topNave.saveAsConfigDescription": "Enregistrer en tant que nouveau tableau de bord",
|
||||
"dashboard.topNave.saveButtonAriaLabel": "enregistrer",
|
||||
|
|
|
@ -1203,11 +1203,11 @@
|
|||
"dashboard.topNav.cloneModal.enterNewNameForDashboardDescription": "ダッシュボードの新しい名前を入力してください。",
|
||||
"dashboard.topNav.labsButtonAriaLabel": "ラボ",
|
||||
"dashboard.topNav.labsConfigDescription": "ラボ",
|
||||
"dashboard.topNav.options.hideAllPanelTitlesSwitchLabel": "パネルタイトルを表示",
|
||||
"dashboard.topNav.options.syncColorsBetweenPanelsSwitchLabel": "パネル全体でカラーパレットを同期",
|
||||
"dashboard.topNav.options.syncCursorBetweenPanelsSwitchLabel": "パネル全体でカーソルを同期",
|
||||
"dashboard.topNav.options.syncTooltipsBetweenPanelsSwitchLabel": "パネル間でツールチップを同期",
|
||||
"dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel": "パネルの間に余白を使用",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.hideAllPanelTitlesSwitchLabel": "パネルタイトルを表示",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel": "パネル全体でカラーパレットを同期",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel": "パネル全体でカーソルを同期",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel": "パネル間でツールチップを同期",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel": "パネルの間に余白を使用",
|
||||
"dashboard.topNav.saveModal.descriptionFormRowLabel": "説明",
|
||||
"dashboard.topNav.saveModal.objectType": "ダッシュボード",
|
||||
"dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText": "有効化すると、ダッシュボードが読み込まれるごとに現在選択された時刻の時間フィルターが変更されます。",
|
||||
|
@ -1219,8 +1219,6 @@
|
|||
"dashboard.topNave.editConfigDescription": "編集モードに切り替えます",
|
||||
"dashboard.topNave.fullScreenButtonAriaLabel": "全画面",
|
||||
"dashboard.topNave.fullScreenConfigDescription": "全画面モード",
|
||||
"dashboard.topNave.optionsButtonAriaLabel": "オプション",
|
||||
"dashboard.topNave.optionsConfigDescription": "オプション",
|
||||
"dashboard.topNave.saveAsButtonAriaLabel": "名前を付けて保存",
|
||||
"dashboard.topNave.saveAsConfigDescription": "新しいダッシュボードとして保存",
|
||||
"dashboard.topNave.saveButtonAriaLabel": "保存",
|
||||
|
|
|
@ -1203,11 +1203,11 @@
|
|||
"dashboard.topNav.cloneModal.enterNewNameForDashboardDescription": "请为您的仪表板输入新的名称。",
|
||||
"dashboard.topNav.labsButtonAriaLabel": "实验",
|
||||
"dashboard.topNav.labsConfigDescription": "实验",
|
||||
"dashboard.topNav.options.hideAllPanelTitlesSwitchLabel": "显示面板标题",
|
||||
"dashboard.topNav.options.syncColorsBetweenPanelsSwitchLabel": "在面板之间同步调色板",
|
||||
"dashboard.topNav.options.syncCursorBetweenPanelsSwitchLabel": "在面板之间同步光标",
|
||||
"dashboard.topNav.options.syncTooltipsBetweenPanelsSwitchLabel": "在面板之间同步工具提示",
|
||||
"dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel": "在面板间使用边距",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.hideAllPanelTitlesSwitchLabel": "显示面板标题",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel": "在面板之间同步调色板",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel": "在面板之间同步光标",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel": "在面板之间同步工具提示",
|
||||
"dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel": "在面板间使用边距",
|
||||
"dashboard.topNav.saveModal.descriptionFormRowLabel": "描述",
|
||||
"dashboard.topNav.saveModal.objectType": "仪表板",
|
||||
"dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText": "每次加载此仪表板时,都会将时间筛选更改为当前选定的时间。",
|
||||
|
@ -1219,8 +1219,6 @@
|
|||
"dashboard.topNave.editConfigDescription": "切换到编辑模式",
|
||||
"dashboard.topNave.fullScreenButtonAriaLabel": "全屏",
|
||||
"dashboard.topNave.fullScreenConfigDescription": "全屏模式",
|
||||
"dashboard.topNave.optionsButtonAriaLabel": "选项",
|
||||
"dashboard.topNave.optionsConfigDescription": "选项",
|
||||
"dashboard.topNave.saveAsButtonAriaLabel": "另存为",
|
||||
"dashboard.topNave.saveAsConfigDescription": "另存为新仪表板",
|
||||
"dashboard.topNave.saveButtonAriaLabel": "保存",
|
||||
|
|
|
@ -20,6 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
'timePicker',
|
||||
]);
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const dashboardSettings = getService('dashboardSettings');
|
||||
const filterBar = getService('filterBar');
|
||||
const elasticChart = getService('elasticChart');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
@ -87,7 +88,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await filterBar.addFilter({ field: 'geo.src', operation: 'is not', value: 'CN' });
|
||||
|
||||
await PageObjects.lens.save('vis2', false, true);
|
||||
await PageObjects.dashboard.useColorSync(true);
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.toggleSyncColors(true);
|
||||
await dashboardSettings.clickApplyButton();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
|
||||
|
@ -116,7 +119,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should be possible to disable color sync', async () => {
|
||||
await PageObjects.dashboard.useColorSync(false);
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.toggleSyncColors(false);
|
||||
await dashboardSettings.clickApplyButton();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const colorMapping1 = getColorMapping(await PageObjects.dashboard.getPanelChartDebugState(0));
|
||||
const colorMapping2 = getColorMapping(await PageObjects.dashboard.getPanelChartDebugState(1));
|
||||
|
|
|
@ -14,6 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
const kibanaServer = getService('kibanaServer');
|
||||
const listingTable = getService('listingTable');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dashboardSettings = getService('dashboardSettings');
|
||||
const PageObjects = getPageObjects(['dashboard', 'tagManagement', 'common']);
|
||||
|
||||
describe('dashboard integration', () => {
|
||||
|
@ -161,7 +162,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
it('retains dashboard saved object tags after quicksave', async () => {
|
||||
// edit and save dashboard
|
||||
await PageObjects.dashboard.gotoDashboardEditMode('dashboard 4 with real data (tag-1)');
|
||||
await PageObjects.dashboard.useMargins(false); // turn margins off to cause quicksave to be enabled
|
||||
await PageObjects.dashboard.openSettingsFlyout();
|
||||
await dashboardSettings.toggleUseMarginsBetweenPanels(false); // turn margins off to cause quicksave to be enabled
|
||||
await dashboardSettings.clickApplyButton();
|
||||
await PageObjects.dashboard.clickQuickSave();
|
||||
|
||||
// verify dashboard still has original tags
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue