From 43639cd9c0d189ae9ecf10a56061d5c559fc78f1 Mon Sep 17 00:00:00 2001 From: "dave.snider@gmail.com" Date: Tue, 12 Jun 2018 17:43:20 -0700 Subject: [PATCH] [Design] Clean up dashboard listing page (#19657) Along with @nreese, cleaned up the dashboard listing view to add an empty state. --- .../dashboard_listing.test.js.snap | 1285 +++++++---------- .../dashboard/listing/dashboard_listing.js | 131 +- .../listing/dashboard_listing.test.js | 30 +- .../kibana/public/dashboard/styles/index.less | 10 + .../functional/page_objects/dashboard_page.js | 9 +- test/functional/page_objects/header_page.js | 9 +- 6 files changed, 664 insertions(+), 810 deletions(-) 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 index 03bf60ae1ec0..b8bec4e252ed 100644 --- 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 @@ -2,81 +2,17 @@ exports[`after fetch hideWriteControls 1`] = ` - - + - -

- Dashboard -

-
-
-
- - - - - - - @@ -88,729 +24,564 @@ exports[`after fetch hideWriteControls 1`] = ` - } - onChange={[Function]} - pagination={ - Object { - "pageIndex": 0, - "pageSize": 20, - "pageSizeOptions": Array [ - 10, - 20, - 50, - ], - "totalItemCount": 0, - } - } - responsive={true} - selection={ - Object { - "onSelectionChange": [Function], - } - } - sorting={Object {}} - /> + + +
+`; + +exports[`after fetch initialFilter 1`] = ` + + + +
+ + + +

+ Dashboards +

+
+
+ + + Create new dashboard + + +
+ + + + + + + + +
+
+
`; 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, - } - } - responsive={true} - selection={ - Object { - "onSelectionChange": [Function], - } - } - sorting={Object {}} - /> + Create new dashboard + + } + body={ + +

+ You can combine data views from any Kibana app into one dashboard and see everything in one place. +

+

+ New to Kibana? + + Install some sample data + + to take a test drive. +

+
+ } + iconColor="subdued" + iconType="dashboardApp" + title={ +

+ Create your first dashboard +

+ } + /> + + +
`; exports[`after fetch renders table rows 1`] = ` - - + - -

- Dashboard -

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

+ Dashboards +

+
+
+ + + Create new dashboard + + +
+ + + + + + + + + "pageIndex": 0, + "pageSize": 20, + "pageSizeOptions": Array [ + 10, + 20, + 50, + ], + "totalItemCount": 2, + } + } + responsive={true} + selection={ + Object { + "onSelectionChange": [Function], + } + } + sorting={Object {}} + /> + + +
`; 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 - - . -

-
- -
- - - - - - + +

+ Dashboards +

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

+
+ +
+ + + + + + + + "pageIndex": 0, + "pageSize": 20, + "pageSizeOptions": Array [ + 10, + 20, + 50, + ], + "totalItemCount": 2, + } + } + responsive={true} + selection={ + Object { + "onSelectionChange": [Function], + } + } + sorting={Object {}} + /> + + +
`; -exports[`initialFilter 1`] = ` +exports[`renders empty page in before initial fetch to avoid flickering 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.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index 22bae4cb7634..f7b9265960e7 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -17,7 +17,7 @@ * under the License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import { toastNotifications } from 'ui/notify'; @@ -26,6 +26,8 @@ import { EuiFieldSearch, EuiBasicTable, EuiPage, + EuiPageBody, + EuiPageContent, EuiLink, EuiFlexGroup, EuiFlexItem, @@ -36,6 +38,7 @@ import { EuiCallOut, EuiText, EuiTextColor, + EuiEmptyPrompt, } from '@elastic/eui'; import { DashboardConstants, createDashboardEditUrl } from '../dashboard_constants'; @@ -52,6 +55,7 @@ export class DashboardListing extends React.Component { super(props); this.state = { + hasInitialFetchReturned: false, isFetchingItems: false, showDeleteModal: false, showLimitError: false, @@ -87,6 +91,7 @@ export class DashboardListing extends React.Component { // order than they were sent out. Only load results for the most recent search. if (filter === this.state.filter) { this.setState({ + hasInitialFetchReturned: true, isFetchingItems: false, dashboards: response.hits, totalDashboards: response.total, @@ -177,6 +182,14 @@ export class DashboardListing extends React.Component { return dashboardsCopy.slice(startIndex, lastIndex); } + hasNoDashboards() { + if (!this.state.isFetchingItems && this.state.dashboards.length === 0 && !this.state.filter) { + return true; + } + + return false; + } + renderConfirmDeleteModal() { return ( @@ -215,46 +228,57 @@ export class DashboardListing extends React.Component { } } - renderNoItemsMessage() { + renderNoResultsMessage() { if (this.state.isFetchingItems) { return ''; } - 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.`} - -

-
- ); - } + return 'No dashboards matched your search.'; + } + renderNoItemsMessage() { + + if (this.props.hideWriteControls) { return ( - - -

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

-
- - Create new dashboard - -
+ +

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

+
); } - return 'No dashboards matched your search.'; + return ( +
+ Create your first dashboard} + body={ + +

+ You can combine data views from any Kibana app into one dashboard and see everything in one place. +

+

+ New to Kibana? Install some sample data to take a test drive. +

+
+ } + actions={ + + Create new dashboard + + } + /> +
+ ); + } renderSearchBar() { @@ -356,6 +380,7 @@ export class DashboardListing extends React.Component { }; } const items = this.state.dashboards.length === 0 ? [] : this.getPageOfItems(); + return ( - +
{this.state.showDeleteModal && this.renderConfirmDeleteModal()}

- Dashboard + Dashboards

@@ -409,8 +441,31 @@ export class DashboardListing extends React.Component { {this.renderSearchBar()} - {this.renderTable()} + + {this.renderTable()} +
+ ); + } + + renderPageContent() { + if (!this.state.hasInitialFetchReturned) { + return; + } + + return ( + + {this.renderListingOrEmptyState()} + + ); + } + + render() { + return ( + + + {this.renderPageContent()} + ); } 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 index afb4c6a12c86..e1a95b65982a 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js @@ -57,7 +57,7 @@ const find = (num) => { }); }; -test('renders table in loading state', () => { +test('renders empty page in before initial fetch to avoid flickering', () => { const component = shallow( {}} @@ -67,18 +67,24 @@ test('renders table in loading state', () => { expect(component).toMatchSnapshot(); }); -test('initialFilter', () => { - const component = shallow( {}} - listingLimit={1000} - hideWriteControls={false} - initialFilter="my dashboard" - />); - expect(component).toMatchSnapshot(); -}); - describe('after fetch', () => { + test('initialFilter', async () => { + const component = shallow( {}} + listingLimit={1000} + hideWriteControls={false} + initialFilter="my dashboard" + />); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + test('renders table rows', async () => { const component = shallow( { + const isNavVisible = await testSubjects.exists('top-nav'); + const isLandingPageVisible = await testSubjects.exists('dashboardLandingPage'); + if (!isNavVisible && !isLandingPageVisible) { + throw new Error('Dashboard application not loaded yet'); + } + }); await this.awaitGlobalLoadingIndicatorHidden(); }