From 66ac67ec60ba81bc4071c0b91e85e637650d5bf7 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Tue, 25 Feb 2020 16:36:22 +0900 Subject: [PATCH 01/28] Add embed mode options in the Share UI --- src/core/public/chrome/chrome_service.tsx | 11 +- .../public/application/dashboard_app.tsx | 7 + .../application/dashboard_app_controller.tsx | 33 +- src/plugins/dashboard/public/plugin.tsx | 5 +- .../ui/filter_bar/_global_filter_group.scss | 4 + .../query_string_input/query_bar_top_row.tsx | 2 +- .../public/angular/kbn_top_nav.js | 5 +- .../public/top_nav_menu/top_nav_menu.test.tsx | 17 + .../public/top_nav_menu/top_nav_menu.tsx | 44 +- .../url_panel_content.test.tsx.snap | 484 ++++++++++++++++++ .../components/url_panel_content.test.tsx | 22 + .../public/components/url_panel_content.tsx | 160 +++++- 12 files changed, 763 insertions(+), 31 deletions(-) diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 67cd43f0647e43..dfbc91d73d3836 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -107,9 +107,8 @@ export class ChromeService { * reset the visibility whenever the next application is mounted * 3. Having "embed" in the query string */ - private initVisibility(application: StartDeps['application']) { + private initVisibility(application: StartDeps['application'], isEmbedded: boolean) { // Start off the chrome service hidden if "embed" is in the hash query string. - const isEmbedded = 'embed' in parse(location.hash.slice(1), true).query; this.isForceHidden$ = new BehaviorSubject(isEmbedded); const appHidden$ = merge( @@ -140,7 +139,9 @@ export class ChromeService { notifications, uiSettings, }: StartDeps): Promise { - this.initVisibility(application); + const isEmbedded = 'embed' in parse(location.hash.slice(1), true).query; + + this.initVisibility(application, isEmbedded); const appTitle$ = new BehaviorSubject('Kibana'); const brand$ = new BehaviorSubject({}); @@ -213,6 +214,8 @@ export class ChromeService { recentlyAccessed, docTitle, + isEmbedded, + getHeaderComponent: () => (
boolean; updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void; topNavMenu: any; + showTopNav: () => boolean; + showTopNavMenu: () => boolean; + showSearchBar: () => boolean; + showQueryBar: () => boolean; + showQueryInput: () => boolean; + showDatePicker: () => boolean; + showFilterBar: () => boolean; showAddPanel: any; showSaveQuery: boolean; kbnTopNav: any; diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index a59d1e8c546d4b..4303e1c325fabe 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -27,6 +27,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 { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; import { TimeRange } from 'src/plugins/data/public'; @@ -133,8 +135,20 @@ export class DashboardAppController { const filterManager = queryService.filterManager; const queryFilter = filterManager; const timefilter = queryService.timefilter.timefilter; + let showTopNavMenu = true; let showSearchBar = true; let showQueryBar = true; + let showQueryInput = true; + let showDatePicker = true; + + // url param rules should only apply when embedded (e.g. url?embed=true) + const shouldForceDisplay = (param: string): boolean => + chrome.isEmbedded && param in parse(location.hash.slice(1), true).query; + + const forceShowTopNavMenu = shouldForceDisplay('show-top-nav-menu'); + const forceShowQueryInput = shouldForceDisplay('show-query-input'); + const forceShowDatePicker = shouldForceDisplay('show-date-picker'); + const forceHideFilterBar = shouldForceDisplay('hide-filter-bar'); let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); @@ -252,7 +266,8 @@ export class DashboardAppController { }; const showFilterBar = () => - $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode(); + !forceHideFilterBar && + ($scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode()); const getEmptyScreenProps = ( shouldShowEditHelp: boolean, @@ -595,11 +610,14 @@ export class DashboardAppController { const screenTitle = dashboardStateManager.getTitle(); return { appName: 'dashboard', - config: $scope.isVisible ? $scope.topNavMenu : undefined, + config: showTopNavMenu ? $scope.topNavMenu : undefined, className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined, screenTitle, + showTopNavMenu, showSearchBar, showQueryBar, + showQueryInput, + showDatePicker, showFilterBar: showFilterBar(), indexPatterns: $scope.indexPatterns, showSaveQuery: $scope.showSaveQuery, @@ -798,7 +816,6 @@ export class DashboardAppController { } = {}; navActions[TopNavIds.FULL_SCREEN] = () => { dashboardStateManager.setFullScreenMode(true); - showQueryBar = false; updateNavBar(); }; navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW); @@ -954,9 +971,15 @@ export class DashboardAppController { const visibleSubscription = chrome.getIsVisible$().subscribe((isVisible) => { $scope.$evalAsync(() => { + const shouldShow = (forceShow: boolean) => (forceShow || isVisible) && !dashboardStateManager.getFullScreenMode(); + $scope.isVisible = isVisible; - showSearchBar = isVisible || showFilterBar(); - showQueryBar = !dashboardStateManager.getFullScreenMode() && isVisible; + showTopNavMenu = shouldShow(forceShowTopNavMenu); + showQueryInput = shouldShow(forceShowQueryInput); + showDatePicker = shouldShow(forceShowDatePicker); + showQueryBar = showQueryInput || showDatePicker; + showSearchBar = showQueryBar || showFilterBar(); + updateNavBar(); }); }); diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 0de3982039928b..af6b8c47dda6f2 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -175,6 +175,7 @@ export class DashboardPlugin }; const ExitFullScreenButton: React.FC = (props) => { + // TODO: Only call useHideChrome if chrome is visible useHideChrome(); return ; }; @@ -290,7 +291,7 @@ export class DashboardPlugin kibanaLegacy.forwardApp( DashboardConstants.DASHBOARD_ID, DashboardConstants.DASHBOARDS_ID, - (path) => { + path => { const [, id, tail] = /dashboard\/?(.*?)($|\?.*)/.exec(path) || []; if (!id && !tail) { // unrecognized sub url @@ -307,7 +308,7 @@ export class DashboardPlugin kibanaLegacy.forwardApp( DashboardConstants.DASHBOARDS_ID, DashboardConstants.DASHBOARDS_ID, - (path) => { + path => { const [, tail] = /(\?.*)/.exec(path) || []; // carry over query if it exists return `#/list${tail || ''}`; 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 f65bf97e391e22..78c007ddfb68a3 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 @@ -347,7 +347,7 @@ export function QueryBarTopRow(props: Props) { className={classes} responsive={!!props.showDatePicker} gutterSize="s" - justifyContent="flexEnd" + justifyContent="flexStart" > {renderQueryInput()} {renderSharingMetaFields()} diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index a0faf4a6a071c1..26801bb6e46540 100644 --- a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -93,12 +93,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/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 74cfd125c2e3a0..946136ae595b5f 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 d492c7feb61a7f..c71d0087517f9b 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'; @@ -28,7 +28,12 @@ import { StatefulSearchBarProps, DataPublicPluginStart } from '../../../data/pub export type TopNavMenuProps = StatefulSearchBarProps & { config?: TopNavMenuData[]; + showTopNavMenu?: boolean; showSearchBar?: boolean; + showQueryBar?: boolean; + showQueryInput?: boolean; + showDatePicker?: boolean; + showFilterBar?: boolean; data?: DataPublicPluginStart; className?: string; }; @@ -42,8 +47,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) => { @@ -59,6 +69,22 @@ export function TopNavMenu(props: TopNavMenuProps) { }); } + function renderMenu(className: string) { + if (!showTopNavMenu) return; + return ( + + {renderItems()} + + ); + } + function renderSearchBar() { // Validate presense of all required fields if (!showSearchBar || !props.data) return; @@ -70,16 +96,7 @@ export function TopNavMenu(props: TopNavMenuProps) { const className = classNames('kbnTopNavMenu', props.className); return ( - - {renderItems()} - + {renderMenu(className)} {renderSearchBar()} ); @@ -89,6 +106,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 8787e0c0273755..4c6418aa5bd3ac 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[`share url panel content should hide short url section when allowShortUr `; + +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 bd30dbf002df80..8ddae7300299f8 100644 --- a/src/plugins/share/public/components/url_panel_content.test.tsx +++ b/src/plugins/share/public/components/url_panel_content.test.tsx @@ -28,6 +28,10 @@ import { ExportUrlAsType, UrlPanelContent } from './url_panel_content'; import { act } from 'react-dom/test-utils'; import { shortenUrl } from '../lib/url_shortener'; +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', @@ -202,3 +206,21 @@ describe('share url panel content', () => { }); }); }); + +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 2ece2052c4b958..2eca197a7f7b3d 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 @@ export 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()} @@ -195,13 +212,25 @@ export class UrlPanelContent extends Component { return url; }; - 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) => { @@ -287,6 +316,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 [ { @@ -397,4 +431,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)} + + ); + }; } From ab23f52b1201c7dee043eaf34dc1e59a120ac6e4 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 4 Mar 2020 16:10:23 +0900 Subject: [PATCH 02/28] Skip chrome visibility changes when embedded --- .../application/dashboard_app_controller.tsx | 1 + .../embeddable/dashboard_container.tsx | 2 ++ .../dashboard_container_factory.tsx | 1 + .../viewport/dashboard_viewport.tsx | 11 ++++++++-- .../get_sample_dashboard_input.ts | 1 + src/plugins/dashboard/public/plugin.tsx | 20 +++++++++++++------ .../exit_full_screen_button.tsx | 1 + .../public/app/dashboard_input.ts | 1 + 8 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 4303e1c325fabe..9438366c8966fb 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -314,6 +314,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), + isEmbedded: Boolean($routeParams.embed), isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 5d4cc851cf4554..53b43f4adfb274 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -57,6 +57,7 @@ export interface DashboardContainerInput extends ContainerInput { useMargins: boolean; title: string; description?: string; + isEmbedded: boolean; isFullScreenMode: boolean; panels: { [panelId: string]: DashboardPanelState; @@ -105,6 +106,7 @@ export class DashboardContainer extends Container { return { panels: {}, + isEmbedded: false, isFullScreenMode: false, useMargins: true, }; diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index 429837583b6481..d51fc1db9bdf5b 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -36,6 +36,7 @@ interface State { title: string; description?: string; panels: { [key: string]: PanelState }; + isEmbedded?: boolean; isEmptyState?: boolean; } @@ -52,6 +53,7 @@ export class DashboardViewport extends React.Component {isFullScreenMode && ( )} {renderEmpty && renderEmpty()} @@ -116,7 +122,7 @@ export class DashboardViewport extends React.Component )} diff --git a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts index 4ceac90672cb34..82fb253a4a9f6c 100644 --- a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts +++ b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts @@ -27,6 +27,7 @@ export function getSampleDashboardInput( id: '123', filters: [], useMargins: false, + isEmbedded: false, isFullScreenMode: false, title: 'My Dashboard', query: { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index af6b8c47dda6f2..134fddd2fe1f17 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -167,16 +167,24 @@ export class DashboardPlugin const getStartServices = async () => { const [coreStart, deps] = await core.getStartServices(); - const useHideChrome = () => { + const useHideChrome = ( + { toggleChrome }: Pick = { toggleChrome: true } + ) => { React.useEffect(() => { - coreStart.chrome.setIsVisible(false); - return () => coreStart.chrome.setIsVisible(true); - }, []); + if (toggleChrome) { + coreStart.chrome.setIsVisible(false); + } + + return () => { + if (toggleChrome) { + coreStart.chrome.setIsVisible(true); + } + }; + }, [toggleChrome]); }; const ExitFullScreenButton: React.FC = (props) => { - // TODO: Only call useHideChrome if chrome is visible - useHideChrome(); + useHideChrome({ toggleChrome: props.toggleChrome }); return ; }; return { diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx index 2a359b7cca5d1f..d4116fe73978e1 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -24,6 +24,7 @@ import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; export interface ExitFullScreenButtonProps { onExitFullScreenMode: () => void; + toggleChrome?: boolean; } import './index.scss'; diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts index 6f4e1f052f5e09..87def9fc4c1213 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts @@ -92,6 +92,7 @@ export const dashboardInput: DashboardContainerInput = { }, }, }, + isEmbedded: false, isFullScreenMode: false, filters: [], useMargins: true, From 4699307e70fdc77a32bdfadcde24b497dd6c1bf2 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 4 Mar 2020 16:21:20 +0900 Subject: [PATCH 03/28] Update URL parameter checks --- .../dashboard/public/application/dashboard_app_controller.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 9438366c8966fb..d846793743c4f9 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -27,7 +27,6 @@ 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 { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; @@ -143,7 +142,7 @@ export class DashboardAppController { // url param rules should only apply when embedded (e.g. url?embed=true) const shouldForceDisplay = (param: string): boolean => - chrome.isEmbedded && param in parse(location.hash.slice(1), true).query; + chrome.isEmbedded && Boolean($routeParams[param]); const forceShowTopNavMenu = shouldForceDisplay('show-top-nav-menu'); const forceShowQueryInput = shouldForceDisplay('show-query-input'); From 335277b4679435be1bdb3aaa1a39c401e09e52ef Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 4 Mar 2020 19:12:41 +0900 Subject: [PATCH 04/28] Add prop for date picker content alignment --- src/plugins/dashboard/public/application/dashboard_app.tsx | 2 ++ .../dashboard/public/application/dashboard_app_controller.tsx | 2 ++ .../data/public/ui/query_string_input/query_bar_top_row.tsx | 4 +++- src/plugins/data/public/ui/search_bar/search_bar.tsx | 3 +++ src/plugins/kibana_legacy/public/angular/kbn_top_nav.js | 1 + 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index f21a45f9e4982e..ef1d68d9217c84 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -20,6 +20,7 @@ import moment from 'moment'; import { Subscription } from 'rxjs'; import { History } from 'history'; +import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; import { ViewMode } from 'src/plugins/embeddable/public'; import { IIndexPattern, TimeRange, Query, Filter, SavedQuery } from 'src/plugins/data/public'; @@ -61,6 +62,7 @@ export interface DashboardAppScope extends ng.IScope { showQueryInput: () => boolean; showDatePicker: () => boolean; showFilterBar: () => boolean; + justifyContent: FlexGroupJustifyContent; showAddPanel: any; showSaveQuery: boolean; kbnTopNav: any; diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index d846793743c4f9..d54cc96d79ee51 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -20,6 +20,7 @@ import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; +import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; import React from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; @@ -619,6 +620,7 @@ export class DashboardAppController { showQueryInput, showDatePicker, showFilterBar: showFilterBar(), + justifyContent: (chrome.isEmbedded ? 'flexStart' : 'flexEnd') as FlexGroupJustifyContent, indexPatterns: $scope.indexPatterns, showSaveQuery: $scope.showSaveQuery, query: $scope.model.query, 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 78c007ddfb68a3..26ba6691f22ccb 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 @@ -31,6 +31,7 @@ import { EuiFieldText, prettyDuration, } from '@elastic/eui'; +import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; // @ts-ignore import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -56,6 +57,7 @@ interface Props { showDatePicker?: boolean; dateRangeFrom?: string; dateRangeTo?: string; + justifyContent?: FlexGroupJustifyContent; isRefreshPaused?: boolean; refreshInterval?: number; showAutoRefreshOnly?: boolean; @@ -347,7 +349,7 @@ export function QueryBarTopRow(props: Props) { className={classes} responsive={!!props.showDatePicker} gutterSize="s" - justifyContent="flexStart" + justifyContent={props.justifyContent || 'flexEnd'} > {renderQueryInput()} {renderSharingMetaFields()} diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index a5ac2275591158..a3ad1aa0fca5cd 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -23,6 +23,7 @@ import classNames from 'classnames'; import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; +import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; import { withKibana, KibanaReactContextValue } from '../../../../kibana_react/public'; @@ -48,6 +49,7 @@ export interface SearchBarOwnProps { customSubmitButton?: React.ReactNode; screenTitle?: string; dataTestSubj?: string; + justifyContent?: FlexGroupJustifyContent; // Togglers showQueryBar?: boolean; showQueryInput?: boolean; @@ -402,6 +404,7 @@ class SearchBarUI extends Component { this.props.customSubmitButton ? this.props.customSubmitButton : undefined } dataTestSubj={this.props.dataTestSubj} + justifyContent={this.props.justifyContent} /> ); } diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index 26801bb6e46540..fcd31eb7e56ae4 100644 --- a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -110,6 +110,7 @@ export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { 'refreshInterval', 'disableAutoFocus', 'showAutoRefreshOnly', + 'justifyContent', // temporary flag to use the stateful components 'useDefaultBehaviors', From ffdc84dae1c32c2a4a4e2c1e61eea1d5b054a59b Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Sun, 15 Mar 2020 18:37:58 +0900 Subject: [PATCH 05/28] Use menu items for top nav menu display logic --- .../public/angular/kbn_top_nav.js | 1 - .../public/top_nav_menu/top_nav_menu.test.tsx | 27 ++++++++++--------- .../public/top_nav_menu/top_nav_menu.tsx | 10 +++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index fcd31eb7e56ae4..d1494b18bf1654 100644 --- a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -93,7 +93,6 @@ export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { // All modifiers default to true. // Set to false to hide subcomponents. - 'showTopNavMenu', 'showSearchBar', 'showQueryBar', 'showQueryInput', 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 946136ae595b5f..3c542c0738e8d3 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 @@ -52,44 +52,47 @@ describe('TopNavMenu', () => { it('Should render nothing when no config is provided', () => { 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 not render the wrapper when showTopNavMenu and showSearchBar are both false', () => { - const component = shallowWithIntl( - - ); + it('Should not render menu items when config is empty', () => { + 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 1 menu item', () => { const component = shallowWithIntl(); + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(1); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); }); it('Should render multiple menu items', () => { const component = shallowWithIntl(); + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(menuItems.length); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); }); - it('Should not render menu items when showTopNavMenu is false', () => { + it('Should render search bar', () => { const component = shallowWithIntl( - + ); - expect(component.find(WRAPPER_SELECTOR).length).toBe(0); + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); - expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); }); - it('Should render search bar', () => { + it('Should render menu items and search bar', () => { const component = shallowWithIntl( - + ); - - expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); + expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(menuItems.length); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); }); 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 c71d0087517f9b..964bb49f3968db 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 @@ -28,7 +28,6 @@ import { StatefulSearchBarProps, DataPublicPluginStart } from '../../../data/pub export type TopNavMenuProps = StatefulSearchBarProps & { config?: TopNavMenuData[]; - showTopNavMenu?: boolean; showSearchBar?: boolean; showQueryBar?: boolean; showQueryInput?: boolean; @@ -48,14 +47,14 @@ export type TopNavMenuProps = StatefulSearchBarProps & { **/ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { - const { config, showTopNavMenu, showSearchBar, ...searchBarProps } = props; + const { config, showSearchBar, ...searchBarProps } = props; - if (!showTopNavMenu && (!showSearchBar || !props.data)) { + if ((!config || config.length === 0) && (!showSearchBar || !props.data)) { return null; } function renderItems() { - if (!config) return; + if (!config || config.length === 0) return; return config.map((menuItem: TopNavMenuData, i: number) => { return ( Date: Tue, 17 Mar 2020 10:37:39 +0900 Subject: [PATCH 06/28] Use checkbox group for URL share params --- .../url_panel_content.test.tsx.snap | 234 +++++------------- .../components/url_panel_content.test.tsx | 19 +- .../public/components/url_panel_content.tsx | 185 ++++---------- 3 files changed, 119 insertions(+), 319 deletions(-) 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 4c6418aa5bd3ac..8e1f25a47b2ff2 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 @@ -436,7 +436,7 @@ exports[`share url panel content should hide short url section when allowShortUr `; -exports[`should not show embedded option switches when permalink 1`] = ` +exports[`should not show url param checkboxes when permalink 1`] = ` `; -exports[`should show embedded option switches when embedded link 1`] = ` +exports[`should show url param checkboxes when embedded link 1`] = ` - - - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - - - - - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - - - - - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - - - - - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - - + + , + } + } + onChange={[Function]} + options={ + Array [ + Object { + "id": "topMenu", + "label": , + }, + Object { + "id": "query", + "label": , + }, + Object { + "id": "timeFilter", + "label": , + }, + Object { + "id": "filterBar", + "label": , + }, + ] + } + /> 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 8ddae7300299f8..d13fde52d7a265 100644 --- a/src/plugins/share/public/components/url_panel_content.test.tsx +++ b/src/plugins/share/public/components/url_panel_content.test.tsx @@ -28,10 +28,7 @@ import { ExportUrlAsType, UrlPanelContent } from './url_panel_content'; import { act } from 'react-dom/test-utils'; import { shortenUrl } from '../lib/url_shortener'; -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 URL_PARAM_EXTENSIONS_SELECTOR = '[data-test-subj="urlParamExtensions"]'; const defaultProps = { allowShortUrl: true, objectType: 'dashboard', @@ -207,20 +204,14 @@ describe('share url panel content', () => { }); }); -test('should show embedded option switches when embedded link', () => { +test('should show url param checkboxes 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.find(URL_PARAM_EXTENSIONS_SELECTOR).length).toBe(1); expect(component).toMatchSnapshot(); }); -test('should not show embedded option switches when permalink', () => { +test('should not show url param checkboxes 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.find(URL_PARAM_EXTENSIONS_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 2eca197a7f7b3d..a2e16a6917c585 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -21,12 +21,15 @@ import React, { Component, ReactElement } from 'react'; import { EuiButton, + EuiCheckboxGroup, + EuiCheckboxGroupIdToSelectedMap, EuiCopy, EuiFlexGroup, EuiSpacer, EuiFlexItem, EuiForm, EuiFormRow, + EuiHorizontalRule, EuiIconTip, EuiLoadingSpinner, EuiRadioGroup, @@ -57,23 +60,22 @@ export enum ExportUrlAsType { EXPORT_URL_AS_SNAPSHOT = 'snapshot', } +interface UrlParams { + topNavMenu: boolean; + query: boolean; + datePicker: boolean; + filterBar: boolean; +} + interface State { exportUrlAs: ExportUrlAsType; useShortUrl: boolean; - showTopNavMenu: boolean; - showQueryInput: boolean; - showDatePicker: boolean; - showFilterBar: boolean; isCreatingShortUrl: boolean; url?: string; shortUrlErrorMsg?: string; + urlParamsSelectedMap: UrlParams; } -type UrlParams = Pick< - State, - 'showTopNavMenu' | 'showQueryInput' | 'showDatePicker' | 'showFilterBar' ->; - export class UrlPanelContent extends Component { private mounted?: boolean; private shortUrlCache?: string; @@ -86,12 +88,14 @@ 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: '', + urlParamsSelectedMap: { + topNavMenu: false, + query: false, + datePicker: false, + filterBar: true, + }, }; } @@ -113,12 +117,8 @@ export class UrlPanelContent extends Component { {this.renderExportAsRadioGroup()} - {this.renderShortUrlSwitch()} - {this.renderTopNavMenuSwitch()} - {this.renderQueryInputSwitch()} - {this.renderDatePickerSwitch()} - {this.renderFilterBarSwitch()} + {this.renderUrlParamExtensions()} @@ -214,10 +214,10 @@ export class UrlPanelContent extends Component { 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 + ['&show-top-nav-menu=true', this.state.urlParamsSelectedMap.topNavMenu], + ['&show-query-input=true', this.state.urlParamsSelectedMap.query], + ['&show-date-picker=true', this.state.urlParamsSelectedMap.datePicker], + ['&hide-filter-bar=true', !this.state.urlParamsSelectedMap.filterBar], // Inverted to keep default behaviour for old links ].reduce( (accumulator, [queryParam, include]) => (include ? accumulator + queryParam : accumulator), '?embed=true' @@ -316,8 +316,14 @@ export class UrlPanelContent extends Component { } }; - private handleUrlParamChange = (param: keyof UrlParams) => (evt: EuiSwitchEvent): void => { - const stateUpdate: Partial = { [param]: evt.target.checked }; + private handleUrlParamChange = (optionId: string): void => { + const param = optionId as keyof UrlParams; + const stateUpdate: Partial = { + urlParamsSelectedMap: { + ...this.state.urlParamsSelectedMap, + [param]: !this.state.urlParamsSelectedMap[param], + }, + }; this.setState(stateUpdate as State, this.setUrl); }; @@ -432,119 +438,36 @@ export class UrlPanelContent extends Component { ); }; - private renderTopNavMenuSwitch = (): ReactElement | void => { + private renderUrlParamExtensions = (): 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 = ( - - ); + const checkboxes = [ + ['topNavMenu', 'Top menu'], + ['query', 'Query'], + ['datePicker', 'Time filter'], + ['filterBar', 'Filter bar'], + ].map(([id, message]) => ({ + id, + label: , + })); 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)} - + + + , + }} + data-test-subj="urlParamExtensions" + /> + ); }; } From f5f1d8a691d70fac8b219cdb9d86a88316387120 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Tue, 17 Mar 2020 11:49:50 +0900 Subject: [PATCH 07/28] Rename URL params --- .../application/dashboard_app_controller.tsx | 4 ++-- .../public/components/url_panel_content.tsx | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index d54cc96d79ee51..ffeb799c3403aa 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -145,9 +145,9 @@ export class DashboardAppController { const shouldForceDisplay = (param: string): boolean => chrome.isEmbedded && Boolean($routeParams[param]); - const forceShowTopNavMenu = shouldForceDisplay('show-top-nav-menu'); + const forceShowTopNavMenu = shouldForceDisplay('show-top-menu'); const forceShowQueryInput = shouldForceDisplay('show-query-input'); - const forceShowDatePicker = shouldForceDisplay('show-date-picker'); + const forceShowDatePicker = shouldForceDisplay('show-time-filter'); const forceHideFilterBar = shouldForceDisplay('hide-filter-bar'); let lastReloadRequestTime = 0; diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index a2e16a6917c585..db14165f44fa2b 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -61,9 +61,9 @@ export enum ExportUrlAsType { } interface UrlParams { - topNavMenu: boolean; + topMenu: boolean; query: boolean; - datePicker: boolean; + timeFilter: boolean; filterBar: boolean; } @@ -91,9 +91,9 @@ export class UrlPanelContent extends Component { isCreatingShortUrl: false, url: '', urlParamsSelectedMap: { - topNavMenu: false, + topMenu: false, query: false, - datePicker: false, + timeFilter: false, filterBar: true, }, }; @@ -214,9 +214,9 @@ export class UrlPanelContent extends Component { private getEmbedQueryParams = (): string => { return [ - ['&show-top-nav-menu=true', this.state.urlParamsSelectedMap.topNavMenu], + ['&show-top-menu=true', this.state.urlParamsSelectedMap.topMenu], ['&show-query-input=true', this.state.urlParamsSelectedMap.query], - ['&show-date-picker=true', this.state.urlParamsSelectedMap.datePicker], + ['&show-time-filter=true', this.state.urlParamsSelectedMap.timeFilter], ['&hide-filter-bar=true', !this.state.urlParamsSelectedMap.filterBar], // Inverted to keep default behaviour for old links ].reduce( (accumulator, [queryParam, include]) => (include ? accumulator + queryParam : accumulator), @@ -444,9 +444,9 @@ export class UrlPanelContent extends Component { } const checkboxes = [ - ['topNavMenu', 'Top menu'], + ['topMenu', 'Top menu'], ['query', 'Query'], - ['datePicker', 'Time filter'], + ['timeFilter', 'Time filter'], ['filterBar', 'Filter bar'], ].map(([id, message]) => ({ id, From f4a5c4a932caf7fb58732e4e1b0e7597cb4622a1 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Tue, 17 Mar 2020 14:05:29 +0900 Subject: [PATCH 08/28] Rename isEmbedded to isEmbeddedExternally --- .../application/dashboard_app_controller.tsx | 2 +- .../embeddable/dashboard_container.tsx | 4 +-- .../dashboard_container_factory.tsx | 2 +- .../viewport/dashboard_viewport.tsx | 25 ++++++++++++------- .../get_sample_dashboard_input.ts | 2 +- .../public/app/dashboard_input.ts | 2 +- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index ffeb799c3403aa..c4f0f98b307b28 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -314,7 +314,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), - isEmbedded: Boolean($routeParams.embed), + isEmbeddedExternally: Boolean($routeParams.embed), isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 53b43f4adfb274..2121ca4c784bd5 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -57,7 +57,7 @@ export interface DashboardContainerInput extends ContainerInput { useMargins: boolean; title: string; description?: string; - isEmbedded: boolean; + isEmbeddedExternally: boolean; isFullScreenMode: boolean; panels: { [panelId: string]: DashboardPanelState; @@ -106,7 +106,7 @@ export class DashboardContainer extends Container { return { panels: {}, - isEmbedded: false, + isEmbeddedExternally: false, isFullScreenMode: false, useMargins: true, }; diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index d51fc1db9bdf5b..9ee50426b19bbf 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -36,7 +36,7 @@ interface State { title: string; description?: string; panels: { [key: string]: PanelState }; - isEmbedded?: boolean; + isEmbeddedExternally?: boolean; isEmptyState?: boolean; } @@ -53,7 +53,7 @@ export class DashboardViewport extends React.Component {isFullScreenMode && ( )} {renderEmpty && renderEmpty()} @@ -122,7 +122,14 @@ export class DashboardViewport extends React.Component )} diff --git a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts index 82fb253a4a9f6c..825a69155ba221 100644 --- a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts +++ b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts @@ -27,7 +27,7 @@ export function getSampleDashboardInput( id: '123', filters: [], useMargins: false, - isEmbedded: false, + isEmbeddedExternally: false, isFullScreenMode: false, title: 'My Dashboard', query: { diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts index 87def9fc4c1213..21b12e2134767a 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts @@ -92,7 +92,7 @@ export const dashboardInput: DashboardContainerInput = { }, }, }, - isEmbedded: false, + isEmbeddedExternally: false, isFullScreenMode: false, filters: [], useMargins: true, From e45723c97805585f39b9693bfff8b0a01acb8525 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Mon, 6 Apr 2020 19:47:37 +0100 Subject: [PATCH 09/28] Remove app specific logic from share plugin --- .../application/dashboard_app_controller.tsx | 102 +++++++- .../url_panel_content.test.tsx.snap | 220 +----------------- .../public/components/share_context_menu.tsx | 4 +- .../components/url_panel_content.test.tsx | 17 +- .../public/components/url_panel_content.tsx | 121 +++++----- .../public/services/share_menu_manager.tsx | 2 + src/plugins/share/public/types.ts | 11 + 7 files changed, 180 insertions(+), 297 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index c4f0f98b307b28..113ddec7d68b00 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -19,9 +19,10 @@ import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; +import { EUI_MODAL_CANCEL_BUTTON, EuiCheckboxGroup, EuiHorizontalRule } from '@elastic/eui'; +import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/form/checkbox/checkbox_group'; import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; -import React from 'react'; +import React, { useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; @@ -96,6 +97,25 @@ export interface DashboardAppControllerDependencies extends RenderDeps { navigation: NavigationStart; } +enum UrlParams { + SHOW_TOP_MENU = 'show-top-menu', + SHOW_QUERY_INPUT = 'show-query-input', + SHOW_TIME_FILTER = 'show-time-filter', + SHOW_FILTER_BAR = 'show-filter-bar', + HIDE_FILTER_BAR = 'hide-filter-bar', +} + +interface UrlParamsSelectedMap { + [UrlParams.SHOW_TOP_MENU]: boolean; + [UrlParams.SHOW_QUERY_INPUT]: boolean; + [UrlParams.SHOW_TIME_FILTER]: boolean; + [UrlParams.SHOW_FILTER_BAR]: boolean; +} + +interface UrlParamValues extends Omit { + [UrlParams.HIDE_FILTER_BAR]: boolean; +} + export class DashboardAppController { // Part of the exposed plugin API - do not remove without careful consideration. appStatus: { @@ -145,10 +165,10 @@ export class DashboardAppController { const shouldForceDisplay = (param: string): boolean => chrome.isEmbedded && Boolean($routeParams[param]); - const forceShowTopNavMenu = shouldForceDisplay('show-top-menu'); - const forceShowQueryInput = shouldForceDisplay('show-query-input'); - const forceShowDatePicker = shouldForceDisplay('show-time-filter'); - const forceHideFilterBar = shouldForceDisplay('hide-filter-bar'); + const forceShowTopNavMenu = shouldForceDisplay(UrlParams.SHOW_TOP_MENU); + const forceShowQueryInput = shouldForceDisplay(UrlParams.SHOW_QUERY_INPUT); + const forceShowDatePicker = shouldForceDisplay(UrlParams.SHOW_TIME_FILTER); + const forceHideFilterBar = shouldForceDisplay(UrlParams.HIDE_FILTER_BAR); let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); @@ -942,6 +962,70 @@ export class DashboardAppController { if (share) { // the share button is only availabale if "share" plugin contract enabled navActions[TopNavIds.SHARE] = (anchorElement) => { + const EmbedUrlParamExtension = ({ + setParamValue, + }: { + setParamValue: (paramUpdate: UrlParamValues) => void; + }): ReactElement => { + const [urlParamsSelectedMap, setUrlParamsSelectedMap] = useState({ + [UrlParams.SHOW_TOP_MENU]: false, + [UrlParams.SHOW_QUERY_INPUT]: false, + [UrlParams.SHOW_TIME_FILTER]: false, + [UrlParams.SHOW_FILTER_BAR]: true, + }); + + const checkboxes = [ + [UrlParams.SHOW_TOP_MENU, 'topMenu', 'Top menu'], + [UrlParams.SHOW_QUERY_INPUT, 'query', 'Query'], + [UrlParams.SHOW_TIME_FILTER, 'timeFilter', 'Time filter'], + [UrlParams.SHOW_FILTER_BAR, 'filterBar', 'Filter bar'], + ].map(([urlParam, translationId, defaultMessage]) => ({ + id: urlParam, + label: i18n.translate(`share.urlPanel.${translationId}`, { + defaultMessage, + }), + })); + + const handleChange = (param: string): void => { + const urlParamsSelectedMapUpdate = { + ...urlParamsSelectedMap, + [param]: !urlParamsSelectedMap[param as keyof UrlParamsSelectedMap], + }; + setUrlParamsSelectedMap(urlParamsSelectedMapUpdate); + + const urlParamValues = { + [UrlParams.SHOW_TOP_MENU]: urlParamsSelectedMap[UrlParams.SHOW_TOP_MENU], + [UrlParams.SHOW_QUERY_INPUT]: urlParamsSelectedMap[UrlParams.SHOW_QUERY_INPUT], + [UrlParams.SHOW_TIME_FILTER]: urlParamsSelectedMap[UrlParams.SHOW_TIME_FILTER], + [UrlParams.HIDE_FILTER_BAR]: !urlParamsSelectedMap[UrlParams.SHOW_FILTER_BAR], + [param === UrlParams.SHOW_FILTER_BAR ? UrlParams.HIDE_FILTER_BAR : param]: + param === UrlParams.SHOW_FILTER_BAR + ? urlParamsSelectedMap[UrlParams.SHOW_FILTER_BAR] + : !urlParamsSelectedMap[param as keyof UrlParamsSelectedMap], + }; + setParamValue(urlParamValues); + }; + + return ( + + + + + ); + }; + share.toggleShareContextMenu({ anchorElement, allowEmbed: true, @@ -954,6 +1038,12 @@ export class DashboardAppController { title: dash.title, }, isDirty: dashboardStateManager.getIsDirty(), + embedUrlParamExtensions: [ + { + paramName: 'embed', + component: EmbedUrlParamExtension, + }, + ], }); }; } 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 8e1f25a47b2ff2..45ed2c4618bade 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 @@ -436,7 +436,7 @@ exports[`share url panel content should hide short url section when allowShortUr `; -exports[`should not show url param checkboxes when permalink 1`] = ` +exports[`should show url param extensions 1`] = ` - - - - - - -`; - -exports[`should show url param checkboxes 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]} - options={ - Array [ - Object { - "id": "topMenu", - "label": , - }, - Object { - "id": "query", - "label": , - }, - Object { - "id": "timeFilter", - "label": , - }, - Object { - "id": "filterBar", - "label": , - }, - ] - } + " + textToCopy="http://localhost/" > diff --git a/src/plugins/share/public/components/share_context_menu.tsx b/src/plugins/share/public/components/share_context_menu.tsx index c12e9dabd1938d..26426853ddabe3 100644 --- a/src/plugins/share/public/components/share_context_menu.tsx +++ b/src/plugins/share/public/components/share_context_menu.tsx @@ -26,7 +26,7 @@ import { EuiContextMenu, EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { HttpStart } from 'kibana/public'; import { UrlPanelContent } from './url_panel_content'; -import { ShareMenuItem, ShareContextMenuPanelItem } from '../types'; +import { ShareMenuItem, ShareContextMenuPanelItem, UrlParamExtension } from '../types'; interface Props { allowEmbed: boolean; @@ -39,6 +39,7 @@ interface Props { onClose: () => void; basePath: string; post: HttpStart['post']; + embedUrlParamExtensions?: UrlParamExtension[]; } export class ShareContextMenu extends Component { @@ -100,6 +101,7 @@ export class ShareContextMenu extends Component { basePath={this.props.basePath} post={this.props.post} shareableUrl={this.props.shareableUrl} + urlParamExtensions={this.props.embedUrlParamExtensions} /> ), }; 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 d13fde52d7a265..481f8312f4262a 100644 --- a/src/plugins/share/public/components/url_panel_content.test.tsx +++ b/src/plugins/share/public/components/url_panel_content.test.tsx @@ -28,7 +28,6 @@ import { ExportUrlAsType, UrlPanelContent } from './url_panel_content'; import { act } from 'react-dom/test-utils'; import { shortenUrl } from '../lib/url_shortener'; -const URL_PARAM_EXTENSIONS_SELECTOR = '[data-test-subj="urlParamExtensions"]'; const defaultProps = { allowShortUrl: true, objectType: 'dashboard', @@ -204,14 +203,12 @@ describe('share url panel content', () => { }); }); -test('should show url param checkboxes when embedded link', () => { - const component = shallow(); - expect(component.find(URL_PARAM_EXTENSIONS_SELECTOR).length).toBe(1); - expect(component).toMatchSnapshot(); -}); - -test('should not show url param checkboxes when permalink', () => { - const component = shallow(); - expect(component.find(URL_PARAM_EXTENSIONS_SELECTOR).length).toBe(0); +test('should show url param extensions', () => { + const TestExtension = () =>
; + const extensions = [{ paramName: 'testExtension', component: TestExtension }]; + const component = shallow( + + ); + expect(component.find('TestExtension').length).toBe(1); 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 db14165f44fa2b..cdfee2ac313908 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -21,15 +21,12 @@ import React, { Component, ReactElement } from 'react'; import { EuiButton, - EuiCheckboxGroup, - EuiCheckboxGroupIdToSelectedMap, EuiCopy, EuiFlexGroup, EuiSpacer, EuiFlexItem, EuiForm, EuiFormRow, - EuiHorizontalRule, EuiIconTip, EuiLoadingSpinner, EuiRadioGroup, @@ -44,6 +41,7 @@ import { HttpStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { shortenUrl } from '../lib/url_shortener'; +import { UrlParamExtension } from '../types'; interface Props { allowShortUrl: boolean; @@ -53,6 +51,7 @@ interface Props { shareableUrl?: string; basePath: string; post: HttpStart['post']; + urlParamExtensions?: UrlParamExtension[]; } export enum ExportUrlAsType { @@ -60,20 +59,13 @@ export enum ExportUrlAsType { EXPORT_URL_AS_SNAPSHOT = 'snapshot', } -interface UrlParams { - topMenu: boolean; - query: boolean; - timeFilter: boolean; - filterBar: boolean; -} - interface State { exportUrlAs: ExportUrlAsType; useShortUrl: boolean; isCreatingShortUrl: boolean; url?: string; shortUrlErrorMsg?: string; - urlParamsSelectedMap: UrlParams; + urlParams?: { [key: string]: { [queryParam: string]: boolean } }; } export class UrlPanelContent extends Component { @@ -90,12 +82,6 @@ export class UrlPanelContent extends Component { useShortUrl: false, isCreatingShortUrl: false, url: '', - urlParamsSelectedMap: { - topMenu: false, - query: false, - timeFilter: false, - filterBar: true, - }, }; } @@ -206,31 +192,52 @@ export class UrlPanelContent extends Component { private getSnapshotUrl = () => { let url = this.props.shareableUrl || window.location.href; + if (this.props.isEmbedded) { url = this.makeUrlEmbeddable(url); } - return url; - }; + if (this.state.urlParams) { + url = this.getUrlParamExtensions(url); + } - private getEmbedQueryParams = (): string => { - return [ - ['&show-top-menu=true', this.state.urlParamsSelectedMap.topMenu], - ['&show-query-input=true', this.state.urlParamsSelectedMap.query], - ['&show-time-filter=true', this.state.urlParamsSelectedMap.timeFilter], - ['&hide-filter-bar=true', !this.state.urlParamsSelectedMap.filterBar], // Inverted to keep default behaviour for old links - ].reduce( - (accumulator, [queryParam, include]) => (include ? accumulator + queryParam : accumulator), - '?embed=true' - ); + return url; }; private makeUrlEmbeddable = (url: string): string => { + const embedParam = '?embed=true'; const urlHasQueryString = url.indexOf('?') !== -1; - const embedQueryParams = this.getEmbedQueryParams(); + if (urlHasQueryString) { - return url.replace('?', `${embedQueryParams}&`); + return url.replace('?', `${embedParam}&`); + } + + return `${url}${embedParam}`; + }; + + private getUrlParamExtensions = (url: string): string => { + if (!this.state.urlParams) { + return url; } - return `${url}${embedQueryParams}`; + + return Object.keys(this.state.urlParams).reduce((urlAccumulator, key) => { + if (!this.state.urlParams || !this.state.urlParams[key]) { + return urlAccumulator; + } + + return Object.keys(this.state.urlParams[key]).reduce((queryAccumulator, queryParam) => { + if ( + !this.state.urlParams || + !this.state.urlParams[key] || + !this.state.urlParams[key][queryParam] + ) { + return queryAccumulator; + } + + return this.state.urlParams[key][queryParam] + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator); + }, url); }; private makeIframeTag = (url?: string) => { @@ -316,17 +323,6 @@ export class UrlPanelContent extends Component { } }; - private handleUrlParamChange = (optionId: string): void => { - const param = optionId as keyof UrlParams; - const stateUpdate: Partial = { - urlParamsSelectedMap: { - ...this.state.urlParamsSelectedMap, - [param]: !this.state.urlParamsSelectedMap[param], - }, - }; - this.setState(stateUpdate as State, this.setUrl); - }; - private renderExportUrlAsOptions = () => { return [ { @@ -439,34 +435,29 @@ export class UrlPanelContent extends Component { }; private renderUrlParamExtensions = (): ReactElement | void => { - if (!this.props.isEmbedded) { + if (!this.props.urlParamExtensions) { return; } - const checkboxes = [ - ['topMenu', 'Top menu'], - ['query', 'Query'], - ['timeFilter', 'Time filter'], - ['filterBar', 'Filter bar'], - ].map(([id, message]) => ({ - id, - label: , - })); + const setParamValue = (paramName: string) => ( + values: { [queryParam: string]: boolean } = {} + ): void => { + const stateUpdate = { + urlParams: { + ...this.state.urlParams, + [paramName]: { + ...values, + }, + }, + }; + this.setState(stateUpdate, this.setUrl); + }; return ( - - , - }} - data-test-subj="urlParamExtensions" - /> + {this.props.urlParamExtensions.map(({ paramName, component: UrlParamComponent }) => ( + + ))} ); }; diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 35116efa859615..3325c5503fe894 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -67,6 +67,7 @@ export class ShareMenuManager { shareableUrl, post, basePath, + embedUrlParamExtensions, }: ShowShareMenuOptions & { menuItems: ShareMenuItem[]; post: HttpStart['post']; @@ -102,6 +103,7 @@ export class ShareMenuManager { onClose={this.onClose} post={post} basePath={basePath} + embedUrlParamExtensions={embedUrlParamExtensions} /> diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts index 6b20f1f53a28c7..8dda9f1195a390 100644 --- a/src/plugins/share/public/types.ts +++ b/src/plugins/share/public/types.ts @@ -17,6 +17,7 @@ * under the License. */ +import { ComponentType } from 'react'; import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; /** @@ -80,9 +81,19 @@ export interface ShareMenuProvider { getShareMenuItems: (context: ShareContext) => ShareMenuItem[]; } +interface UrlParamExtensionProps { + setParamValue: (values: {}) => void; +} + +export interface UrlParamExtension { + paramName: string; + component: ComponentType; +} + /** @public */ export interface ShowShareMenuOptions extends Omit { anchorElement: HTMLElement; allowEmbed: boolean; allowShortUrl: boolean; + embedUrlParamExtensions?: UrlParamExtension[]; } From 098de51f624af6d0c9221aee63b628ccdefba265 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Mon, 11 May 2020 21:28:41 +0100 Subject: [PATCH 10/28] Fix date picker content alignment after rebase --- src/plugins/data/public/ui/search_bar/create_search_bar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 81e84e31980726..6d90c7c92c7703 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -196,6 +196,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) showQueryBar={props.showQueryBar} showQueryInput={props.showQueryInput} showSaveQuery={props.showSaveQuery} + justifyContent={props.justifyContent} screenTitle={props.screenTitle} indexPatterns={props.indexPatterns} timeHistory={data.query.timefilter.history} From 7f4af461bf76a59edaa6d6d6de020d549d46558f Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Mon, 11 May 2020 21:44:21 +0100 Subject: [PATCH 11/28] Fix failing TopNavMenu test due to missing items --- src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx | 1 + 1 file changed, 1 insertion(+) 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 3c542c0738e8d3..46384fb3f27d5b 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 @@ -100,6 +100,7 @@ describe('TopNavMenu', () => { const component = shallowWithIntl( Date: Mon, 11 May 2020 22:37:53 +0100 Subject: [PATCH 12/28] Remove unused props from DashboardAppScope --- .../dashboard/public/application/dashboard_app.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index ef1d68d9217c84..f101935b9288d1 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -20,7 +20,6 @@ import moment from 'moment'; import { Subscription } from 'rxjs'; import { History } from 'history'; -import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; import { ViewMode } from 'src/plugins/embeddable/public'; import { IIndexPattern, TimeRange, Query, Filter, SavedQuery } from 'src/plugins/data/public'; @@ -55,14 +54,6 @@ export interface DashboardAppScope extends ng.IScope { getShouldShowViewHelp: () => boolean; updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void; topNavMenu: any; - showTopNav: () => boolean; - showTopNavMenu: () => boolean; - showSearchBar: () => boolean; - showQueryBar: () => boolean; - showQueryInput: () => boolean; - showDatePicker: () => boolean; - showFilterBar: () => boolean; - justifyContent: FlexGroupJustifyContent; showAddPanel: any; showSaveQuery: boolean; kbnTopNav: any; From cb6af62111b12f7090c7131a2e3e70d4496abef7 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Mon, 11 May 2020 22:45:53 +0100 Subject: [PATCH 13/28] Fix saved object share URL --- .../public/components/url_panel_content.tsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index cdfee2ac313908..d1b505e65307c7 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -154,6 +154,13 @@ export class UrlPanelContent extends Component { } }; + private updateUrlParams = (url: string) => { + const embedUrl = this.props.isEmbedded ? this.makeUrlEmbeddable(url) : url; + const extendUrl = this.state.urlParams ? this.getUrlParamExtensions(embedUrl) : embedUrl; + + return extendUrl; + }; + private getSavedObjectUrl = () => { if (this.isNotSaved()) { return; @@ -169,7 +176,7 @@ export class UrlPanelContent extends Component { // Get the application route, after the hash, and remove the #. const parsedAppUrl = parseUrl(parsedUrl.hash.slice(1), true); - let formattedUrl = formatUrl({ + const formattedUrl = formatUrl({ protocol: parsedUrl.protocol, auth: parsedUrl.auth, host: parsedUrl.host, @@ -183,24 +190,14 @@ export class UrlPanelContent extends Component { }, }), }); - if (this.props.isEmbedded) { - formattedUrl = this.makeUrlEmbeddable(formattedUrl); - } - return formattedUrl; + return this.updateUrlParams(formattedUrl); }; private getSnapshotUrl = () => { - let url = this.props.shareableUrl || window.location.href; - - if (this.props.isEmbedded) { - url = this.makeUrlEmbeddable(url); - } - if (this.state.urlParams) { - url = this.getUrlParamExtensions(url); - } + const url = this.props.shareableUrl || window.location.href; - return url; + return this.updateUrlParams(url); }; private makeUrlEmbeddable = (url: string): string => { From d01ad1745adf486045428e97e1adbe7b538caecb Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Mon, 11 May 2020 22:48:19 +0100 Subject: [PATCH 14/28] Remove isEmbedded from chrome service --- src/core/public/chrome/chrome_service.tsx | 11 +++-------- .../public/application/dashboard_app_controller.tsx | 11 ++++++----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index dfbc91d73d3836..67cd43f0647e43 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -107,8 +107,9 @@ export class ChromeService { * reset the visibility whenever the next application is mounted * 3. Having "embed" in the query string */ - private initVisibility(application: StartDeps['application'], isEmbedded: boolean) { + private initVisibility(application: StartDeps['application']) { // Start off the chrome service hidden if "embed" is in the hash query string. + const isEmbedded = 'embed' in parse(location.hash.slice(1), true).query; this.isForceHidden$ = new BehaviorSubject(isEmbedded); const appHidden$ = merge( @@ -139,9 +140,7 @@ export class ChromeService { notifications, uiSettings, }: StartDeps): Promise { - const isEmbedded = 'embed' in parse(location.hash.slice(1), true).query; - - this.initVisibility(application, isEmbedded); + this.initVisibility(application); const appTitle$ = new BehaviorSubject('Kibana'); const brand$ = new BehaviorSubject({}); @@ -214,8 +213,6 @@ export class ChromeService { recentlyAccessed, docTitle, - isEmbedded, - getHeaderComponent: () => (
- chrome.isEmbedded && Boolean($routeParams[param]); + isEmbeddedExternally && Boolean($routeParams[param]); const forceShowTopNavMenu = shouldForceDisplay(UrlParams.SHOW_TOP_MENU); const forceShowQueryInput = shouldForceDisplay(UrlParams.SHOW_QUERY_INPUT); @@ -334,7 +334,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), - isEmbeddedExternally: Boolean($routeParams.embed), + isEmbeddedExternally, isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, @@ -640,7 +640,7 @@ export class DashboardAppController { showQueryInput, showDatePicker, showFilterBar: showFilterBar(), - justifyContent: (chrome.isEmbedded ? 'flexStart' : 'flexEnd') as FlexGroupJustifyContent, + justifyContent: (isEmbeddedExternally ? 'flexStart' : 'flexEnd') as FlexGroupJustifyContent, indexPatterns: $scope.indexPatterns, showSaveQuery: $scope.showSaveQuery, query: $scope.model.query, @@ -1063,7 +1063,8 @@ export class DashboardAppController { const visibleSubscription = chrome.getIsVisible$().subscribe((isVisible) => { $scope.$evalAsync(() => { - const shouldShow = (forceShow: boolean) => (forceShow || isVisible) && !dashboardStateManager.getFullScreenMode(); + const shouldShow = (forceShow: boolean) => + (forceShow || isVisible) && !dashboardStateManager.getFullScreenMode(); $scope.isVisible = isVisible; showTopNavMenu = shouldShow(forceShowTopNavMenu); From c227a104ba610d815167866e51dc3a22f998ab4d Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Mon, 11 May 2020 23:52:12 +0100 Subject: [PATCH 15/28] Rearrange share panel and regenerate short URLs --- .../application/dashboard_app_controller.tsx | 29 ++++++++----------- .../public/components/url_panel_content.tsx | 14 ++++++--- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index a11e5fe94aba2f..5c8f0aa896212a 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -19,7 +19,7 @@ import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { EUI_MODAL_CANCEL_BUTTON, EuiCheckboxGroup, EuiHorizontalRule } from '@elastic/eui'; +import { EUI_MODAL_CANCEL_BUTTON, EuiCheckboxGroup } from '@elastic/eui'; import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/form/checkbox/checkbox_group'; import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; import React, { useState, ReactElement } from 'react'; @@ -1007,22 +1007,17 @@ export class DashboardAppController { }; return ( - - - - + ); }; diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index d1b505e65307c7..dbe157ba42ebb1 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -103,8 +103,8 @@ export class UrlPanelContent extends Component { {this.renderExportAsRadioGroup()} - {this.renderShortUrlSwitch()} {this.renderUrlParamExtensions()} + {this.renderShortUrlSwitch()} @@ -280,6 +280,10 @@ export class UrlPanelContent extends Component { } // "Use short URL" is checked but shortUrl has not been generated yet so one needs to be created. + this.createShortUrl(); + }; + + private createShortUrl = async () => { this.setState({ isCreatingShortUrl: true, shortUrlErrorMsg: undefined, @@ -295,7 +299,7 @@ export class UrlPanelContent extends Component { this.setState( { isCreatingShortUrl: false, - useShortUrl: isChecked, + useShortUrl: true, }, this.setUrl ); @@ -447,13 +451,15 @@ export class UrlPanelContent extends Component { }, }, }; - this.setState(stateUpdate, this.setUrl); + this.setState(stateUpdate, this.state.useShortUrl ? this.createShortUrl : this.setUrl); }; return ( {this.props.urlParamExtensions.map(({ paramName, component: UrlParamComponent }) => ( - + + + ))} ); From 99587f3b1b9015cda1777bafcd3f75c92abc4844 Mon Sep 17 00:00:00 2001 From: cchaos Date: Thu, 14 May 2020 10:29:12 -0400 Subject: [PATCH 16/28] Fix .globalQueryBar style specificity --- src/legacy/ui/public/styles/_legacy/_base.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/legacy/ui/public/styles/_legacy/_base.scss b/src/legacy/ui/public/styles/_legacy/_base.scss index fd0a1335f9685e..877ae033ae5846 100644 --- a/src/legacy/ui/public/styles/_legacy/_base.scss +++ b/src/legacy/ui/public/styles/_legacy/_base.scss @@ -64,10 +64,6 @@ input[type='checkbox'], padding-bottom: $euiSizeS; } - .globalQueryBar { - padding: 0px $euiSizeS $euiSizeS $euiSizeS; - } - > nav, > navbar { z-index: 2 !important; From f94a99fa8a3198682cc052c6c51e0d343ca0ddff Mon Sep 17 00:00:00 2001 From: cchaos Date: Thu, 14 May 2020 10:35:16 -0400 Subject: [PATCH 17/28] Reduce margin between switch and tooltip --- src/plugins/share/public/components/url_panel_content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index dbe157ba42ebb1..142dae5982061c 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -358,7 +358,7 @@ export class UrlPanelContent extends Component { private renderWithIconTip = (child: React.ReactNode, tipContent: React.ReactNode) => { return ( - {child} + {child} From 65222c68142e3988f98d65f2123b9ce44fb20c97 Mon Sep 17 00:00:00 2001 From: cchaos Date: Thu, 14 May 2020 10:36:51 -0400 Subject: [PATCH 18/28] And snaps --- .../url_panel_content.test.tsx.snap | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) 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 45ed2c4618bade..cae7aa96a7c0e3 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 @@ -44,7 +44,9 @@ exports[`share url panel content render 1`] = ` gutterSize="none" responsive={false} > - + - + - + - + - + - + - + - + - + - + + + + - + - From 3df620258de0c5e04a37f0f0497c286035cb51cc Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Tue, 19 May 2020 16:08:18 +0100 Subject: [PATCH 19/28] Return null from TopNavMenu render functions --- .../navigation/public/top_nav_menu/top_nav_menu.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 964bb49f3968db..2cfca332effb0d 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 @@ -53,8 +53,8 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { return null; } - function renderItems() { - if (!config || config.length === 0) return; + function renderItems(): ReactElement[] | null { + if (!config || config.length === 0) return null; return config.map((menuItem: TopNavMenuData, i: number) => { return ( ; } From 1867d9e0a5ab4995f7e2d49aaba87f3543f320e8 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Tue, 19 May 2020 16:12:32 +0100 Subject: [PATCH 20/28] Refactor getUrlParamExtensions function --- .../public/components/url_panel_content.tsx | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index 142dae5982061c..65a8538693a495 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -59,13 +59,19 @@ export enum ExportUrlAsType { EXPORT_URL_AS_SNAPSHOT = 'snapshot', } +interface UrlParams { + [extensionName: string]: { + [queryParam: string]: boolean; + }; +} + interface State { exportUrlAs: ExportUrlAsType; useShortUrl: boolean; isCreatingShortUrl: boolean; url?: string; shortUrlErrorMsg?: string; - urlParams?: { [key: string]: { [queryParam: string]: boolean } }; + urlParams?: UrlParams; } export class UrlPanelContent extends Component { @@ -212,29 +218,20 @@ export class UrlPanelContent extends Component { }; private getUrlParamExtensions = (url: string): string => { - if (!this.state.urlParams) { - return url; - } - - return Object.keys(this.state.urlParams).reduce((urlAccumulator, key) => { - if (!this.state.urlParams || !this.state.urlParams[key]) { - return urlAccumulator; - } - - return Object.keys(this.state.urlParams[key]).reduce((queryAccumulator, queryParam) => { - if ( - !this.state.urlParams || - !this.state.urlParams[key] || - !this.state.urlParams[key][queryParam] - ) { - return queryAccumulator; - } - - return this.state.urlParams[key][queryParam] - ? queryAccumulator + `&${queryParam}=true` - : queryAccumulator; - }, urlAccumulator); - }, url); + const { urlParams } = this.state; + return urlParams + ? Object.keys(urlParams).reduce((urlAccumulator, key) => { + const urlParam = urlParams[key]; + return urlParam + ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { + const isQueryParamEnabled = urlParam[queryParam]; + return isQueryParamEnabled + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator) + : urlAccumulator; + }, url) + : url; }; private makeIframeTag = (url?: string) => { From 61f16cb6e51b42cf99a3be2be01209c839802493 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Tue, 19 May 2020 16:20:14 +0100 Subject: [PATCH 21/28] Remove toggleChrome prop from ExitFullScreenButton --- src/plugins/dashboard/public/plugin.tsx | 10 +++++----- .../exit_full_screen_button.tsx | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 134fddd2fe1f17..f26b93d7e7e8a6 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -167,9 +167,7 @@ export class DashboardPlugin const getStartServices = async () => { const [coreStart, deps] = await core.getStartServices(); - const useHideChrome = ( - { toggleChrome }: Pick = { toggleChrome: true } - ) => { + const useHideChrome = ({ toggleChrome } = { toggleChrome: true }) => { React.useEffect(() => { if (toggleChrome) { coreStart.chrome.setIsVisible(false); @@ -183,8 +181,10 @@ export class DashboardPlugin }, [toggleChrome]); }; - const ExitFullScreenButton: React.FC = (props) => { - useHideChrome({ toggleChrome: props.toggleChrome }); + const ExitFullScreenButton: React.FC = ({ toggleChrome, ...props }) => { + useHideChrome({ toggleChrome }); return ; }; return { diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx index d4116fe73978e1..2a359b7cca5d1f 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -24,7 +24,6 @@ import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; export interface ExitFullScreenButtonProps { onExitFullScreenMode: () => void; - toggleChrome?: boolean; } import './index.scss'; From 3b9bc78bf214767cf2f529f919c243170dfd34a7 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Tue, 19 May 2020 16:32:18 +0100 Subject: [PATCH 22/28] Remove justifyContent prop from SearchBar --- .../dashboard/public/application/dashboard_app_controller.tsx | 2 -- .../data/public/ui/query_string_input/query_bar_top_row.tsx | 4 +--- src/plugins/data/public/ui/search_bar/create_search_bar.tsx | 1 - src/plugins/data/public/ui/search_bar/search_bar.tsx | 3 --- src/plugins/kibana_legacy/public/angular/kbn_top_nav.js | 1 - 5 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 5c8f0aa896212a..712f672ed1ab94 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -21,7 +21,6 @@ import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EUI_MODAL_CANCEL_BUTTON, EuiCheckboxGroup } from '@elastic/eui'; import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/form/checkbox/checkbox_group'; -import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; import React, { useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; @@ -640,7 +639,6 @@ export class DashboardAppController { showQueryInput, showDatePicker, showFilterBar: showFilterBar(), - justifyContent: (isEmbeddedExternally ? 'flexStart' : 'flexEnd') as FlexGroupJustifyContent, indexPatterns: $scope.indexPatterns, showSaveQuery: $scope.showSaveQuery, query: $scope.model.query, 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 26ba6691f22ccb..f65bf97e391e22 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 @@ -31,7 +31,6 @@ import { EuiFieldText, prettyDuration, } from '@elastic/eui'; -import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; // @ts-ignore import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -57,7 +56,6 @@ interface Props { showDatePicker?: boolean; dateRangeFrom?: string; dateRangeTo?: string; - justifyContent?: FlexGroupJustifyContent; isRefreshPaused?: boolean; refreshInterval?: number; showAutoRefreshOnly?: boolean; @@ -349,7 +347,7 @@ export function QueryBarTopRow(props: Props) { className={classes} responsive={!!props.showDatePicker} gutterSize="s" - justifyContent={props.justifyContent || 'flexEnd'} + justifyContent="flexEnd" > {renderQueryInput()} {renderSharingMetaFields()} diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 6d90c7c92c7703..81e84e31980726 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -196,7 +196,6 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) showQueryBar={props.showQueryBar} showQueryInput={props.showQueryInput} showSaveQuery={props.showSaveQuery} - justifyContent={props.justifyContent} screenTitle={props.screenTitle} indexPatterns={props.indexPatterns} timeHistory={data.query.timefilter.history} diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index a3ad1aa0fca5cd..a5ac2275591158 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -23,7 +23,6 @@ import classNames from 'classnames'; import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; -import { FlexGroupJustifyContent } from '@elastic/eui/src/components/flex/flex_group'; import { withKibana, KibanaReactContextValue } from '../../../../kibana_react/public'; @@ -49,7 +48,6 @@ export interface SearchBarOwnProps { customSubmitButton?: React.ReactNode; screenTitle?: string; dataTestSubj?: string; - justifyContent?: FlexGroupJustifyContent; // Togglers showQueryBar?: boolean; showQueryInput?: boolean; @@ -404,7 +402,6 @@ class SearchBarUI extends Component { this.props.customSubmitButton ? this.props.customSubmitButton : undefined } dataTestSubj={this.props.dataTestSubj} - justifyContent={this.props.justifyContent} /> ); } diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index d1494b18bf1654..b3fbe8baadec37 100644 --- a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -109,7 +109,6 @@ export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { 'refreshInterval', 'disableAutoFocus', 'showAutoRefreshOnly', - 'justifyContent', // temporary flag to use the stateful components 'useDefaultBehaviors', From aca575d04f98a3e51f24a36ac42b077cd1f4c57a Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 20 May 2020 11:48:37 +0100 Subject: [PATCH 23/28] Add functional test for embed mode options --- test/functional/apps/dashboard/embed_mode.js | 39 +++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/dashboard/embed_mode.js b/test/functional/apps/dashboard/embed_mode.js index 65ef75f3f65e1f..924cc46a1a2acf 100644 --- a/test/functional/apps/dashboard/embed_mode.js +++ b/test/functional/apps/dashboard/embed_mode.js @@ -20,6 +20,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { + const testSubjects = getService('testSubjects'); const retry = getService('retry'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); @@ -28,6 +29,13 @@ export default function ({ getService, getPageObjects }) { const globalNav = getService('globalNav'); describe('embed mode', () => { + const urlParamExtensions = [ + 'show-top-menu=true', + 'show-query-input=true', + 'show-time-filter=true', + 'hide-filter-bar=true', + ]; + before(async () => { await esArchiver.load('dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ @@ -54,9 +62,38 @@ export default function ({ getService, getPageObjects }) { }); }); + it('shows or hides elements based on URL params', async () => { + const topMenuShown = await testSubjects.exists('top-nav'); + expect(topMenuShown).to.be(false); + const queryInputShown = await testSubjects.exists('queryInput'); + expect(queryInputShown).to.be(false); + const timeFilterShown = await testSubjects.exists('superDatePickerToggleQuickMenuButton'); + expect(timeFilterShown).to.be(false); + const filterBarShown = await testSubjects.exists('showFilterActions'); + expect(filterBarShown).to.be(true); + + const currentUrl = await browser.getCurrentUrl(); + const newUrl = [currentUrl].concat(urlParamExtensions).join('&'); + // Embed parameter only works on a hard refresh. + const useTimeStamp = true; + await browser.get(newUrl.toString(), useTimeStamp); + + await retry.try(async () => { + const topMenuShown = await testSubjects.exists('top-nav'); + expect(topMenuShown).to.be(true); + const queryInputShown = await testSubjects.exists('queryInput'); + expect(queryInputShown).to.be(true); + const timeFilterShown = await testSubjects.exists('superDatePickerToggleQuickMenuButton'); + expect(timeFilterShown).to.be(true); + const filterBarShown = await testSubjects.exists('showFilterActions'); + expect(filterBarShown).to.be(false); + }); + }); + after(async function () { const currentUrl = await browser.getCurrentUrl(); - const newUrl = currentUrl.replace('&embed=true', ''); + const replaceParams = ['', 'embed=true'].concat(urlParamExtensions).join('&'); + const newUrl = currentUrl.replace(replaceParams, ''); // First use the timestamp to cause a hard refresh so the new embed parameter works correctly. let useTimeStamp = true; await browser.get(newUrl.toString(), useTimeStamp); From 6f18ca8450f71d21609eb5fe6f56c37a1a75dab8 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 20 May 2020 11:57:46 +0100 Subject: [PATCH 24/28] Update i18n translation IDs --- .../dashboard/public/application/dashboard_app_controller.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 712f672ed1ab94..ce79b5c851b9d2 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -979,7 +979,7 @@ export class DashboardAppController { [UrlParams.SHOW_FILTER_BAR, 'filterBar', 'Filter bar'], ].map(([urlParam, translationId, defaultMessage]) => ({ id: urlParam, - label: i18n.translate(`share.urlPanel.${translationId}`, { + label: i18n.translate(`dashboard.embedUrlParamExtension.${translationId}`, { defaultMessage, }), })); @@ -1010,7 +1010,7 @@ export class DashboardAppController { idToSelectedMap={(urlParamsSelectedMap as unknown) as EuiCheckboxGroupIdToSelectedMap} onChange={handleChange} legend={{ - children: i18n.translate('share.urlPanel.include', { + children: i18n.translate('dashboard.embedUrlParamExtension.include', { defaultMessage: 'Include', }), }} From 9bb73ddda86679fafb84ec06b523e178139cf5b7 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 20 May 2020 13:34:03 +0100 Subject: [PATCH 25/28] Use helpers in functional test --- test/functional/apps/dashboard/embed_mode.js | 26 ++++++-------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/test/functional/apps/dashboard/embed_mode.js b/test/functional/apps/dashboard/embed_mode.js index 924cc46a1a2acf..a1828143555b01 100644 --- a/test/functional/apps/dashboard/embed_mode.js +++ b/test/functional/apps/dashboard/embed_mode.js @@ -63,14 +63,10 @@ export default function ({ getService, getPageObjects }) { }); it('shows or hides elements based on URL params', async () => { - const topMenuShown = await testSubjects.exists('top-nav'); - expect(topMenuShown).to.be(false); - const queryInputShown = await testSubjects.exists('queryInput'); - expect(queryInputShown).to.be(false); - const timeFilterShown = await testSubjects.exists('superDatePickerToggleQuickMenuButton'); - expect(timeFilterShown).to.be(false); - const filterBarShown = await testSubjects.exists('showFilterActions'); - expect(filterBarShown).to.be(true); + await testSubjects.missingOrFail('top-nav'); + await testSubjects.missingOrFail('queryInput'); + await testSubjects.missingOrFail('superDatePickerToggleQuickMenuButton'); + await testSubjects.existOrFail('showFilterActions'); const currentUrl = await browser.getCurrentUrl(); const newUrl = [currentUrl].concat(urlParamExtensions).join('&'); @@ -78,16 +74,10 @@ export default function ({ getService, getPageObjects }) { const useTimeStamp = true; await browser.get(newUrl.toString(), useTimeStamp); - await retry.try(async () => { - const topMenuShown = await testSubjects.exists('top-nav'); - expect(topMenuShown).to.be(true); - const queryInputShown = await testSubjects.exists('queryInput'); - expect(queryInputShown).to.be(true); - const timeFilterShown = await testSubjects.exists('superDatePickerToggleQuickMenuButton'); - expect(timeFilterShown).to.be(true); - const filterBarShown = await testSubjects.exists('showFilterActions'); - expect(filterBarShown).to.be(false); - }); + await testSubjects.existOrFail('top-nav'); + await testSubjects.existOrFail('queryInput'); + await testSubjects.existOrFail('superDatePickerToggleQuickMenuButton'); + await testSubjects.missingOrFail('showFilterActions'); }); after(async function () { From d1ff1026aee6651bf1e7ad89b349e1261b2705cb Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 20 May 2020 15:15:16 +0100 Subject: [PATCH 26/28] Pass strings to i18n.translate function --- .../application/dashboard_app_controller.tsx | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index ce79b5c851b9d2..0e77a0c597878c 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -973,16 +973,31 @@ export class DashboardAppController { }); const checkboxes = [ - [UrlParams.SHOW_TOP_MENU, 'topMenu', 'Top menu'], - [UrlParams.SHOW_QUERY_INPUT, 'query', 'Query'], - [UrlParams.SHOW_TIME_FILTER, 'timeFilter', 'Time filter'], - [UrlParams.SHOW_FILTER_BAR, 'filterBar', 'Filter bar'], - ].map(([urlParam, translationId, defaultMessage]) => ({ - id: urlParam, - label: i18n.translate(`dashboard.embedUrlParamExtension.${translationId}`, { - defaultMessage, - }), - })); + { + id: UrlParams.SHOW_TOP_MENU, + label: i18n.translate('dashboard.embedUrlParamExtension.topMenu', { + defaultMessage: 'Top menu', + }), + }, + { + id: UrlParams.SHOW_QUERY_INPUT, + label: i18n.translate('dashboard.embedUrlParamExtension.query', { + defaultMessage: 'Query', + }), + }, + { + id: UrlParams.SHOW_TIME_FILTER, + label: i18n.translate('dashboard.embedUrlParamExtension.timeFilter', { + defaultMessage: 'Time filter', + }), + }, + { + id: UrlParams.SHOW_FILTER_BAR, + label: i18n.translate('dashboard.embedUrlParamExtension.filterBar', { + defaultMessage: 'Filter bar', + }), + }, + ]; const handleChange = (param: string): void => { const urlParamsSelectedMapUpdate = { From aac8b12a03949ed36fc5629f15b0a0937cd1628a Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Wed, 3 Jun 2020 10:24:52 +0100 Subject: [PATCH 27/28] Fix ESLint errors --- src/plugins/dashboard/public/plugin.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index f26b93d7e7e8a6..a3338ab3bbcbb9 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -181,9 +181,11 @@ export class DashboardPlugin }, [toggleChrome]); }; - const ExitFullScreenButton: React.FC = ({ toggleChrome, ...props }) => { + const ExitFullScreenButton: React.FC< + ExitFullScreenButtonProps & { + toggleChrome: boolean; + } + > = ({ toggleChrome, ...props }) => { useHideChrome({ toggleChrome }); return ; }; @@ -299,7 +301,7 @@ export class DashboardPlugin kibanaLegacy.forwardApp( DashboardConstants.DASHBOARD_ID, DashboardConstants.DASHBOARDS_ID, - path => { + (path) => { const [, id, tail] = /dashboard\/?(.*?)($|\?.*)/.exec(path) || []; if (!id && !tail) { // unrecognized sub url @@ -316,7 +318,7 @@ export class DashboardPlugin kibanaLegacy.forwardApp( DashboardConstants.DASHBOARDS_ID, DashboardConstants.DASHBOARDS_ID, - path => { + (path) => { const [, tail] = /(\?.*)/.exec(path) || []; // carry over query if it exists return `#/list${tail || ''}`; From 417abf036310ced144530602f776cc1bc2ad7185 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Sun, 7 Jun 2020 15:13:33 +0100 Subject: [PATCH 28/28] Hide components in full screen when embedded --- .../application/dashboard_app_controller.tsx | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 0e77a0c597878c..206ef4f3d4313a 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -154,11 +154,6 @@ export class DashboardAppController { const queryFilter = filterManager; const timefilter = queryService.timefilter.timefilter; const isEmbeddedExternally = Boolean($routeParams.embed); - let showTopNavMenu = true; - let showSearchBar = true; - let showQueryBar = true; - let showQueryInput = true; - let showDatePicker = true; // url param rules should only apply when embedded (e.g. url?embed=true) const shouldForceDisplay = (param: string): boolean => @@ -284,10 +279,6 @@ export class DashboardAppController { } }; - const showFilterBar = () => - !forceHideFilterBar && - ($scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode()); - const getEmptyScreenProps = ( shouldShowEditHelp: boolean, isEmptyInReadOnlyMode: boolean @@ -625,9 +616,22 @@ export class DashboardAppController { dashboardStateManager.setSavedQueryId(savedQueryId); }; + const shouldShowFilterBar = (forceHide: boolean): boolean => + !forceHide && ($scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode()); + + const shouldShowNavBarComponent = (forceShow: boolean): boolean => + (forceShow || $scope.isVisible) && !dashboardStateManager.getFullScreenMode(); + const getNavBarProps = () => { const isFullScreenMode = dashboardStateManager.getFullScreenMode(); const screenTitle = dashboardStateManager.getTitle(); + const showTopNavMenu = shouldShowNavBarComponent(forceShowTopNavMenu); + const showQueryInput = shouldShowNavBarComponent(forceShowQueryInput); + const showDatePicker = shouldShowNavBarComponent(forceShowDatePicker); + const showQueryBar = showQueryInput || showDatePicker; + const showFilterBar = shouldShowFilterBar(forceHideFilterBar); + const showSearchBar = showQueryBar || showFilterBar; + return { appName: 'dashboard', config: showTopNavMenu ? $scope.topNavMenu : undefined, @@ -638,7 +642,7 @@ export class DashboardAppController { showQueryBar, showQueryInput, showDatePicker, - showFilterBar: showFilterBar(), + showFilterBar, indexPatterns: $scope.indexPatterns, showSaveQuery: $scope.showSaveQuery, query: $scope.model.query, @@ -1071,16 +1075,7 @@ export class DashboardAppController { const visibleSubscription = chrome.getIsVisible$().subscribe((isVisible) => { $scope.$evalAsync(() => { - const shouldShow = (forceShow: boolean) => - (forceShow || isVisible) && !dashboardStateManager.getFullScreenMode(); - $scope.isVisible = isVisible; - showTopNavMenu = shouldShow(forceShowTopNavMenu); - showQueryInput = shouldShow(forceShowQueryInput); - showDatePicker = shouldShow(forceShowDatePicker); - showQueryBar = showQueryInput || showDatePicker; - showSearchBar = showQueryBar || showFilterBar(); - updateNavBar(); }); });