kibana/test/functional/page_objects/dashboard_page_links.ts
Hannah Mudge 9e8312f2e4
[Dashboard Navigation] Make links panel available under technical preview (#166896)
## Summary
This PR wraps up the work the @elastic/kibana-presentation team has done
to finish the MVP of [Phase
1](https://github.com/elastic/kibana/issues/154354) of the `Link`
embeddable, which enables users to add panels to their dashboard that
contain links to other dashboards + external links - with respect to
dashboard links, we give the author control over which pieces of context
should be kept across dashboards so that things like filter pills,
queries, and time ranges are not lost. This marks a huge improvement in
dashboard navigation overall, which was previously only available via a
variety of different workarounds including (but not limited to):
- Creating (essentially) a `noop` dashboard-to-dashboard drilldown 
- Using markdown panels with hard Dashboard links, which are prone to
break across updates
- Avoiding navigation all together, which resulted in large,
slow-to-load dashboards.

As an added benefit, because these panels contain **references** to each
dashboard rather than hard links, (1) unlike markdown links, they should
not break after updates and (2) if a links panel is exported and
imported into another space or instance, all of the dashboards it links
to will also be imported.



1a86b713-47e7-4db9-8a04-29d41b13681a

> **Note**
> 🔉 The above video has audio! Turn on your sound for the best
experience.

### Note about this PR
- A majority of this work was done on a feature branch, with thorough
reviews from @andreadelrio on behalf of @elastic/kibana-design along the
way. Therefore, while feedback on the design is encouraged, any large
concerns brought up in this PR should be filed as separate issues and
addressed in follow-up PRs.
- This PR contains work for giving embeddables control over their own
panel size / default positioning on the dashboard. This was especially
important for the links panel, since we assume that (a) most links
panels would be located somewhere near the top of the dashboard and (b)
the horizontal links panel should have a different default "shape"
(longer than it is tall) than the vertical panel (taller than it is
long).
- This PR also contains work for caching dashboard saved objects, which
makes navigation much more seamless.

### Flaky Test Runner
-
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3251


![image](7616443e-0cb0-43ce-a1d0-41f8bee6cbfc)


### Checklist

- [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)
- [ ]
~[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials~ This will
be addressed in a follow up:
https://github.com/elastic/kibana/issues/166750
- [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 - ~Units tests
are added, functional tests are forthcoming~ Edit: All tests are in.
- [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 renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [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)

---------

Co-authored-by: Nick Peihl <nick.peihl@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Andrea Del Rio <delrio.andre@gmail.com>
Co-authored-by: Devon Thomson <devon.thomson@elastic.co>
Co-authored-by: Nick Peihl <nickpeihl@gmail.com>
Co-authored-by: Gerard Soldevila <gerard.soldevila@elastic.co>
2023-09-29 08:25:51 -06:00

191 lines
7.1 KiB
TypeScript

/*
* 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 { LinksLayoutType } from '@kbn/links-plugin/common/content_management';
import { FtrService } from '../ftr_provider_context';
export class DashboardPageLinks extends FtrService {
private readonly retry = this.ctx.getService('retry');
private readonly browser = this.ctx.getService('browser');
private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly comboBox = this.ctx.getService('comboBox');
private readonly header = this.ctx.getPageObject('header');
private readonly settings = this.ctx.getPageObject('settings');
public async toggleLinksLab(value?: boolean) {
await this.header.clickStackManagement();
await this.settings.clickKibanaSettings();
await this.settings.toggleAdvancedSettingCheckbox('labs:dashboard:linksPanel', value);
}
/* -----------------------------------------------------------
Links panel
----------------------------------------------------------- */
public async getAllLinksInPanel() {
const listGroup = await this.testSubjects.find('links--component--listGroup');
return await listGroup.findAllByCssSelector('li');
}
public async getNumberOfLinksInPanel() {
const links = await this.getAllLinksInPanel();
return links.length;
}
/* -----------------------------------------------------------
Links flyout
----------------------------------------------------------- */
public async expectFlyoutIsOpen() {
await this.testSubjects.exists('links--panelEditor--flyout');
}
public async clickPanelEditorSaveButton() {
await this.expectFlyoutIsOpen();
await this.testSubjects.clickWhenNotDisabled('links--panelEditor--saveBtn');
}
public async clickLinkEditorCloseButton() {
await this.testSubjects.click('links--linkEditor--closeBtn');
}
public async clickPanelEditorCloseButton() {
await this.testSubjects.click('links--panelEditor--closeBtn');
}
public async clickLinksEditorSaveButton() {
await this.testSubjects.clickWhenNotDisabled('links--linkEditor--saveBtn');
}
public async findDraggableLinkByIndex(index: number) {
await this.testSubjects.exists('links--panelEditor--flyout');
const linksFormRow = await this.testSubjects.find('links--panelEditor--linksAreaDroppable');
return await linksFormRow.findByCssSelector(
`[data-test-subj="links--panelEditor--draggableLink"]:nth-child(${index})`
);
}
public async addDashboardLink(
destination: string,
useCurrentFilters: boolean = true,
useCurrentDateRange: boolean = true,
openInNewTab: boolean = false,
linkLabel?: string
) {
await this.expectFlyoutIsOpen();
await this.testSubjects.click('links--panelEditor--addLinkBtn');
await this.testSubjects.exists('links--linkEditor--flyout');
const radioOption = await this.testSubjects.find('links--linkEditor--dashboardLink--radioBtn');
const label = await radioOption.findByCssSelector('label[for="dashboardLink"]');
await label.click();
await this.comboBox.set('links--linkEditor--dashboardLink--comboBox', destination);
if (linkLabel) {
await this.testSubjects.setValue('links--linkEditor--linkLabel--input', linkLabel);
}
await this.testSubjects.setEuiSwitch(
'dashboardDrillDownOptions--useCurrentFilters--checkbox',
useCurrentFilters ? 'check' : 'uncheck'
);
await this.testSubjects.setEuiSwitch(
'dashboardDrillDownOptions--useCurrentDateRange--checkbox',
useCurrentDateRange ? 'check' : 'uncheck'
);
await this.testSubjects.setEuiSwitch(
'dashboardDrillDownOptions--openInNewTab--checkbox',
openInNewTab ? 'check' : 'uncheck'
);
await this.clickLinksEditorSaveButton();
}
public async addExternalLink(
destination: string,
openInNewTab: boolean = true,
encodeUrl: boolean = true,
linkLabel?: string
) {
await this.setExternalUrlInput(destination);
if (linkLabel) {
await this.testSubjects.setValue('links--linkEditor--linkLabel--input', linkLabel);
}
await this.testSubjects.setEuiSwitch(
'urlDrilldownOpenInNewTab',
openInNewTab ? 'check' : 'uncheck'
);
await this.testSubjects.setEuiSwitch('urlDrilldownEncodeUrl', encodeUrl ? 'check' : 'uncheck');
await this.clickLinksEditorSaveButton();
}
public async deleteLinkByIndex(index: number) {
const linkToDelete = await this.findDraggableLinkByIndex(index);
await this.retry.try(async () => {
await linkToDelete.moveMouseTo();
await this.testSubjects.existOrFail(`panelEditorLink--deleteBtn`);
});
const deleteButton = await linkToDelete.findByTestSubject(`panelEditorLink--deleteBtn`);
await deleteButton.click();
}
public async editLinkByIndex(index: number) {
const linkToEdit = await this.findDraggableLinkByIndex(index);
await this.retry.try(async () => {
await linkToEdit.moveMouseTo();
await this.testSubjects.existOrFail(`panelEditorLink--editBtn`);
});
const editButton = await linkToEdit.findByTestSubject(`panelEditorLink--editBtn`);
await editButton.click();
}
public async reorderLinks(linkLabel: string, startIndex: number, steps: number, reverse = false) {
const linkToMove = await this.findDraggableLinkByIndex(startIndex);
const draggableButton = await linkToMove.findByTestSubject(`panelEditorLink--dragHandle`);
expect(await draggableButton.getAttribute('data-rfd-drag-handle-draggable-id')).to.equal(
linkLabel
);
await draggableButton.focus();
await this.browser.pressKeys(this.browser.keys.SPACE);
for (let i = 0; i < steps; i++) {
await this.browser.pressKeys(reverse ? this.browser.keys.UP : this.browser.keys.DOWN);
}
await this.browser.pressKeys(this.browser.keys.SPACE);
await this.retry.try(async () => {
expect(await linkToMove.elementHasClass('euiDraggable--isDragging')).to.be(false);
});
}
public async setLayout(layout: LinksLayoutType) {
await this.expectFlyoutIsOpen();
const testSubj = `links--panelEditor--${layout}LayoutBtn`;
await this.testSubjects.click(testSubj);
}
public async setExternalUrlInput(destination: string) {
await this.expectFlyoutIsOpen();
await this.testSubjects.click('links--panelEditor--addLinkBtn');
await this.testSubjects.exists('links--linkEditor--flyout');
const option = await this.testSubjects.find('links--linkEditor--externalLink--radioBtn');
const label = await option.findByCssSelector('label[for="externalLink"]');
await label.click();
await this.testSubjects.setValue('links--linkEditor--externalLink--input', destination);
}
public async toggleSaveByReference(checked: boolean) {
await this.expectFlyoutIsOpen();
await this.testSubjects.setEuiSwitch(
'links--panelEditor--saveByReferenceSwitch',
checked ? 'check' : 'uncheck'
);
}
}