diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html index 3cf8932958b6d8..b07e92fccfb4ca 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html @@ -4,13 +4,17 @@ > - - - -

{{screenTitle}}

diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx index c0a0693431295b..389e00efd92047 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx @@ -76,6 +76,12 @@ export interface DashboardAppScope extends ng.IScope { onSavedQueryUpdated: (savedQuery: SavedQuery) => void; onClearSavedQuery: () => void; topNavMenu: any; + showTopNav: () => boolean; + showTopNavMenu: () => boolean; + showSearchBar: () => boolean; + showQueryBar: () => boolean; + showQueryInput: () => boolean; + showDatePicker: () => boolean; showFilterBar: () => boolean; showAddPanel: any; showSaveQuery: boolean; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 075516d52bab63..2b32d8d14e0f85 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -26,6 +26,8 @@ import angular from 'angular'; import { Subscription } from 'rxjs'; import { map } from 'rxjs/operators'; import { History } from 'history'; +import { parse } from 'url'; + import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; @@ -713,8 +715,44 @@ export class DashboardAppController { }); } + const hasUrlParam = (param: string): boolean => + param in parse(location.hash.slice(1), true).query; + + const displayIfUrlParam = (param: string): boolean => + hasUrlParam(param) && !dashboardStateManager.getFullScreenMode(); + + const topNavUpdate = (): void => { + $scope.topNavMenu = $scope.showTopNavMenu() + ? getTopNavConfig( + dashboardStateManager.getViewMode(), + navActions, + dashboardConfig.getHideWriteControls() + ) + : null; + }; + + $scope.$watch( + (): boolean => $scope.showTopNavMenu(), + (): void => topNavUpdate() + ); + + $scope.showTopNav = () => $scope.isVisible || $scope.showSearchBar(); + + $scope.showTopNavMenu = () => $scope.isVisible || displayIfUrlParam('show-top-nav-menu'); + + $scope.showSearchBar = () => + $scope.isVisible || $scope.showQueryBar() || $scope.showFilterBar(); + + $scope.showQueryBar = () => + $scope.isVisible || $scope.showQueryInput() || $scope.showDatePicker(); + + $scope.showQueryInput = () => $scope.isVisible || displayIfUrlParam('show-query-input'); + + $scope.showDatePicker = () => $scope.isVisible || displayIfUrlParam('show-date-picker'); + $scope.showFilterBar = () => - $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode(); + ($scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode()) && + !hasUrlParam('hide-filter-bar'); $scope.showAddPanel = () => { dashboardStateManager.setFullScreenMode(false); @@ -888,14 +926,8 @@ export class DashboardAppController { }); }); - dashboardStateManager.registerChangeListener(() => { - // view mode could have changed, so trigger top nav update - $scope.topNavMenu = getTopNavConfig( - dashboardStateManager.getViewMode(), - navActions, - dashboardConfig.getHideWriteControls() - ); - }); + // view mode could have changed, so trigger top nav update + dashboardStateManager.registerChangeListener(topNavUpdate); $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js index 12c3ca2acc3cd8..77b5faa7828fa1 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js @@ -99,12 +99,13 @@ export const createTopNavHelper = ({ TopNavMenu }) => reactDirective => { // All modifiers default to true. // Set to false to hide subcomponents. + 'showTopNavMenu', 'showSearchBar', - 'showFilterBar', 'showQueryBar', 'showQueryInput', - 'showDatePicker', 'showSaveQuery', + 'showDatePicker', + 'showFilterBar', 'appName', 'screenTitle', diff --git a/src/plugins/dashboard_embeddable_container/public/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx index 44c9dbf2dcc4b6..0a5ec33cb82f9a 100644 --- a/src/plugins/dashboard_embeddable_container/public/plugin.tsx +++ b/src/plugins/dashboard_embeddable_container/public/plugin.tsx @@ -70,6 +70,7 @@ export class DashboardEmbeddableContainerPublicPlugin }; const ExitFullScreenButton: React.FC = props => { + // TODO: Only call useHideChrome if chrome is visible useHideChrome(); return ; }; diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss index 1c47c28097454e..731c9f4d7f18d8 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss @@ -3,6 +3,10 @@ padding: 0px $euiSizeS $euiSizeS $euiSizeS; } +.globalQueryBar:first-child { + padding-top: $euiSizeS; +} + .globalQueryBar:not(:empty) { padding-bottom: $euiSizeS; } diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index ad9c8401389fac..2a28e8a5c42381 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -348,7 +348,7 @@ function QueryBarTopRowUI(props: Props) { className={classes} responsive={!!props.showDatePicker} gutterSize="s" - justifyContent="flexEnd" + justifyContent="flexStart" > {renderQueryInput()} {renderSharingMetaFields()} diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 8e0e8b3031132b..4009977ff89a4b 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -29,6 +29,7 @@ const dataShim = { }; describe('TopNavMenu', () => { + const WRAPPER_SELECTOR = '.kbnTopNavMenu__wrapper'; const TOP_NAV_ITEM_SELECTOR = 'TopNavMenuItem'; const SEARCH_BAR_SELECTOR = 'SearchBar'; const menuItems: TopNavMenuData[] = [ @@ -55,6 +56,13 @@ describe('TopNavMenu', () => { expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); }); + it('Should not render the wrapper when showTopNavMenu and showSearchBar are both false', () => { + const component = shallowWithIntl( + + ); + expect(component.find(WRAPPER_SELECTOR).length).toBe(0); + }); + it('Should render 1 menu item', () => { const component = shallowWithIntl(); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(1); @@ -67,6 +75,15 @@ describe('TopNavMenu', () => { expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); }); + it('Should not render menu items when showTopNavMenu is false', () => { + const component = shallowWithIntl( + + ); + expect(component.find(WRAPPER_SELECTOR).length).toBe(0); + expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); + }); + it('Should render search bar', () => { const component = shallowWithIntl( diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index cf39c82eff3ce5..ba55fa00e87eb5 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React from 'react'; +import React, { ReactElement } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n/react'; @@ -28,6 +28,7 @@ import { StatefulSearchBarProps, DataPublicPluginStart } from '../../../data/pub export type TopNavMenuProps = StatefulSearchBarProps & { config?: TopNavMenuData[]; + showTopNavMenu?: boolean; showSearchBar?: boolean; data?: DataPublicPluginStart; }; @@ -41,8 +42,13 @@ export type TopNavMenuProps = StatefulSearchBarProps & { * **/ -export function TopNavMenu(props: TopNavMenuProps) { - const { config, showSearchBar, ...searchBarProps } = props; +export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { + const { config, showTopNavMenu, showSearchBar, ...searchBarProps } = props; + + if (!showTopNavMenu && (!showSearchBar || !props.data)) { + return null; + } + function renderItems() { if (!config) return; return config.map((menuItem: TopNavMenuData, i: number) => { @@ -54,6 +60,21 @@ export function TopNavMenu(props: TopNavMenuProps) { }); } + function renderMenu() { + if (!showTopNavMenu) return; + return ( + + {renderItems()} + + ); + } + function renderSearchBar() { // Validate presense of all required fields if (!showSearchBar || !props.data) return; @@ -64,15 +85,7 @@ export function TopNavMenu(props: TopNavMenuProps) { function renderLayout() { return ( - - {renderItems()} - + {renderMenu()} {renderSearchBar()} ); @@ -82,6 +95,7 @@ export function TopNavMenu(props: TopNavMenuProps) { } TopNavMenu.defaultProps = { + showTopNavMenu: true, showSearchBar: false, showQueryBar: true, showQueryInput: true, diff --git a/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap index c10ca551308809..0aa3dc61b62aa8 100644 --- a/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap +++ b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap @@ -435,3 +435,487 @@ exports[`should hide short url section when allowShortUrl is false 1`] = ` `; + +exports[`should not show embedded option switches when permalink 1`] = ` + + + + } + labelType="label" + > + + + + + + + } + position="bottom" + /> + + , + }, + Object { + "data-test-subj": "exportAsSavedObject", + "disabled": false, + "id": "savedObject", + "label": + + + + + + } + position="bottom" + /> + + , + }, + ] + } + /> + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + + +`; + +exports[`should show embedded option switches when embedded link 1`] = ` + + + + } + labelType="label" + > + + + + + + + } + position="bottom" + /> + + , + }, + Object { + "data-test-subj": "exportAsSavedObject", + "disabled": false, + "id": "savedObject", + "label": + + + + + + } + position="bottom" + /> + + , + }, + ] + } + /> + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + " + > + + + + +`; diff --git a/src/plugins/share/public/components/url_panel_content.test.tsx b/src/plugins/share/public/components/url_panel_content.test.tsx index 9db8d1ccf2efa2..4c5cd62d62ec6b 100644 --- a/src/plugins/share/public/components/url_panel_content.test.tsx +++ b/src/plugins/share/public/components/url_panel_content.test.tsx @@ -24,6 +24,10 @@ import { shallow } from 'enzyme'; import { UrlPanelContent } from './url_panel_content'; +const TOP_NAV_MENU_SWITCH_SELECTOR = '[data-test-subj="topNavMenuSwitch"]'; +const QUERY_INPUT_SWITCH_SELECTOR = '[data-test-subj="queryInputSwitch"]'; +const DATE_PICKER_SWITCH_SELECTOR = '[data-test-subj="datePickerSwitch"]'; +const FILTER_BAR_SWITCH_SELECTOR = '[data-test-subj="filterBarSwitch"]'; const defaultProps = { allowShortUrl: true, objectType: 'dashboard', @@ -47,3 +51,21 @@ test('should hide short url section when allowShortUrl is false', () => { ); expect(component).toMatchSnapshot(); }); + +test('should show embedded option switches when embedded link', () => { + const component = shallow(); + expect(component.find(TOP_NAV_MENU_SWITCH_SELECTOR).length).toBe(1); + expect(component.find(QUERY_INPUT_SWITCH_SELECTOR).length).toBe(1); + expect(component.find(DATE_PICKER_SWITCH_SELECTOR).length).toBe(1); + expect(component.find(FILTER_BAR_SWITCH_SELECTOR).length).toBe(1); + expect(component).toMatchSnapshot(); +}); + +test('should not show embedded option switches when permalink', () => { + const component = shallow(); + expect(component.find(TOP_NAV_MENU_SWITCH_SELECTOR).length).toBe(0); + expect(component.find(QUERY_INPUT_SWITCH_SELECTOR).length).toBe(0); + expect(component.find(DATE_PICKER_SWITCH_SELECTOR).length).toBe(0); + expect(component.find(FILTER_BAR_SWITCH_SELECTOR).length).toBe(0); + expect(component).toMatchSnapshot(); +}); diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index d0d4ce55dc1ac2..96a23fdfd1cc18 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { Component } from 'react'; +import React, { Component, ReactElement } from 'react'; import { EuiButton, @@ -60,11 +60,20 @@ enum ExportUrlAsType { interface State { exportUrlAs: ExportUrlAsType; useShortUrl: boolean; + showTopNavMenu: boolean; + showQueryInput: boolean; + showDatePicker: boolean; + showFilterBar: boolean; isCreatingShortUrl: boolean; url?: string; shortUrlErrorMsg?: string; } +type UrlParams = Pick< + State, + 'showTopNavMenu' | 'showQueryInput' | 'showDatePicker' | 'showFilterBar' +>; + export class UrlPanelContent extends Component { private mounted?: boolean; private shortUrlCache?: string; @@ -77,6 +86,10 @@ export class UrlPanelContent extends Component { this.state = { exportUrlAs: ExportUrlAsType.EXPORT_URL_AS_SNAPSHOT, useShortUrl: false, + showTopNavMenu: false, + showQueryInput: false, + showDatePicker: false, + showFilterBar: true, isCreatingShortUrl: false, url: '', }; @@ -102,6 +115,10 @@ export class UrlPanelContent extends Component { {this.renderExportAsRadioGroup()} {this.renderShortUrlSwitch()} + {this.renderTopNavMenuSwitch()} + {this.renderQueryInputSwitch()} + {this.renderDatePickerSwitch()} + {this.renderFilterBarSwitch()} @@ -186,13 +203,25 @@ export class UrlPanelContent extends Component { return this.props.shareableUrl || window.location.href; }; - private makeUrlEmbeddable = (url: string) => { - const embedQueryParam = '?embed=true'; + private getEmbedQueryParams = (): string => { + return [ + ['&show-top-nav-menu=true', this.state.showTopNavMenu], + ['&show-query-input=true', this.state.showQueryInput], + ['&show-date-picker=true', this.state.showDatePicker], + ['&hide-filter-bar=true', !this.state.showFilterBar], // Inverted to keep default behaviour for old links + ].reduce( + (accumulator, [queryParam, include]) => (include ? accumulator + queryParam : accumulator), + '?embed=true' + ); + }; + + private makeUrlEmbeddable = (url: string): string => { const urlHasQueryString = url.indexOf('?') !== -1; + const embedQueryParams = this.getEmbedQueryParams(); if (urlHasQueryString) { - return url.replace('?', `${embedQueryParam}&`); + return url.replace('?', `${embedQueryParams}&`); } - return `${url}${embedQueryParam}`; + return `${url}${embedQueryParams}`; }; private makeIframeTag = (url?: string) => { @@ -279,6 +308,11 @@ export class UrlPanelContent extends Component { } }; + private handleUrlParamChange = (param: keyof UrlParams) => (evt: EuiSwitchEvent): void => { + const stateUpdate: Partial = { [param]: evt.target.checked }; + this.setState(stateUpdate as State, this.setUrl); + }; + private renderExportUrlAsOptions = () => { return [ { @@ -391,4 +425,120 @@ export class UrlPanelContent extends Component { ); }; + + private renderTopNavMenuSwitch = (): ReactElement | void => { + if (!this.props.isEmbedded) { + return; + } + const switchLabel = ( + + ); + const switchComponent = ( + + ); + const tipContent = ( + + ); + + return ( + + {this.renderWithIconTip(switchComponent, tipContent)} + + ); + }; + + private renderQueryInputSwitch = (): ReactElement | void => { + if (!this.props.isEmbedded) { + return; + } + const switchLabel = ( + + ); + const switchComponent = ( + + ); + const tipContent = ( + + ); + + return ( + + {this.renderWithIconTip(switchComponent, tipContent)} + + ); + }; + + private renderDatePickerSwitch = (): ReactElement | void => { + if (!this.props.isEmbedded) { + return; + } + const switchLabel = ( + + ); + const switchComponent = ( + + ); + const tipContent = ( + + ); + + return ( + + {this.renderWithIconTip(switchComponent, tipContent)} + + ); + }; + + private renderFilterBarSwitch = (): ReactElement | void => { + if (!this.props.isEmbedded) { + return; + } + const switchLabel = ( + + ); + const switchComponent = ( + + ); + const tipContent = ( + + ); + + return ( + + {this.renderWithIconTip(switchComponent, tipContent)} + + ); + }; }