From 239f964fc9364c876ff18043ca02ef69b0fd9bc1 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 26 Apr 2018 08:16:56 -0600 Subject: [PATCH] Eui dashboard listing (#16967) * convert dashboard listing page to react and EUI * add jest test for DashboardListing component * add data-test-subj attributes * clean up jest test * hideWriteControls and call to action when no dashboards exist * pass initial filter to dashboard listing, get functional tests to work * fix dashboard queries functional tests * upgraded to EUI 0.0.29 to get defaultFocusedButton fix * move dashboardListing directive to index * spacing in if statement * switch to EuiBasicTable * pagination * add sorting * fix jest test * handle out of order fetchs * remove info.gif * re-instate search functional test * replace EuiSearchBar with EuiFieldSearch * fix functional tests * update snapshot - when code rebased - new EUI version add another prop * add Edit link to actions column --- .../kibana/public/dashboard/index.js | 31 +- .../dashboard_listing.test.js.snap | 788 ++++++++++++++++++ .../dashboard/listing/dashboard_listing.html | 268 ------ .../dashboard/listing/dashboard_listing.js | 514 +++++++++--- .../dashboard/listing/dashboard_listing.less | 9 - .../listing/dashboard_listing.test.js | 126 +++ .../listing/dashboard_listing_ng_wrapper.html | 7 + .../apps/dashboard/_dashboard_listing.js | 8 +- .../functional/page_objects/dashboard_page.js | 13 +- 9 files changed, 1340 insertions(+), 424 deletions(-) create mode 100644 src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap delete mode 100644 src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html delete mode 100644 src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.less create mode 100644 src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js create mode 100644 src/core_plugins/kibana/public/dashboard/listing/dashboard_listing_ng_wrapper.html diff --git a/src/core_plugins/kibana/public/dashboard/index.js b/src/core_plugins/kibana/public/dashboard/index.js index bcad8f3293a11..cd135c05fec16 100644 --- a/src/core_plugins/kibana/public/dashboard/index.js +++ b/src/core_plugins/kibana/public/dashboard/index.js @@ -6,14 +6,25 @@ import uiRoutes from 'ui/routes'; import { toastNotifications } from 'ui/notify'; import dashboardTemplate from './dashboard_app.html'; -import dashboardListingTemplate from './listing/dashboard_listing.html'; +import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; -import { DashboardListingController } from './listing/dashboard_listing'; import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; import { SavedObjectNotFound } from 'ui/errors'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { recentlyAccessed } from 'ui/persisted_log'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; +import { uiModules } from 'ui/modules'; + +const app = uiModules.get('app/dashboard', [ + 'ngRoute', + 'react', +]); + +app.directive('dashboardListing', function (reactDirective) { + return reactDirective(DashboardListing); +}); uiRoutes .defaults(/dashboard/, { @@ -21,8 +32,20 @@ uiRoutes }) .when(DashboardConstants.LANDING_PAGE_PATH, { template: dashboardListingTemplate, - controller: DashboardListingController, - controllerAs: 'listingController', + controller($injector, $location, $scope, Private, config) { + const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; + const dashboardConfig = $injector.get('dashboardConfig'); + + $scope.listingLimit = config.get('savedObjects:listingLimit'); + $scope.find = (search) => { + return services.dashboards.find(search, $scope.listingLimit); + }; + $scope.delete = (ids) => { + return services.dashboards.delete(ids); + }; + $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); + $scope.initialFilter = ($location.search()).filter || EMPTY_FILTER; + }, resolve: { dash: function ($route, Private, courier, kbnUrl) { const savedObjectsClient = Private(SavedObjectsClientProvider); diff --git a/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap new file mode 100644 index 0000000000000..3b999bf8a3ac6 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap @@ -0,0 +1,788 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`after fetch hideWriteControls 1`] = ` + + + + +

+ Dashboard +

+
+
+
+ + + + + + + +

+ + Looks like you don't have any dashboards. + +

+ + } + onChange={[Function]} + pagination={ + Object { + "pageIndex": 0, + "pageSize": 20, + "pageSizeOptions": Array [ + 10, + 20, + 50, + ], + "totalItemCount": 0, + } + } + selection={ + Object { + "itemId": "id", + "onSelectionChange": [Function], + } + } + sorting={Object {}} + /> +
+`; + +exports[`after fetch renders call to action when no dashboards exist 1`] = ` + + + + +

+ Dashboard +

+
+
+ + + Create new dashboard + + +
+ + + + + + + + +

+ + Looks like you don't have any dashboards. Let's create some! + +

+
+ + Create new dashboard + + + } + onChange={[Function]} + pagination={ + Object { + "pageIndex": 0, + "pageSize": 20, + "pageSizeOptions": Array [ + 10, + 20, + 50, + ], + "totalItemCount": 0, + } + } + selection={ + Object { + "itemId": "id", + "onSelectionChange": [Function], + } + } + sorting={Object {}} + /> +
+`; + +exports[`after fetch renders table rows 1`] = ` + + + + +

+ Dashboard +

+
+
+ + + Create new dashboard + + +
+ + + + + + + +
+`; + +exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` + + + + +

+ Dashboard +

+
+
+ + + Create new dashboard + + +
+ + + +

+ You have + 2 + dashboards, but your + + listingLimit + + setting prevents the table below from displaying more than + 1 + . You can change this setting under + + Advanced Settings + + . +

+
+ +
+ + + + + + +
+`; + +exports[`initialFilter 1`] = ` + + + + +

+ Dashboard +

+
+
+ + + Create new dashboard + + +
+ + + + + + + +
+`; + +exports[`renders table in loading state 1`] = ` + + + + +

+ Dashboard +

+
+
+ + + Create new dashboard + + +
+ + + + + + + +
+`; diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html deleted file mode 100644 index c696083101565..0000000000000 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.html +++ /dev/null @@ -1,268 +0,0 @@ - - - -
- -
- Dashboard -
-
-
- -
-
-
-
-
- You have {{ listingController.totalItems }} dashboards, but your "listingLimit" setting prevents the table below from displaying more than {{ listingController.listingLimit }}. You can change this setting under Advanced Settings. -
-
-
-
- -
- -
-
-
- - -
-
- -
- - - - - - - -
- -
- - - -
-
- - -
-
- No dashboards matched your search. -
-
- - -
-
-
- Looks like you don’t have any dashboards. Let’s create some! -
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
- - - - -
Actions
-
-
- -
-
- - -
- {{ item.description }} -
-
- -
- - -
-
-
- {{ listingController.getSelectedItemsCount() }} selected -
-
- -
- - - -
-
-
-
diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index b5b91dd275a25..658dcac84570c 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -1,168 +1,410 @@ -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import 'ui/pager_control'; -import 'ui/pager'; -import './dashboard_listing.less'; +import React from 'react'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import { toastNotifications } from 'ui/notify'; +import { + EuiTitle, + EuiFieldSearch, + EuiBasicTable, + EuiPage, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiSpacer, + EuiOverlayMask, + EuiConfirmModal, + EuiCallOut, + EuiText, + EuiTextColor, +} from '@elastic/eui'; import { DashboardConstants, createDashboardEditUrl } from '../dashboard_constants'; -import { SortableProperties } from '@elastic/eui'; -import { ConfirmationButtonTypes } from 'ui/modals'; - -export function DashboardListingController($injector, $scope, $location) { - const $filter = $injector.get('$filter'); - const confirmModal = $injector.get('confirmModal'); - const Notifier = $injector.get('Notifier'); - const pagerFactory = $injector.get('pagerFactory'); - const Private = $injector.get('Private'); - const timefilter = $injector.get('timefilter'); - const config = $injector.get('config'); - const dashboardConfig = $injector.get('dashboardConfig'); - - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - - const limitTo = $filter('limitTo'); - // TODO: Extract this into an external service. - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; - const dashboardService = services.dashboards; - const notify = new Notifier({ location: 'Dashboard' }); - - let selectedItems = []; - const sortableProperties = new SortableProperties([ - { - name: 'title', - getValue: item => item.title.toLowerCase(), - isAscending: true, - }, - { - name: 'description', - getValue: item => item.description.toLowerCase(), - isAscending: true + +export const EMPTY_FILTER = ''; + +// saved object client does not support sorting by title because title is only mapped as analyzed +// the legacy implementation got around this by pulling `listingLimit` items and doing client side sorting +// and not supporting server-side paging. +// This component does not try to tackle these problems (yet) and is just feature matching the legacy component +// TODO support server side sorting/paging once title and description are sortable on the server. +export class DashboardListing extends React.Component { + + constructor(props) { + super(props); + + this.state = { + isFetchingItems: false, + showDeleteModal: false, + showLimitError: false, + filter: this.props.initialFilter, + dashboards: [], + selectedIds: [], + page: 0, + perPage: 20, + }; + } + + componentWillMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + this.debouncedFetch.cancel(); + } + + componentDidMount() { + this.fetchItems(); + } + + debouncedFetch = _.debounce(async (filter) => { + const response = await this.props.find(filter); + + if (!this._isMounted) { + return; } - ], - 'title'); - const calculateItemsOnPage = () => { - this.items = sortableProperties.sortItems(this.items); - this.pager.setTotalItems(this.items.length); - this.pageOfItems = limitTo(this.items, this.pager.pageSize, this.pager.startIndex); - }; + // We need this check to handle the case where search results come back in a different + // order than they were sent out. Only load results for the most recent search. + if (filter === this.state.filter) { + this.setState({ + isFetchingItems: false, + dashboards: response.hits, + totalDashboards: response.total, + showLimitError: response.total > this.props.listingLimit, + }); + } + }, 300); - const fetchItems = () => { - this.isFetchingItems = true; - - dashboardService.find(this.filter, config.get('savedObjects:listingLimit')) - .then(result => { - this.isFetchingItems = false; - this.items = result.hits; - this.totalItems = result.total; - this.showLimitError = result.total > config.get('savedObjects:listingLimit'); - this.listingLimit = config.get('savedObjects:listingLimit'); - calculateItemsOnPage(); + fetchItems = () => { + this.setState({ + isFetchingItems: true, + }, this.debouncedFetch.bind(null, this.state.filter)); + } + + deleteSelectedItems = async () => { + try { + await this.props.delete(this.state.selectedIds); + } catch (error) { + toastNotifications.addDanger({ + title: `Unable to delete dashboard(s)`, + text: `${error}`, }); - }; + } + this.fetchItems(); + this.setState({ + selectedIds: [] + }); + this.closeDeleteModal(); + } - const deselectAll = () => { - selectedItems = []; + closeDeleteModal = () => { + this.setState({ showDeleteModal: false }); }; - const selectAll = () => { - selectedItems = this.pageOfItems.slice(0); + openDeleteModal = () => { + this.setState({ showDeleteModal: true }); }; - this.isFetchingItems = false; - this.items = []; - this.pageOfItems = []; - this.filter = ($location.search()).filter || ''; + onTableChange = ({ page, sort = {} }) => { + const { + index: pageIndex, + size: pageSize, + } = page; - this.pager = pagerFactory.create(this.items.length, 20, 1); + let { + field: sortField, + direction: sortDirection, + } = sort; - this.hideWriteControls = dashboardConfig.getHideWriteControls(); + // 3rd sorting state that is not captured by sort - native order (no sort) + // when switching from desc to asc for the same field - use native order + if (this.state.sortField === sortField + && this.state.sortDirection === 'desc' + && sortDirection === 'asc') { + sortField = null; + sortDirection = null; + } - $scope.$watch(() => this.filter, () => { - deselectAll(); - fetchItems(); - $location.search('filter', this.filter); - }); - this.isAscending = (name) => sortableProperties.isAscendingByName(name); - this.getSortedProperty = () => sortableProperties.getSortedProperty(); + this.setState({ + page: pageIndex, + perPage: pageSize, + sortField, + sortDirection, + }); + } - this.sortOn = function sortOn(propertyName) { - sortableProperties.sortOn(propertyName); - deselectAll(); - calculateItemsOnPage(); - }; + // server-side paging not supported - see component comment for details + getPageOfItems = () => { + // do not sort original list to preserve elasticsearch ranking order + const dashboardsCopy = this.state.dashboards.slice(); - this.toggleAll = function toggleAll() { - if (this.areAllItemsChecked()) { - deselectAll(); - } else { - selectAll(); + if (this.state.sortField) { + dashboardsCopy.sort((a, b) => { + const fieldA = _.get(a, this.state.sortField, ''); + const fieldB = _.get(b, this.state.sortField, ''); + let order = 1; + if (this.state.sortDirection === 'desc') { + order = -1; + } + return order * fieldA.toLowerCase().localeCompare(fieldB.toLowerCase()); + }); } - }; - this.toggleItem = function toggleItem(item) { - if (this.isItemChecked(item)) { - const index = selectedItems.indexOf(item); - selectedItems.splice(index, 1); - } else { - selectedItems.push(item); + // If begin is greater than the length of the sequence, an empty array is returned. + const startIndex = this.state.page * this.state.perPage; + // If end is greater than the length of the sequence, slice extracts through to the end of the sequence (arr.length). + const lastIndex = startIndex + this.state.perPage; + return dashboardsCopy.slice(startIndex, lastIndex); + } + + renderConfirmDeleteModal() { + return ( + + +

{`You can't recover deleted dashboards.`}

+
+
+ ); + } + + renderListingLimitWarning() { + if (this.state.showLimitError) { + return ( + + +

+ You have {this.state.totalDashboards} dashboards, + but your listingLimit setting prevents the table below from displaying more than {this.props.listingLimit}. + You can change this setting under Advanced Settings. +

+
+ +
+ ); } - }; + } - this.isItemChecked = function isItemChecked(item) { - return selectedItems.indexOf(item) !== -1; - }; + renderNoItemsMessage() { + if (this.state.isFetchingItems) { + return ''; + } - this.areAllItemsChecked = function areAllItemsChecked() { - return this.getSelectedItemsCount() === this.pageOfItems.length; - }; + if (!this.state.isFetchingItems && this.state.dashboards.length === 0 && !this.state.filter) { + if (this.props.hideWriteControls) { + return ( + +

+ + {`Looks like you don't have any dashboards.`} + +

+
+ ); + } - this.getSelectedItemsCount = function getSelectedItemsCount() { - return selectedItems.length; - }; + return ( + + +

+ + {`Looks like you don't have any dashboards. Let's create some!`} + +

+
+ + Create new dashboard + +
+ ); + } - this.deleteSelectedItems = function deleteSelectedItems() { - const doDelete = () => { - const selectedIds = selectedItems.map(item => item.id); + return 'No dashboards matched your search.'; + } - dashboardService.delete(selectedIds) - .then(fetchItems) - .then(() => { - deselectAll(); - }) - .catch(error => notify.error(error)); - }; + renderSearchBar() { + let deleteBtn; + if (this.state.selectedIds.length > 0) { + deleteBtn = ( + + + Delete selected + + + ); + } + + return ( + + {deleteBtn} + + { + this.setState({ + filter: e.target.value + }, this.fetchItems); + }} + data-test-subj="searchFilter" + /> + + + ); + } - confirmModal( - `You can't recover deleted dashboards.`, + renderTable() { + const tableColumns = [ + { + field: 'title', + name: 'Title', + sortable: true, + render: (field, record) => ( + + {field} + + ) + }, { - confirmButtonText: 'Delete', - onConfirm: doDelete, - defaultFocusedButton: ConfirmationButtonTypes.CANCEL, - title: 'Delete selected dashboards?' + field: 'description', + name: 'Description', + dataType: 'string', + sortable: true, + } + ]; + if (!this.props.hideWriteControls) { + tableColumns.push({ + name: 'Actions', + actions: [ + { + render: (record) => { + return ( + + Edit + + ); + } + } + ] }); - }; + } + const pagination = { + pageIndex: this.state.page, + pageSize: this.state.perPage, + totalItemCount: this.state.dashboards.length, + pageSizeOptions: [10, 20, 50], + }; + const selection = { + itemId: 'id', + onSelectionChange: (selection) => { + this.setState({ + selectedIds: selection.map(item => { return item.id; }) + }); + } + }; + const sorting = {}; + if (this.state.sortField) { + sorting.sort = { + field: this.state.sortField, + direction: this.state.sortDirection, + }; + } + const items = this.state.dashboards.length === 0 ? [] : this.getPageOfItems(); + return ( + + ); + } - this.onPageNext = () => { - deselectAll(); - this.pager.nextPage(); - calculateItemsOnPage(); - }; + render() { + let createButton; + if (!this.props.hideWriteControls) { + createButton = ( + + + Create new dashboard + + + ); + } + return ( + - this.onPagePrevious = () => { - deselectAll(); - this.pager.previousPage(); - calculateItemsOnPage(); - }; + {this.state.showDeleteModal && this.renderConfirmDeleteModal()} - this.getUrlForItem = function getUrlForItem(item) { - return `#${createDashboardEditUrl(item.id)}`; - }; + + + +

+ Dashboard +

+
+
- this.getEditUrlForItem = function getEditUrlForItem(item) { - return `#${createDashboardEditUrl(item.id)}?_a=(viewMode:edit)`; - }; + {createButton} - this.getCreateDashboardHref = function getCreateDashboardHref() { - return `#${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`; - }; +
+ + + + {this.renderListingLimitWarning()} + + {this.renderSearchBar()} + + {this.renderTable()} + +
+ ); + } } + +DashboardListing.propTypes = { + find: PropTypes.func.isRequired, + delete: PropTypes.func.isRequired, + listingLimit: PropTypes.number.isRequired, + hideWriteControls: PropTypes.bool.isRequired, + initialFilter: PropTypes.string, +}; + +DashboardListing.defaultProps = { + initialFilter: EMPTY_FILTER, +}; diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.less b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.less deleted file mode 100644 index 5afa8bf68d145..0000000000000 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.less +++ /dev/null @@ -1,9 +0,0 @@ -.dashboardListingTable { - .kuiTableHeaderCell { - max-width: none; - } - - .actionBtnHeaderCell { - width: 80px; - } -} diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js new file mode 100644 index 0000000000000..80566bfb18814 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js @@ -0,0 +1,126 @@ +jest.mock('ui/notify', + () => ({ + toastNotifications: { + addWarning: () => {}, + } + }), { virtual: true }); + +jest.mock('lodash', + () => ({ + // mock debounce to fire immediately with no internal timer + debounce: function (func) { + function debounced(...args) { + return func.apply(this, args); + } + return debounced; + } + }), { virtual: true }); + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + DashboardListing, +} from './dashboard_listing'; + +const find = (num) => { + const hits = []; + for (let i = 0; i < num; i++) { + hits.push({ + id: `dashboard${i}`, + title: `dashboard${i} title`, + description: `dashboard${i} desc` + }); + } + return Promise.resolve({ + total: num, + hits: hits + }); +}; + +test('renders table in loading state', () => { + const component = shallow( {}} + listingLimit={1000} + hideWriteControls={false} + />); + expect(component).toMatchSnapshot(); +}); + +test('initialFilter', () => { + const component = shallow( {}} + listingLimit={1000} + hideWriteControls={false} + initialFilter="my dashboard" + />); + expect(component).toMatchSnapshot(); +}); + +describe('after fetch', () => { + test('renders table rows', async () => { + const component = shallow( {}} + listingLimit={1000} + hideWriteControls={false} + />); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('renders call to action when no dashboards exist', async () => { + const component = shallow( {}} + listingLimit={1} + hideWriteControls={false} + />); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('hideWriteControls', async () => { + const component = shallow( {}} + listingLimit={1} + hideWriteControls={true} + />); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('renders warning when listingLimit is exceeded', async () => { + const component = shallow( {}} + listingLimit={1} + hideWriteControls={false} + />); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing_ng_wrapper.html b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing_ng_wrapper.html new file mode 100644 index 0000000000000..8fb0cfe5621c5 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing_ng_wrapper.html @@ -0,0 +1,7 @@ + diff --git a/test/functional/apps/dashboard/_dashboard_listing.js b/test/functional/apps/dashboard/_dashboard_listing.js index bd2506ea8513a..b8436b256c10a 100644 --- a/test/functional/apps/dashboard/_dashboard_listing.js +++ b/test/functional/apps/dashboard/_dashboard_listing.js @@ -42,8 +42,8 @@ export default function ({ getService, getPageObjects }) { describe('delete', async function () { it('default confirm action is cancel', async function () { - await PageObjects.dashboard.searchForDashboardWithName(''); - await PageObjects.dashboard.clickListItemCheckbox(); + await PageObjects.dashboard.searchForDashboardWithName(dashboardName); + await PageObjects.dashboard.checkDashboardListingSelectAllCheckbox(); await PageObjects.dashboard.clickDeleteSelectedDashboards(); await PageObjects.common.pressEnterKey(); @@ -56,7 +56,7 @@ export default function ({ getService, getPageObjects }) { }); it('succeeds on confirmation press', async function () { - await PageObjects.dashboard.clickListItemCheckbox(); + await PageObjects.dashboard.checkDashboardListingSelectAllCheckbox(); await PageObjects.dashboard.clickDeleteSelectedDashboards(); await PageObjects.common.clickConfirmOnModal(); @@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }) { describe('search', function () { before(async () => { await PageObjects.dashboard.clearSearchValue(); - await PageObjects.dashboard.clickCreateDashboardPrompt(); + await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('Two Words'); }); diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.js index 70d4f9dd729aa..6964d60eb387c 100644 --- a/test/functional/page_objects/dashboard_page.js +++ b/test/functional/page_objects/dashboard_page.js @@ -198,8 +198,13 @@ export function DashboardPageProvider({ getService, getPageObjects }) { return await testSubjects.exists('createDashboardPromptButton'); } - async clickListItemCheckbox() { - await testSubjects.click('dashboardListItemCheckbox'); + async checkDashboardListingSelectAllCheckbox() { + const element = await testSubjects.find('checkboxSelectAll'); + const isSelected = await element.isSelected(); + if (!isSelected) { + log.debug(`checking checkbox "checkboxSelectAll"`); + await testSubjects.click('checkboxSelectAll'); + } } async clickDeleteSelectedDashboards() { @@ -366,6 +371,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) { await retry.try(async () => { const searchFilter = await testSubjects.find('searchFilter'); await searchFilter.clearValue(); + await PageObjects.common.pressEnterKey(); }); } @@ -385,13 +391,14 @@ export function DashboardPageProvider({ getService, getPageObjects }) { await searchFilter.click(); // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. await searchFilter.type(dashName.replace('-', ' ')); + await PageObjects.common.pressEnterKey(); }); await PageObjects.header.waitUntilLoadingHasFinished(); } async getCountOfDashboardsInListingTable() { - const dashboardTitles = await testSubjects.findAll('dashboardListingRow'); + const dashboardTitles = await find.allByCssSelector('.dashboardLink'); return dashboardTitles.length; }