diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index 07e4173d5323f..8410040a0100d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -193,78 +193,160 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` textComponent={Symbol(react.fragment)} > - - - - - -

- This dashboard is empty. Let’s fill it up! -

-

- - Click the - - - - button in the menu bar above to add a visualization to the dashboard. - -

-

- - visit the Visualize app - , + "maxWidth": "36em", } } > - If you haven't set up any visualizations yet, - - visit the Visualize app - - to create your first visualization - -

+
+ + +
+ + + + + + +
+ + +
+

+ This dashboard is empty. Let’s fill it up! +

+
+
+ +
+ + +
+

+ Click the + + + + button in the menu bar above to add a visualization to the dashboard. +

+
+
+ +
+ + +
+

+ + visit the Visualize app + , + } + } + > + If you haven't set up any visualizations yet, + + visit the Visualize app + + to create your first visualization + +

+
+
+
+ + +
+ + +
@@ -464,51 +546,119 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] textComponent={Symbol(react.fragment)} > - - - - - -

- This dashboard is empty. Let’s fill it up! -

-

- - Click the - - - - button in the menu bar above to start working on your new dashboard. - -

+ + +
+ + + + + + +
+ + +
+

+ This dashboard is empty. Let’s fill it up! +

+
+
+ +
+ + +
+

+ Click the + + + + button in the menu bar above to start working on your new dashboard. +

+
+
+
+ + + + +
+ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index a4604d17ddecd..69bdcf59bb227 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -18,7 +18,9 @@ */ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardEmptyScreen, Props } from '../dashboard_empty_screen'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; describe('DashboardEmptyScreen', () => { const defaultProps = { @@ -26,7 +28,7 @@ describe('DashboardEmptyScreen', () => { onLinkClick: jest.fn(), }; - function mountComponent(props?: Props) { + function mountComponent(props?: DashboardEmptyScreenProps) { const compProps = props || defaultProps; const comp = mountWithIntl(); return comp; @@ -35,14 +37,14 @@ describe('DashboardEmptyScreen', () => { test('renders correctly with visualize paragraph', () => { const component = mountComponent(); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(1); }); test('renders correctly without visualize paragraph', () => { const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } }); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(0); }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index 14c35759d70a9..d9eadf6c0e37d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -6,9 +6,5 @@ .dshStartScreen { text-align: center; - padding: $euiSize; - - > * { - max-width: 36em !important; - } + padding: $euiSizeS; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index b58325a77e61e..ef1bcab589c4a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -131,7 +131,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/dashboard/State', 'app/dashboard/ConfirmModal', 'app/dashboard/icon', - 'app/dashboard/emptyScreen', ]); return dashboardAngularModule; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index c7fd8600b73bb..3cf8932958b6d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -48,24 +48,6 @@ > -
-
-

-
- -
- -
- -
- -
-
-

{{screenTitle}}

diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 186a2b525d1ec..fd49b26e0d948 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -21,9 +21,10 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; -import { uniq } from 'lodash'; +import { uniq, noop } from 'lodash'; import { Subscription } from 'rxjs'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { subscribeWithScope, @@ -40,8 +41,6 @@ import { import { FilterStateManager, IndexPattern } from '../../../data/public'; import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; -import './dashboard_empty_screen_directive'; - import { DashboardContainer, DASHBOARD_CONTAINER_TYPE, @@ -146,6 +145,16 @@ export class DashboardAppController { } $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; + $scope.getShouldShowEditHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsEditMode() && + !dashboardConfig.getHideWriteControls(); + + $scope.getShouldShowViewHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsViewMode() && + !dashboardConfig.getHideWriteControls(); + const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { return; @@ -174,6 +183,17 @@ export class DashboardAppController { } }; + const getEmptyScreenProps = (shouldShowEditHelp: boolean): DashboardEmptyScreenProps => { + const emptyScreenProps: DashboardEmptyScreenProps = { + onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode, + showLinkToVisualize: shouldShowEditHelp, + }; + if (shouldShowEditHelp) { + emptyScreenProps.onVisualizeClick = noop; + } + return emptyScreenProps; + }; + const getDashboardInput = (): DashboardContainerInput => { const embeddablesMap: { [key: string]: DashboardPanelState; @@ -185,6 +205,8 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { expandedPanelId = dashboardContainer.getInput().expandedPanelId; } + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); return { id: dashboardStateManager.savedDashboard.id || '', filters: queryFilter.getFilters(), @@ -197,6 +219,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), + isEmptyState: shouldShowEditHelp || shouldShowViewHelp, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, title: dashboardStateManager.getTitle(), @@ -237,6 +260,15 @@ export class DashboardAppController { if (!isErrorEmbeddable(container)) { dashboardContainer = container; + dashboardContainer.renderEmpty = () => { + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); + const isEmptyState = shouldShowEditHelp || shouldShowViewHelp; + return isEmptyState ? ( + + ) : null; + }; + updateIndexPatterns(dashboardContainer); outputSubscription = dashboardContainer.getOutput$().subscribe(() => { @@ -337,15 +369,6 @@ export class DashboardAppController { updateBreadcrumbs(); dashboardStateManager.registerChangeListener(updateBreadcrumbs); - $scope.getShouldShowEditHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsEditMode() && - !dashboardConfig.getHideWriteControls(); - $scope.getShouldShowViewHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsViewMode() && - !dashboardConfig.getHideWriteControls(); - const getChangesFromAppStateForContainerState = () => { const appStateDashboardInput = getDashboardInput(); if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) { @@ -736,6 +759,8 @@ export class DashboardAppController { } }; + navActions[TopNavIds.VISUALIZE] = async () => {}; + navActions[TopNavIds.OPTIONS] = anchorElement => { showOptionsPopover({ anchorElement, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx index d5a4e6e6a325d..234228ba4166a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx @@ -18,29 +18,43 @@ */ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { EuiIcon, EuiLink } from '@elastic/eui'; +import { + EuiIcon, + EuiLink, + EuiSpacer, + EuiPageContent, + EuiPageBody, + EuiPage, + EuiText, +} from '@elastic/eui'; import * as constants from './dashboard_empty_screen_constants'; -export interface Props { +export interface DashboardEmptyScreenProps { showLinkToVisualize: boolean; onLinkClick: () => void; + onVisualizeClick?: () => void; } -export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props) { +export function DashboardEmptyScreen({ + showLinkToVisualize, + onLinkClick, +}: DashboardEmptyScreenProps) { const linkToVisualizeParagraph = ( -

- - {constants.visualizeAppLinkTest} - - ), - }} - /> -

+ +

+ + {constants.visualizeAppLinkTest} + + ), + }} + /> +

+
); const paragraph = ( description1: string, @@ -50,15 +64,15 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props dataTestSubj?: string ) => { return ( -

- + +

{description1} {linkText} {description2} - -

+

+ ); }; const addVisualizationParagraph = ( @@ -70,6 +84,7 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props constants.addVisualizationLinkAriaLabel, 'emptyDashboardAddPanelButton' )} + {linkToVisualizeParagraph} ); @@ -81,11 +96,19 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props ); return ( - - -

{constants.fillDashboardTitle}

- {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} -
+ + + + + + +

{constants.fillDashboardTitle}

+
+ + {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} +
+
+
); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts deleted file mode 100644 index 5ebefd817ca4a..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -// @ts-ignore -import angular from 'angular'; -import { DashboardEmptyScreen } from './dashboard_empty_screen'; - -angular - .module('app/dashboard/emptyScreen', ['react']) - .directive('dashboardEmptyScreen', function(reactDirective: any) { - return reactDirective(DashboardEmptyScreen, [ - ['showLinkToVisualize', { watchDepth: 'value' }], - ['onLinkClick', { watchDepth: 'reference' }], - ]); - }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts index 9df86f2ca3cce..c67d6891c18e7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts @@ -26,4 +26,5 @@ export const TopNavIds = { ENTER_EDIT_MODE: 'enterEditMode', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', + VISUALIZE: 'visualize', }; diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx index 684aa93779bc1..021a1a9d1e64a 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx @@ -90,6 +90,8 @@ export type DashboardReactContext = KibanaReactContext { public readonly type = DASHBOARD_CONTAINER_TYPE; + public renderEmpty?: undefined | (() => React.ReactNode); + constructor( initialInput: DashboardContainerInput, private readonly options: DashboardContainerOptions, @@ -124,7 +126,7 @@ export class DashboardContainer extends Container - + , dom diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss index 24b813ec58964..0bd356522c7fa 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss @@ -34,7 +34,7 @@ .dshLayout-isMaximizedPanel { height: 100% !important; /* 1. */ width: 100%; - position: absolute; + position: absolute !important; } /** diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx index a2f7b8dc28fb0..e3d9b8552f060 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx @@ -121,6 +121,24 @@ test('renders DashboardViewport with no visualizations', () => { component.unmount(); }); +test('renders DashboardEmptyScreen', () => { + const renderEmptyScreen = jest.fn(); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true }); + const component = mount( + + + + + + ); + const dashboardEmptyScreenDiv = component.find('.dshDashboardEmptyScreen'); + expect(dashboardEmptyScreenDiv.length).toBe(1); + expect(renderEmptyScreen).toHaveBeenCalled(); + + component.unmount(); +}); + test('renders exit full screen button when in full screen mode', async () => { const { props, options } = getProps(); props.container.updateInput({ isFullScreenMode: true }); @@ -153,6 +171,39 @@ test('renders exit full screen button when in full screen mode', async () => { component.unmount(); }); +test('renders exit full screen button when in full screen mode and empty screen', async () => { + const renderEmptyScreen = jest.fn(); + renderEmptyScreen.mockReturnValue(React.createElement('div')); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true, isFullScreenMode: true }); + const component = mount( + + + + + + ); + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).toBe('ExitFullScreenButton'); + + props.container.updateInput({ isFullScreenMode: false }); + component.update(); + await nextTick(); + + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).not.toBe('ExitFullScreenButton'); + + component.unmount(); +}); + test('DashboardViewport unmount unsubscribes', async done => { const { props, options } = getProps(); const component = mount( diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx index 13407e5e33725..e7fd379898dd1 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx @@ -26,6 +26,7 @@ import { context } from '../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; + renderEmpty?: () => React.ReactNode; } interface State { @@ -34,6 +35,7 @@ interface State { title: string; description?: string; panels: { [key: string]: PanelState }; + isEmptyState?: boolean; } export class DashboardViewport extends React.Component { @@ -44,26 +46,40 @@ export class DashboardViewport extends React.Component { - const { isFullScreenMode, useMargins, title, description } = this.props.container.getInput(); + const { + isFullScreenMode, + useMargins, + title, + description, + isEmptyState, + } = this.props.container.getInput(); if (this.mounted) { this.setState({ isFullScreenMode, description, useMargins, title, + isEmptyState, }); } }); @@ -82,19 +98,33 @@ export class DashboardViewport extends React.Component + {isFullScreenMode && ( + + )} + {renderEmpty && renderEmpty()} +
+ ); + } + + private renderContainerScreen() { const { container } = this.props; + const { isFullScreenMode, panels, title, description, useMargins } = this.state; return (
- {this.state.isFullScreenMode && ( + {isFullScreenMode && ( @@ -103,4 +133,13 @@ export class DashboardViewport extends React.Component ); } + + public render() { + return ( + + {this.state.isEmptyState ? this.renderEmptyScreen() : null} + {this.renderContainerScreen()} + + ); + } } diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index bce16747ed48e..71e7cca3552bb 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -240,6 +240,7 @@ export abstract class Container< ...this.input.panels, [panelState.explicitInput.id]: panelState, }, + isEmptyState: false, } as Partial); return await this.untilEmbeddableLoaded(panelState.explicitInput.id); diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 33cb146a056cb..0197582778940 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -28,7 +28,7 @@ export interface EmbeddableInput { id: string; lastReloadRequestTime?: number; hidePanelTitles?: boolean; - + isEmptyState?: boolean; /** * List of action IDs that this embeddable should not render. */ diff --git a/test/functional/apps/dashboard/full_screen_mode.js b/test/functional/apps/dashboard/full_screen_mode.js index e18fd47b39b16..bf549ec21a6d3 100644 --- a/test/functional/apps/dashboard/full_screen_mode.js +++ b/test/functional/apps/dashboard/full_screen_mode.js @@ -78,7 +78,6 @@ export default function ({ getService, getPageObjects }) { const logoButton = await PageObjects.dashboard.getExitFullScreenLogoButton(); await logoButton.moveMouseTo(); await PageObjects.dashboard.clickExitFullScreenTextButton(); - await retry.try(async () => { const isChromeVisible = await PageObjects.common.isChromeVisible(); expect(isChromeVisible).to.be(true); diff --git a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts index 4cfe581b7eac5..d1568a5ab96ce 100644 --- a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts +++ b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts @@ -17,6 +17,10 @@ export function doesInheritTimeRange(embeddable: Embeddable) { // Note: this logic might not work in a container nested world... the explicit input // may be on the root... or any of the interim parents. + // if it's a dashboard emptys screen, there will be no embeddable + if (!parent.getInput().panels[embeddable.id]) { + return false; + } // If there is no explicit input defined on the parent then this embeddable inherits the // time range from whatever the time range of the parent is. return parent.getInput().panels[embeddable.id].explicitInput.timeRange === undefined; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 98ffcf33febcb..1190bffb2956d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12741,4 +12741,4 @@ "xpack.licensing.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", "xpack.licensing.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです" } -} +} \ No newline at end of file