[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:
Nick Peihl 2023-03-31 09:52:51 -04:00 committed by GitHub
parent 7ebfc6e6cd
commit b692e347f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 791 additions and 324 deletions

View file

@ -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.

View file

@ -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: {

View file

@ -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 };

View file

@ -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';

View file

@ -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>
</>
);
};

View file

@ -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';

View file

@ -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>
);
};

View file

@ -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);
}

View file

@ -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();
},
}
)
);
}

View file

@ -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;

View file

@ -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;
},
};

View file

@ -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;

View file

@ -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();
});

View file

@ -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]);
});
});
});
}

View 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);
});
});
});
}

View file

@ -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'));

View file

@ -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) {

View 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();
});
}
})();
}

View file

@ -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,

View file

@ -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 loption 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",

View file

@ -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": "保存",

View file

@ -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": "保存",

View file

@ -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));

View file

@ -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