Fix reset UX for panel title and description (#182986)

## Summary

Updates to the panel title flyout behavior to be more strict about changes to the title, including...

- Input defaults to the actual title when empty (`""`), no longer fills
with default viz `title`/`description`.
- Uses the default title/description when the value is `undefined`, such
that the value has never been set.
- Adds a clear button to the `title` input.
- `Reset` wording replaced with `Reset to default`, for `title` and
`description`.
- Only shows reset if there is a `default` non-empty
`title`/`description` to reset to, applies mostly to by-value viz.
- Changes the inspect panel `title` to always match the panel `title`
and show `"[No Title]"` when empty.
This commit is contained in:
Nick Partridge 2024-05-14 10:15:10 -07:00 committed by GitHub
parent 5346e0ddcd
commit 382ee2d076
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 294 additions and 153 deletions

View file

@ -109,6 +109,7 @@ export {
export {
apiPublishesPanelDescription,
apiPublishesWritablePanelDescription,
getPanelDescription,
type PublishesPanelDescription,
type PublishesWritablePanelDescription,
} from './interfaces/titles/publishes_panel_description';

View file

@ -0,0 +1,36 @@
/*
* 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 { BehaviorSubject } from 'rxjs';
import { getPanelDescription } from './publishes_panel_description';
describe('getPanelDescription', () => {
test('should return default description when description is undefined', () => {
const api = {
panelDescription: new BehaviorSubject<string | undefined>(undefined),
defaultPanelDescription: new BehaviorSubject<string | undefined>('default description'),
};
expect(getPanelDescription(api)).toBe('default description');
});
test('should return empty description when description is empty string', () => {
const api = {
panelDescription: new BehaviorSubject<string | undefined>(''),
defaultPanelDescription: new BehaviorSubject<string | undefined>('default description'),
};
expect(getPanelDescription(api)).toBe('');
});
test('should return description when description is provided', () => {
const api = {
panelDescription: new BehaviorSubject<string | undefined>('custom description'),
defaultPanelDescription: new BehaviorSubject<string | undefined>('default description'),
};
expect(getPanelDescription(api)).toBe('custom description');
});
});

View file

@ -13,6 +13,10 @@ export interface PublishesPanelDescription {
defaultPanelDescription?: PublishingSubject<string | undefined>;
}
export function getPanelDescription(api: Partial<PublishesPanelDescription>): string | undefined {
return api.panelDescription?.value ?? api.defaultPanelDescription?.value;
}
export type PublishesWritablePanelDescription = PublishesPanelDescription & {
setPanelDescription: (newTitle: string | undefined) => void;
};

View file

@ -18,12 +18,12 @@ describe('getPanelTitle', () => {
expect(getPanelTitle(api)).toBe('default title');
});
test('should return default title when title is empty string', () => {
test('should return empty title when title is empty string', () => {
const api = {
panelTitle: new BehaviorSubject<string | undefined>(''),
defaultPanelTitle: new BehaviorSubject<string | undefined>('default title'),
};
expect(getPanelTitle(api)).toBe('default title');
expect(getPanelTitle(api)).toBe('');
});
test('should return title when title is provided', () => {

View file

@ -15,7 +15,7 @@ export interface PublishesPanelTitle {
}
export function getPanelTitle(api: Partial<PublishesPanelTitle>): string | undefined {
return api.panelTitle?.value || api.defaultPanelTitle?.value;
return api.panelTitle?.value ?? api.defaultPanelTitle?.value;
}
export type PublishesWritablePanelTitle = PublishesPanelTitle & {

View file

@ -19,19 +19,17 @@ import { CustomizePanelEditor } from './customize_panel_editor';
describe('customize panel editor', () => {
let api: CustomizePanelActionApi;
let setTitle: (title: string | undefined) => void;
let setTitle: (title?: string) => void;
let setViewMode: (viewMode: ViewMode) => void;
let setDescription: (description: string | undefined) => void;
let setDescription: (description?: string) => void;
beforeEach(() => {
const titleSubject = new BehaviorSubject<string | undefined>(undefined);
setTitle = jest.fn().mockImplementation((title) => titleSubject.next(title));
setTitle = jest.fn((title) => titleSubject.next(title));
const descriptionSubject = new BehaviorSubject<string | undefined>(undefined);
setDescription = jest
.fn()
.mockImplementation((description) => descriptionSubject.next(description));
setDescription = jest.fn((description) => descriptionSubject.next(description));
const viewMode = new BehaviorSubject<ViewMode>('edit');
setViewMode = jest.fn().mockImplementation((nextViewMode) => viewMode.next(nextViewMode));
setViewMode = jest.fn((nextViewMode) => viewMode.next(nextViewMode));
api = {
viewMode,
@ -75,27 +73,44 @@ describe('customize panel editor', () => {
);
});
it('Sets panel title on apply', () => {
it('should set panel title on apply', () => {
renderPanelEditor();
userEvent.type(screen.getByTestId('customEmbeddablePanelTitleInput'), 'New title');
userEvent.click(screen.getByTestId('saveCustomizePanelButton'));
expect(setTitle).toBeCalledWith('New title');
});
it('should use default title when title is undefined', () => {
api.defaultPanelTitle = new BehaviorSubject<string | undefined>('Default title');
setTitle(undefined);
renderPanelEditor();
const titleInput = screen.getByTestId('customEmbeddablePanelTitleInput');
expect(titleInput).toHaveValue('Default title');
});
it('should use title even when empty string', () => {
api.defaultPanelTitle = new BehaviorSubject<string | undefined>('Default title');
setTitle('');
renderPanelEditor();
const titleInput = screen.getByTestId('customEmbeddablePanelTitleInput');
expect(titleInput).toHaveValue('');
});
it('Resets panel title to default when reset button is pressed', () => {
api.defaultPanelTitle = new BehaviorSubject<string | undefined>('Default title');
setTitle('Initial title');
renderPanelEditor();
userEvent.type(screen.getByTestId('customEmbeddablePanelTitleInput'), 'New title');
userEvent.click(screen.getByTestId('resetCustomEmbeddablePanelTitleButton'));
expect(screen.getByTestId('customEmbeddablePanelTitleInput')).toHaveValue('Default title');
});
it('Reset panel title to undefined on apply', () => {
setTitle('very cool title');
it('should hide title reset when no default exists', () => {
api.defaultPanelTitle = new BehaviorSubject<string | undefined>(undefined);
setTitle('Initial title');
renderPanelEditor();
userEvent.click(screen.getByTestId('resetCustomEmbeddablePanelTitleButton'));
userEvent.click(screen.getByTestId('saveCustomizePanelButton'));
expect(setTitle).toBeCalledWith(undefined);
userEvent.type(screen.getByTestId('customEmbeddablePanelTitleInput'), 'New title');
expect(screen.queryByTestId('resetCustomEmbeddablePanelTitleButton')).not.toBeInTheDocument();
});
test('title input receives focus when `focusOnTitle` is `true`', async () => {
@ -128,7 +143,7 @@ describe('customize panel editor', () => {
);
});
it('Sets panel description on apply', () => {
it('should set panel description on apply', () => {
renderPanelEditor();
userEvent.type(
screen.getByTestId('customEmbeddablePanelDescriptionInput'),
@ -138,22 +153,47 @@ describe('customize panel editor', () => {
expect(setDescription).toBeCalledWith('New description');
});
it('Resets panel desription to default when reset button is pressed', () => {
it('should use default description when description is undefined', () => {
api.defaultPanelDescription = new BehaviorSubject<string | undefined>('Default description');
setDescription(undefined);
renderPanelEditor();
userEvent.type(screen.getByTestId('customEmbeddablePanelDescriptionInput'), 'New desription');
const descriptionInput = screen.getByTestId('customEmbeddablePanelDescriptionInput');
expect(descriptionInput).toHaveValue('Default description');
});
it('should use description even when empty string', () => {
api.defaultPanelDescription = new BehaviorSubject<string | undefined>('Default description');
setDescription('');
renderPanelEditor();
const descriptionInput = screen.getByTestId('customEmbeddablePanelDescriptionInput');
expect(descriptionInput).toHaveValue('');
});
it('Resets panel description to default when reset button is pressed', () => {
api.defaultPanelDescription = new BehaviorSubject<string | undefined>('Default description');
setDescription('Initial description');
renderPanelEditor();
userEvent.type(
screen.getByTestId('customEmbeddablePanelDescriptionInput'),
'New description'
);
userEvent.click(screen.getByTestId('resetCustomEmbeddablePanelDescriptionButton'));
expect(screen.getByTestId('customEmbeddablePanelDescriptionInput')).toHaveValue(
'Default description'
);
});
it('Reset panel description to undefined on apply', () => {
setDescription('very cool description');
it('should hide description reset when no default exists', () => {
api.defaultPanelDescription = new BehaviorSubject<string | undefined>(undefined);
setDescription('Initial description');
renderPanelEditor();
userEvent.click(screen.getByTestId('resetCustomEmbeddablePanelDescriptionButton'));
userEvent.click(screen.getByTestId('saveCustomizePanelButton'));
expect(setDescription).toBeCalledWith(undefined);
userEvent.type(
screen.getByTestId('customEmbeddablePanelDescriptionInput'),
'New description'
);
expect(
screen.queryByTestId('resetCustomEmbeddablePanelDescriptionButton')
).not.toBeInTheDocument();
});
});

View file

@ -33,6 +33,7 @@ import {
apiPublishesTimeRange,
apiPublishesUnifiedSearch,
getInheritedViewMode,
getPanelDescription,
getPanelTitle,
PublishesUnifiedSearch,
} from '@kbn/presentation-publishing';
@ -62,10 +63,8 @@ export const CustomizePanelEditor = ({
*/
const editMode = getInheritedViewMode(api) === 'edit';
const [hideTitle, setHideTitle] = useState(api.hidePanelTitle?.value);
const [panelDescription, setPanelDescription] = useState(
api.panelDescription?.value ?? api.defaultPanelDescription?.value
);
const [panelTitle, setPanelTitle] = useState(getPanelTitle(api));
const [panelDescription, setPanelDescription] = useState(getPanelDescription(api));
const [timeRange, setTimeRange] = useState(
api.timeRange$?.value ?? api.parentApi?.timeRange$?.value
);
@ -121,7 +120,6 @@ export const CustomizePanelEditor = ({
<EuiSwitch
checked={!hideTitle}
data-test-subj="customEmbeddablePanelHideTitleSwitch"
disabled={!editMode}
id="hideTitle"
label={
<FormattedMessage
@ -140,23 +138,25 @@ export const CustomizePanelEditor = ({
/>
}
labelAppend={
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelTitleButton"
onClick={() => setPanelTitle(api.defaultPanelTitle?.value)}
disabled={hideTitle || !editMode || api?.defaultPanelTitle?.value === panelTitle}
aria-label={i18n.translate(
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonAriaLabel',
{
defaultMessage: 'Reset title',
}
)}
>
<FormattedMessage
id="presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonLabel"
defaultMessage="Reset"
/>
</EuiButtonEmpty>
api?.defaultPanelTitle?.value && (
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelTitleButton"
onClick={() => setPanelTitle(api.defaultPanelTitle?.value)}
disabled={hideTitle || panelTitle === api?.defaultPanelTitle?.value}
aria-label={i18n.translate(
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonAriaLabel',
{
defaultMessage: 'Reset title to default',
}
)}
>
<FormattedMessage
id="presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonLabel"
defaultMessage="Reset to default"
/>
</EuiButtonEmpty>
)
}
>
<EuiFieldText
@ -166,7 +166,7 @@ export const CustomizePanelEditor = ({
data-test-subj="customEmbeddablePanelTitleInput"
name="title"
type="text"
disabled={hideTitle || !editMode}
disabled={hideTitle}
value={panelTitle ?? ''}
onChange={(e) => setPanelTitle(e.target.value)}
aria-label={i18n.translate(
@ -185,23 +185,25 @@ export const CustomizePanelEditor = ({
/>
}
labelAppend={
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelDescriptionButton"
onClick={() => setPanelDescription(api.defaultPanelDescription?.value)}
disabled={!editMode || api.defaultPanelDescription?.value === panelDescription}
aria-label={i18n.translate(
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomDescriptionButtonAriaLabel',
{
defaultMessage: 'Reset description',
}
)}
>
<FormattedMessage
id="presentationPanel.action.customizePanel.modal.optionsMenuForm.resetCustomDescriptionButtonLabel"
defaultMessage="Reset"
/>
</EuiButtonEmpty>
api.defaultPanelDescription?.value && (
<EuiButtonEmpty
size="xs"
data-test-subj="resetCustomEmbeddablePanelDescriptionButton"
onClick={() => setPanelDescription(api.defaultPanelDescription?.value)}
disabled={api.defaultPanelDescription?.value === panelDescription}
aria-label={i18n.translate(
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomDescriptionButtonAriaLabel',
{
defaultMessage: 'Reset description to default',
}
)}
>
<FormattedMessage
id="presentationPanel.action.customizePanel.modal.optionsMenuForm.resetCustomDescriptionButtonLabel"
defaultMessage="Reset to default"
/>
</EuiButtonEmpty>
)
}
>
<EuiTextArea

View file

@ -59,7 +59,7 @@ export class InspectPanelAction implements Action<EmbeddableApiContext> {
const panelTitle =
getPanelTitle(embeddable) ||
i18n.translate('presentationPanel.action.inspectPanel.untitledEmbeddableFilename', {
defaultMessage: 'untitled',
defaultMessage: '[No Title]',
});
const session = inspector.open(adapters, {
title: panelTitle,

View file

@ -79,7 +79,7 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = `
labelType="label"
>
<EuiTextArea
data-test-subj="viewDescription"
data-test-subj="savedObjectDescription"
fullWidth={true}
onChange={[Function]}
value=""
@ -203,7 +203,7 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali
labelType="label"
>
<EuiTextArea
data-test-subj="viewDescription"
data-test-subj="savedObjectDescription"
fullWidth={true}
onChange={[Function]}
value=""
@ -327,7 +327,7 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali
labelType="label"
>
<EuiTextArea
data-test-subj="viewDescription"
data-test-subj="savedObjectDescription"
fullWidth={true}
onChange={[Function]}
value=""
@ -455,7 +455,7 @@ exports[`SavedObjectSaveModal should render matching snapshot when given options
labelType="label"
>
<EuiTextArea
data-test-subj="viewDescription"
data-test-subj="savedObjectDescription"
fullWidth={true}
onChange={[Function]}
value=""

View file

@ -214,7 +214,7 @@ export class SavedObjectSaveModal extends React.Component<Props, SaveModalState>
>
<EuiTextArea
fullWidth
data-test-subj="viewDescription"
data-test-subj="savedObjectDescription"
value={this.state.visualizationDescription}
onChange={this.onDescriptionChange}
/>

View file

@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboardAddPanel.closeAddPanel();
const originalPanel = await testSubjects.find('embeddablePanelHeading-RenderingTest:heatmap');
await panelActions.legacyUnlinkFromLibary(originalPanel);
await panelActions.legacyUnlinkFromLibrary(originalPanel);
await testSubjects.existOrFail('unlinkPanelSuccess');
const updatedPanel = await testSubjects.find('embeddablePanelHeading-RenderingTest:heatmap');

View file

@ -589,7 +589,7 @@ export class DashboardPageObject extends FtrService {
public async getPanelTitles() {
this.log.debug('in getPanelTitles');
const titleObjects = await this.find.allByCssSelector(
'[data-test-subj=embeddablePanelTitleInner] .embPanel__titleText'
'[data-test-subj="embeddablePanelTitleInner"] .embPanel__titleText'
);
return await Promise.all(titleObjects.map(async (title) => await title.getVisibleText()));
}

View file

@ -14,6 +14,7 @@ interface SaveModalArgs {
dashboardId?: string;
saveAsNew?: boolean;
redirectToOrigin?: boolean;
description?: string;
}
type DashboardPickerOption =
@ -65,13 +66,27 @@ export class TimeToVisualizePageObject extends FtrService {
public async setSaveModalValues(
vizName: string,
{ saveAsNew, redirectToOrigin, addToDashboard, dashboardId, saveToLibrary }: SaveModalArgs = {}
{
saveAsNew,
redirectToOrigin,
addToDashboard,
dashboardId,
saveToLibrary,
description,
}: SaveModalArgs = {}
) {
await this.testSubjects.setValue('savedObjectTitle', vizName, {
typeCharByChar: true,
clearWithKeyboard: true,
});
if (description !== undefined) {
await this.testSubjects.setValue('savedObjectDescription', description, {
typeCharByChar: true,
clearWithKeyboard: true,
});
}
const hasSaveAsNew = await this.testSubjects.exists('saveAsNewCheckbox');
if (hasSaveAsNew && saveAsNew !== undefined) {
const state = saveAsNew ? 'check' : 'uncheck';

View file

@ -447,7 +447,7 @@ export class VisualizePageObject extends FtrService {
await this.testSubjects.setValue('savedObjectTitle', vizName);
if (description) {
await this.testSubjects.setValue('viewDescription', description);
await this.testSubjects.setValue('savedObjectDescription', description);
}
const saveAsNewCheckboxExists = await this.testSubjects.exists('saveAsNewCheckbox');

View file

@ -91,7 +91,16 @@ export class DashboardPanelActionsService extends FtrService {
await this.clickContextMenuMoreItem();
}
private async navigateToEditorFromFlyout() {
async clickContextMenuItem(itemSelector: string, parent?: WebElementWrapper) {
await this.openContextMenu(parent);
const exists = await this.testSubjects.exists(itemSelector);
if (!exists) {
await this.clickContextMenuMoreItem();
}
await this.testSubjects.click(itemSelector);
}
async navigateToEditorFromFlyout() {
await this.testSubjects.clickWhenNotDisabledWithoutRetry(INLINE_EDIT_PANEL_DATA_TEST_SUBJ);
await this.header.waitUntilLoadingHasFinished();
await this.testSubjects.click(EDIT_IN_LENS_EDITOR_DATA_TEST_SUBJ);
@ -113,7 +122,8 @@ export class DashboardPanelActionsService extends FtrService {
await this.common.waitForTopNavToBeVisible();
}
/** The dashboard/canvas panels can be either edited on their editor or inline.
/**
* The dashboard/canvas panels can be either edited on their editor or inline.
* The inline editing panels allow the navigation to the editor after the flyout opens
*/
async clickEdit() {
@ -135,7 +145,8 @@ export class DashboardPanelActionsService extends FtrService {
await this.common.waitForTopNavToBeVisible();
}
/** The dashboard/canvas panels can be either edited on their editor or inline.
/**
* The dashboard/canvas panels can be either edited on their editor or inline.
* The inline editing panels allow the navigation to the editor after the flyout opens
*/
async editPanelByTitle(title?: string) {
@ -253,35 +264,20 @@ export class DashboardPanelActionsService extends FtrService {
}
async openInspector(parent?: WebElementWrapper) {
await this.openContextMenu(parent);
const exists = await this.testSubjects.exists(OPEN_INSPECTOR_TEST_SUBJ);
if (!exists) {
await this.clickContextMenuMoreItem();
}
await this.testSubjects.click(OPEN_INSPECTOR_TEST_SUBJ);
await this.clickContextMenuItem(OPEN_INSPECTOR_TEST_SUBJ, parent);
}
async legacyUnlinkFromLibary(parent?: WebElementWrapper) {
async legacyUnlinkFromLibrary(parent?: WebElementWrapper) {
this.log.debug('legacyUnlinkFromLibrary');
await this.openContextMenu(parent);
const exists = await this.testSubjects.exists(LEGACY_UNLINK_FROM_LIBRARY_TEST_SUBJ);
if (!exists) {
await this.clickContextMenuMoreItem();
}
await this.testSubjects.click(LEGACY_UNLINK_FROM_LIBRARY_TEST_SUBJ);
await this.clickContextMenuItem(LEGACY_UNLINK_FROM_LIBRARY_TEST_SUBJ, parent);
await this.testSubjects.waitForDeleted(
'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION'
);
}
async unlinkFromLibary(parent?: WebElementWrapper) {
async unlinkFromLibrary(parent?: WebElementWrapper) {
this.log.debug('unlinkFromLibrary');
await this.openContextMenu(parent);
const exists = await this.testSubjects.exists(UNLINK_FROM_LIBRARY_TEST_SUBJ);
if (!exists) {
await this.clickContextMenuMoreItem();
}
await this.testSubjects.click(UNLINK_FROM_LIBRARY_TEST_SUBJ);
await this.clickContextMenuItem(UNLINK_FROM_LIBRARY_TEST_SUBJ, parent);
await this.testSubjects.waitForDeleted(
'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION'
);
@ -289,12 +285,7 @@ export class DashboardPanelActionsService extends FtrService {
async legacySaveToLibrary(newTitle: string, parent?: WebElementWrapper) {
this.log.debug('legacySaveToLibrary');
await this.openContextMenu(parent);
const exists = await this.testSubjects.exists(LEGACY_SAVE_TO_LIBRARY_TEST_SUBJ);
if (!exists) {
await this.clickContextMenuMoreItem();
}
await this.testSubjects.click(LEGACY_SAVE_TO_LIBRARY_TEST_SUBJ);
await this.clickContextMenuItem(LEGACY_SAVE_TO_LIBRARY_TEST_SUBJ, parent);
await this.testSubjects.setValue('savedObjectTitle', newTitle, {
clearWithKeyboard: true,
});
@ -308,12 +299,7 @@ export class DashboardPanelActionsService extends FtrService {
async saveToLibrary(newTitle: string, parent?: WebElementWrapper) {
this.log.debug('saveToLibrary');
await this.openContextMenu(parent);
const exists = await this.testSubjects.exists(SAVE_TO_LIBRARY_TEST_SUBJ);
if (!exists) {
await this.clickContextMenuMoreItem();
}
await this.testSubjects.click(SAVE_TO_LIBRARY_TEST_SUBJ);
await this.clickContextMenuItem(SAVE_TO_LIBRARY_TEST_SUBJ, parent);
await this.testSubjects.setValue('savedObjectTitle', newTitle, {
clearWithKeyboard: true,
});

View file

@ -124,6 +124,11 @@ export function DashboardCustomizePanelProvider({ getService, getPageObject }: F
await testSubjects.click('customEmbeddablePanelHideTitleSwitch');
}
public async getCustomPanelTitle() {
log.debug('getCustomPanelTitle');
return (await testSubjects.find('customEmbeddablePanelTitleInput')).getAttribute('value');
}
public async setCustomPanelTitle(customTitle: string) {
log.debug('setCustomPanelTitle');
await testSubjects.setValue('customEmbeddablePanelTitleInput', customTitle, {
@ -136,6 +141,13 @@ export function DashboardCustomizePanelProvider({ getService, getPageObject }: F
await testSubjects.click('resetCustomEmbeddablePanelTitleButton');
}
public async getCustomPanelDescription() {
log.debug('getCustomPanelDescription');
return (await testSubjects.find('customEmbeddablePanelDescriptionInput')).getAttribute(
'value'
);
}
public async setCustomPanelDescription(customDescription: string) {
log.debug('setCustomPanelDescription');
await testSubjects.setValue('customEmbeddablePanelDescriptionInput', customDescription, {

View file

@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
panels[0]
)
).to.be(true);
await dashboardPanelActions.legacyUnlinkFromLibary(panels[0]);
await dashboardPanelActions.legacyUnlinkFromLibrary(panels[0]);
await testSubjects.existOrFail('unlinkPanelSuccess');
panels = await testSubjects.findAll('embeddablePanel');
expect(panels.length).to.be(1);

View file

@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const dashboardPanelActions = getService('dashboardPanelActions');
const dashboardCustomizePanel = getService('dashboardCustomizePanel');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects([
'common',
'dashboard',
@ -23,11 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'lens',
]);
const DASHBOARD_NAME = 'Panel Title Test';
const CUSTOM_TITLE = 'Test Custom Title';
const EMPTY_TITLE = '[No Title]';
const LIBRARY_TITLE_FOR_CUSTOM_TESTS = 'Library Title for Custom Title Tests';
const LIBRARY_TITLE_FOR_EMPTY_TESTS = 'Library Title for Empty Title Tests';
describe('panel titles', () => {
before(async () => {
@ -39,13 +36,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.dashboard.navigateToApp();
await PageObjects.dashboard.preserveCrossAppState();
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.saveDashboard(DASHBOARD_NAME);
await PageObjects.dashboard.saveDashboard('Panel Title Test');
await PageObjects.lens.createAndAddLensFromDashboard({});
});
beforeEach(async () => {
// close any open flyouts to prevent dirty state between tests
if (await testSubjects.exists('euiFlyoutCloseButton')) {
await testSubjects.click('euiFlyoutCloseButton');
}
});
describe('by value', () => {
it('new panel by value has empty title', async () => {
await PageObjects.lens.createAndAddLensFromDashboard({});
const newPanelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
const [newPanelTitle] = await PageObjects.dashboard.getPanelTitles();
expect(newPanelTitle).to.equal(EMPTY_TITLE);
});
@ -58,78 +62,115 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('custom title causes unsaved changes and saving clears it', async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.setCustomPanelTitle(CUSTOM_TITLE);
await dashboardCustomizePanel.setCustomPanelTitle('Custom title');
await dashboardCustomizePanel.clickSaveButton();
const panelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
expect(panelTitle).to.equal(CUSTOM_TITLE);
const [panelTitle] = await PageObjects.dashboard.getPanelTitles();
expect(panelTitle).to.equal('Custom title');
await PageObjects.dashboard.clearUnsavedChanges();
});
it('resetting title on a by value panel sets it to the empty string', async () => {
const BY_VALUE_TITLE = 'Reset Title - By Value';
it('reset title should be hidden on a by value panel', async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.setCustomPanelTitle(BY_VALUE_TITLE);
await dashboardCustomizePanel.setCustomPanelTitle('Some title');
await dashboardCustomizePanel.clickSaveButton();
await dashboardPanelActions.customizePanel();
expect(await testSubjects.exists('resetCustomEmbeddablePanelTitleButton')).to.be(false);
});
it('reset description should be hidden on a by value panel', async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.resetCustomPanelTitle();
await dashboardCustomizePanel.setCustomPanelDescription('Some description');
await dashboardCustomizePanel.clickSaveButton();
const panelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
expect(panelTitle).to.equal(EMPTY_TITLE);
await PageObjects.dashboard.clearUnsavedChanges();
await dashboardPanelActions.customizePanel();
expect(await testSubjects.exists('resetCustomEmbeddablePanelDescriptionButton')).to.be(
false
);
});
});
describe('by reference', () => {
describe('nick by reference', () => {
const VIS_LIBRARY_DESCRIPTION = 'Vis library description';
let count = 0;
const getVisTitle = (increment = false) =>
`Vis Library Title - ${increment ? ++count : count}`;
it('linking a by value panel with a custom title to the library will overwrite the custom title with the library title', async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.setCustomPanelTitle(CUSTOM_TITLE);
await dashboardCustomizePanel.setCustomPanelTitle('Custom title');
await dashboardCustomizePanel.clickSaveButton();
await dashboardPanelActions.legacySaveToLibrary(LIBRARY_TITLE_FOR_CUSTOM_TESTS);
await retry.try(async () => {
await dashboardPanelActions.legacySaveToLibrary(getVisTitle(true));
await retry.tryForTime(500, async () => {
// need to surround in 'retry' due to delays in HTML updates causing the title read to be behind
const newPanelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
expect(newPanelTitle).to.equal(LIBRARY_TITLE_FOR_CUSTOM_TESTS);
const [newPanelTitle] = await PageObjects.dashboard.getPanelTitles();
expect(newPanelTitle).to.equal(getVisTitle());
});
});
it('resetting title on a by reference panel sets it to the library title', async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.setCustomPanelTitle('This should go away');
await dashboardCustomizePanel.setCustomPanelTitle('Custom Title');
await dashboardCustomizePanel.clickSaveButton();
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.resetCustomPanelTitle();
await dashboardCustomizePanel.clickSaveButton();
const resetPanelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
expect(resetPanelTitle).to.equal(LIBRARY_TITLE_FOR_CUSTOM_TESTS);
await dashboardPanelActions.customizePanel();
expect(await dashboardCustomizePanel.getCustomPanelTitle()).to.equal(getVisTitle());
});
it('resetting description on a by reference panel sets it to the library title', async () => {
await dashboardPanelActions.openContextMenu();
await dashboardPanelActions.navigateToEditorFromFlyout();
// legacySaveToLibrary UI cannot set description
await PageObjects.lens.save(
getVisTitle(true),
false,
undefined,
undefined,
undefined,
undefined,
VIS_LIBRARY_DESCRIPTION
);
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.setCustomPanelDescription('Custom description');
await dashboardCustomizePanel.clickSaveButton();
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.resetCustomPanelDescription();
await dashboardCustomizePanel.clickSaveButton();
await dashboardPanelActions.customizePanel();
expect(await dashboardCustomizePanel.getCustomPanelDescription()).to.equal(
VIS_LIBRARY_DESCRIPTION
);
});
it('unlinking a by reference panel with a custom title will keep the current title', async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.setCustomPanelTitle(CUSTOM_TITLE);
await dashboardCustomizePanel.setCustomPanelTitle('Custom title');
await dashboardCustomizePanel.clickSaveButton();
await dashboardPanelActions.legacyUnlinkFromLibary();
const newPanelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
expect(newPanelTitle).to.equal(CUSTOM_TITLE);
await dashboardPanelActions.legacyUnlinkFromLibrary();
const [newPanelTitle] = await PageObjects.dashboard.getPanelTitles();
expect(newPanelTitle).to.equal('Custom title');
});
it("linking a by value panel with a blank title to the library will set the panel's title to the library title", async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.setCustomPanelTitle('');
await dashboardCustomizePanel.clickSaveButton();
await dashboardPanelActions.legacySaveToLibrary(LIBRARY_TITLE_FOR_EMPTY_TESTS);
await retry.try(async () => {
await dashboardPanelActions.legacySaveToLibrary(getVisTitle(true));
await retry.tryForTime(500, async () => {
// need to surround in 'retry' due to delays in HTML updates causing the title read to be behind
const newPanelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
expect(newPanelTitle).to.equal(LIBRARY_TITLE_FOR_EMPTY_TESTS);
const [newPanelTitle] = await PageObjects.dashboard.getPanelTitles();
expect(newPanelTitle).to.equal(getVisTitle());
});
});
it('unlinking a by reference panel without a custom title will keep the library title', async () => {
await dashboardPanelActions.legacyUnlinkFromLibary();
const newPanelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
expect(newPanelTitle).to.equal(LIBRARY_TITLE_FOR_EMPTY_TESTS);
await dashboardPanelActions.legacyUnlinkFromLibrary();
const [newPanelTitle] = await PageObjects.dashboard.getPanelTitles();
expect(newPanelTitle).to.equal(getVisTitle());
});
});
});

View file

@ -242,7 +242,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboardAddPanel.closeAddPanel();
const originalPanel = await testSubjects.find('embeddablePanelHeading-lnsPieVis');
await panelActions.legacyUnlinkFromLibary(originalPanel);
await panelActions.legacyUnlinkFromLibrary(originalPanel);
await testSubjects.existOrFail('unlinkPanelSuccess');
const updatedPanel = await testSubjects.find('embeddablePanelHeading-lnsPieVis');

View file

@ -59,7 +59,7 @@ export default function ({ getPageObjects, getService }) {
it('unlink map panel from embeddable library', async () => {
const originalPanel = await testSubjects.find('embeddablePanelHeading-embeddablelibrarymap');
await dashboardPanelActions.unlinkFromLibary(originalPanel);
await dashboardPanelActions.unlinkFromLibrary(originalPanel);
await testSubjects.existOrFail('unlinkPanelSuccess');
const updatedPanel = await testSubjects.find('embeddablePanelHeading-embeddablelibrarymap');

View file

@ -746,7 +746,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
redirectToOrigin?: boolean,
saveToLibrary?: boolean,
addToDashboard?: 'new' | 'existing' | null,
dashboardId?: string
dashboardId?: string,
description?: string
) {
await PageObjects.timeToVisualize.setSaveModalValues(title, {
saveAsNew,
@ -754,6 +755,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
addToDashboard: addToDashboard ? addToDashboard : null,
dashboardId,
saveToLibrary,
description,
});
await testSubjects.click('confirmSaveSavedObjectButton');
@ -774,7 +776,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
redirectToOrigin?: boolean,
saveToLibrary?: boolean,
addToDashboard?: 'new' | 'existing' | null,
dashboardId?: string
dashboardId?: string,
description?: string
) {
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.click('lnsApp_saveButton');
@ -785,7 +788,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
redirectToOrigin,
saveToLibrary,
addToDashboard,
dashboardId
dashboardId,
description
);
},

View file

@ -59,7 +59,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// Convert to by-value
const byRefPanel = await testSubjects.find('embeddablePanelHeading-' + lensTitle);
await dashboardPanelActions.legacyUnlinkFromLibary(byRefPanel);
await dashboardPanelActions.legacyUnlinkFromLibrary(byRefPanel);
await PageObjects.dashboard.waitForRenderComplete();
const byValueSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle);