diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 1e84ae309bda..7546b49620a4 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -539,6 +539,8 @@ export interface AppMountParameters { * Optional datasource id to pass while mounting app */ dataSourceId?: string; + + optionalRef?: Record>; } /** diff --git a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx index e5b7529f414b..f799b96f08ff 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx @@ -15,6 +15,7 @@ import { import classNames from 'classnames'; import { compact, isEqual } from 'lodash'; import React, { useState } from 'react'; +import { createPortal } from 'react-dom'; import { DataSource, IDataPluginServices, @@ -64,6 +65,7 @@ export interface QueryEditorTopRowProps { isDirty: boolean; timeHistory?: TimeHistoryContract; indicateNoData?: boolean; + datePickerRef?: React.RefObject; } // Needed for React.lazy @@ -290,7 +292,7 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { return ( - + {renderDatePicker()} {button} @@ -355,6 +357,14 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { 'osdQueryEditor--withDatePicker': props.showDatePicker, }); + const datePicker = ( + + + {renderUpdateButton()} + + + ); + return ( + {props?.datePickerRef?.current && uiSettings.get(UI_SETTINGS.QUERY_ENHANCEMENTS_ENABLED) + ? createPortal(datePicker, props.datePickerRef.current) + : datePicker} {renderQueryEditor()} {props.filterBar} {renderSharingMetaFields()} - {renderUpdateButton()} 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 72b28830652b..3383df9d4e66 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 @@ -46,6 +46,7 @@ import { import { EuiSuperUpdateButton, OnRefreshProps, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@osd/i18n/react'; import { Toast } from 'src/core/public'; +import { createPortal } from 'react-dom'; import { IDataPluginServices, IIndexPattern, TimeRange, TimeHistoryContract, Query } from '../..'; import { useOpenSearchDashboards, @@ -83,6 +84,7 @@ export interface QueryBarTopRowProps { isDirty: boolean; timeHistory?: TimeHistoryContract; indicateNoData?: boolean; + datePickerRef?: React.RefObject; } // Needed for React.lazy @@ -262,7 +264,7 @@ export default function QueryBarTopRow(props: QueryBarTopRowProps) { return ( - + {renderDatePicker()} {button} @@ -393,7 +395,11 @@ export default function QueryBarTopRow(props: QueryBarTopRowProps) { > {renderQueryInput()} {renderSharingMetaFields()} - {renderUpdateButton()} + + {props?.datePickerRef?.current && uiSettings.get(UI_SETTINGS.QUERY_ENHANCEMENTS_ENABLED) + ? createPortal(renderUpdateButton(), props.datePickerRef.current) + : renderUpdateButton()} + ); 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 244f4296216c..31f3401dc76f 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 @@ -237,6 +237,7 @@ export function createSearchBar({ onClearSavedQuery={defaultOnClearSavedQuery(props, clearSavedQuery)} onSavedQueryUpdated={defaultOnSavedQueryUpdated(props, setSavedQuery)} onSaved={defaultOnSavedQueryUpdated(props, setSavedQuery)} + datePickerRef={props.datePickerRef} {...overrideDefaultBehaviors(props)} /> 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 11914f134443..b2ff6766e81c 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -77,6 +77,7 @@ export interface SearchBarOwnProps { refreshInterval?: number; dateRangeFrom?: string; dateRangeTo?: string; + datePickerRef?: React.RefObject; // Query bar - should be in SearchBarInjectedDeps query?: Query; settings?: Settings; @@ -482,6 +483,7 @@ class SearchBarUI extends Component { } dataTestSubj={this.props.dataTestSubj} indicateNoData={this.props.indicateNoData} + datePickerRef={this.props.datePickerRef} /> ); } @@ -518,6 +520,7 @@ class SearchBarUI extends Component { filterBar={filterBar} dataTestSubj={this.props.dataTestSubj} indicateNoData={this.props.indicateNoData} + datePickerRef={this.props.datePickerRef} /> ); } diff --git a/src/plugins/data_explorer/public/components/__snapshots__/app_container.test.tsx.snap b/src/plugins/data_explorer/public/components/__snapshots__/app_container.test.tsx.snap index 29b8e5ab54e9..6e79926aa727 100644 --- a/src/plugins/data_explorer/public/components/__snapshots__/app_container.test.tsx.snap +++ b/src/plugins/data_explorer/public/components/__snapshots__/app_container.test.tsx.snap @@ -3,10 +3,14 @@ exports[`DataExplorerApp should render the canvas and panel when selected 1`] = `
-
- Context +
+
+ Context +
diff --git a/src/plugins/data_explorer/public/components/app_container.scss b/src/plugins/data_explorer/public/components/app_container.scss index 7bd5ed6f69f6..07f070be3b17 100644 --- a/src/plugins/data_explorer/public/components/app_container.scss +++ b/src/plugins/data_explorer/public/components/app_container.scss @@ -20,3 +20,13 @@ $osdHeaderOffset: $euiHeaderHeightCompensation; .headerIsExpanded .deLayout { height: calc(100vh - #{$osdHeaderOffset * 2}); } + +.mainPage { + overflow-x: hidden; + overflow-y: auto; + + .navBar { + padding: $euiSizeXS $euiSizeXS $euiSizeXS $euiSizeM; + border-bottom: $euiBorderThin; + } +} diff --git a/src/plugins/data_explorer/public/components/app_container.tsx b/src/plugins/data_explorer/public/components/app_container.tsx index bf4a02bd223b..d334fb2ae0d3 100644 --- a/src/plugins/data_explorer/public/components/app_container.tsx +++ b/src/plugins/data_explorer/public/components/app_container.tsx @@ -3,8 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { memo } from 'react'; -import { EuiPage, EuiPageBody, EuiResizableContainer, useIsWithinBreakpoints } from '@elastic/eui'; +import React, { memo, useRef } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageBody, + EuiResizableContainer, + useIsWithinBreakpoints, +} from '@elastic/eui'; import { Suspense } from 'react'; import { AppMountParameters } from '../../../../core/public'; import { Sidebar } from './sidebar'; @@ -12,11 +19,19 @@ import { NoView } from './no_view'; import { View } from '../services/view_service/view'; import { shallowEqual } from '../utils/use/shallow_equal'; import './app_container.scss'; +import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; +import { IDataPluginServices } from '../../../data/public'; +import { QUERY_ENHANCEMENT_ENABLED_SETTING } from './constants'; export const AppContainer = React.memo( ({ view, params }: { view?: View; params: AppMountParameters }) => { const isMobile = useIsWithinBreakpoints(['xs', 's', 'm']); - // TODO: Make this more robust. + + const opensearchDashboards = useOpenSearchDashboards(); + const { uiSettings } = opensearchDashboards.services; + + const topLinkRef = useRef(null); + const datePickerRef = useRef(null); if (!view) { return ; } @@ -26,38 +41,67 @@ export const AppContainer = React.memo( const MemoizedPanel = memo(Panel); const MemoizedCanvas = memo(Canvas); + params.optionalRef = { + topLinkRef, + datePickerRef, + }; + // Render the application DOM. return ( - - {/* TODO: improve fallback state */} - Loading...
}> - - - {(EuiResizablePanel, EuiResizableButton) => ( - <> - - - - - - +
+ {uiSettings?.get(QUERY_ENHANCEMENT_ENABLED_SETTING) && ( + + +
+ + +
+ + + )} + + + {/* TODO: improve fallback state */} + Loading...
}> + + + {(EuiResizablePanel, EuiResizableButton) => ( + <> + + + + + + - - - - - - - )} - - - - + + + + + + + )} + + + + +
); }, (prevProps, nextProps) => { diff --git a/src/plugins/data_explorer/public/components/constants.ts b/src/plugins/data_explorer/public/components/constants.ts new file mode 100644 index 000000000000..85d0f9ec146b --- /dev/null +++ b/src/plugins/data_explorer/public/components/constants.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const QUERY_ENHANCEMENT_ENABLED_SETTING = 'query:enhancements:enabled'; diff --git a/src/plugins/discover/common/index.ts b/src/plugins/discover/common/index.ts index 45887df880ae..b47e163a6c54 100644 --- a/src/plugins/discover/common/index.ts +++ b/src/plugins/discover/common/index.ts @@ -15,3 +15,4 @@ export const CONTEXT_DEFAULT_SIZE_SETTING = 'context:defaultSize'; export const CONTEXT_STEP_SETTING = 'context:step'; export const CONTEXT_TIE_BREAKER_FIELDS_SETTING = 'context:tieBreakerFields'; export const MODIFY_COLUMNS_ON_SWITCH = 'discover:modifyColumnsOnSwitch'; +export const QUERY_ENHANCEMENT_ENABLED_SETTING = 'query:enhancements:enabled'; diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.tsx index 592cc23afffc..3e0b00846930 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.tsx @@ -29,7 +29,8 @@ import { OpenSearchPanel } from './open_search_panel'; export const getTopNavLinks = ( services: DiscoverViewServices, inspectorAdapters: Adapters, - savedSearch: SavedSearch + savedSearch: SavedSearch, + isEnhancementEnabled: boolean = false ) => { const { history, @@ -44,7 +45,7 @@ export const getTopNavLinks = ( osdUrlStateStorage, } = services; - const newSearch = { + const newSearch: TopNavMenuData = { id: 'new', label: i18n.translate('discover.localMenu.localMenu.newSearchTitle', { defaultMessage: 'New', @@ -61,6 +62,7 @@ export const getTopNavLinks = ( ariaLabel: i18n.translate('discover.topNav.discoverNewButtonLabel', { defaultMessage: `New Search`, }), + iconType: 'plusInCircle', }; const saveSearch: TopNavMenuData = { @@ -160,9 +162,10 @@ export const getTopNavLinks = ( ); showSaveModal(saveModal, core.i18n.Context); }, + iconType: 'save', }; - const openSearch = { + const openSearch: TopNavMenuData = { id: 'open', label: i18n.translate('discover.localMenu.openTitle', { defaultMessage: 'Open', @@ -190,6 +193,7 @@ export const getTopNavLinks = ( ) ); }, + iconType: 'folderOpen', }; const shareSearch: TopNavMenuData = { @@ -225,9 +229,10 @@ export const getTopNavLinks = ( isDirty: !savedSearch.id || state.isDirty || false, }); }, + iconType: 'share', }; - const inspectSearch = { + const inspectSearch: TopNavMenuData = { id: 'inspect', label: i18n.translate('discover.localMenu.inspectTitle', { defaultMessage: 'Inspect', @@ -244,15 +249,28 @@ export const getTopNavLinks = ( title: savedSearch?.title || undefined, }); }, + iconType: 'inspect', }; - return [ + const topNavLinksArray = [ newSearch, ...(capabilities.discover?.save ? [saveSearch] : []), openSearch, ...(share ? [shareSearch] : []), // Show share option only if share plugin is available inspectSearch, ]; + + if (!isEnhancementEnabled) { + return topNavLinksArray.map((topNavLink) => { + if (topNavLink) { + const { iconType, ...rest } = topNavLink; // Removing the Icon Type property to maintain consistency with older Nav Bar + return rest; + } + return topNavLink; + }); + } + + return topNavLinksArray; }; // TODO: This does not seem to affect the share menu. need to look into it in future diff --git a/src/plugins/discover/public/application/view_components/canvas/discover_canvas.scss b/src/plugins/discover/public/application/view_components/canvas/discover_canvas.scss index 2c2c8dfe8ebb..e0ab20a15296 100644 --- a/src/plugins/discover/public/application/view_components/canvas/discover_canvas.scss +++ b/src/plugins/discover/public/application/view_components/canvas/discover_canvas.scss @@ -45,3 +45,9 @@ } } } + +.topNav { + .hidden { + display: none; + } +} diff --git a/src/plugins/discover/public/application/view_components/canvas/index.tsx b/src/plugins/discover/public/application/view_components/canvas/index.tsx index 5c5fcb358600..54489824227e 100644 --- a/src/plugins/discover/public/application/view_components/canvas/index.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/index.tsx @@ -25,14 +25,18 @@ import { setColumns, useDispatch, useSelector } from '../../utils/state_manageme import { DiscoverViewServices } from '../../../build_services'; import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; import { filterColumns } from '../utils/filter_columns'; -import { DEFAULT_COLUMNS_SETTING, MODIFY_COLUMNS_ON_SWITCH } from '../../../../common'; +import { + DEFAULT_COLUMNS_SETTING, + MODIFY_COLUMNS_ON_SWITCH, + QUERY_ENHANCEMENT_ENABLED_SETTING, +} from '../../../../common'; import { OpenSearchSearchHit } from '../../../application/doc_views/doc_views_types'; import { buildColumns } from '../../utils/columns'; import './discover_canvas.scss'; import { getNewDiscoverSetting, setNewDiscoverSetting } from '../../components/utils/local_storage'; // eslint-disable-next-line import/no-default-export -export default function DiscoverCanvas({ setHeaderActionMenu, history }: ViewProps) { +export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalRef }: ViewProps) { const panelRef = useRef(null); const { data$, refetch$, indexPattern } = useDiscoverContext(); const { @@ -46,6 +50,7 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history }: ViewPro columns: stateColumns !== undefined ? stateColumns : buildColumns([]), }; }); + const isEnhancementsEnabled = uiSettings.get(QUERY_ENHANCEMENT_ENABLED_SETTING); const filteredColumns = filterColumns( columns, indexPattern, @@ -171,12 +176,15 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history }: ViewPro className="dscCanvas" > + {fetchState.status === ResultStatus.NO_RESULTS && ( )} diff --git a/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx b/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx index adb4152cc40f..b6547e1b00a4 100644 --- a/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx @@ -5,6 +5,8 @@ import React, { useEffect, useMemo, useState } from 'react'; import { Query, TimeRange } from 'src/plugins/data/common'; +import { createPortal } from 'react-dom'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { AppMountParameters } from '../../../../../../core/public'; import { connectStorageToQueryState, opensearchFilters } from '../../../../../data/public'; import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; @@ -15,16 +17,19 @@ import { getTopNavLinks } from '../../components/top_nav/get_top_nav_links'; import { getRootBreadcrumbs } from '../../helpers/breadcrumbs'; import { useDiscoverContext } from '../context'; import { useDispatch, setSavedQuery, useSelector } from '../../utils/state_management'; +import './discover_canvas.scss'; export interface TopNavProps { opts: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; onQuerySubmit: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; + optionalRef?: Record>; }; showSaveQuery: boolean; + isEnhancementsEnabled?: boolean; } -export const TopNav = ({ opts, showSaveQuery }: TopNavProps) => { +export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavProps) => { const { services } = useOpenSearchDashboards(); const { inspectorAdapters, savedSearch, indexPattern } = useDiscoverContext(); const [indexPatterns, setIndexPatterns] = useState(undefined); @@ -43,7 +48,9 @@ export const TopNav = ({ opts, showSaveQuery }: TopNavProps) => { osdUrlStateStorage, } = services; - const topNavLinks = savedSearch ? getTopNavLinks(services, inspectorAdapters, savedSearch) : []; + const topNavLinks = savedSearch + ? getTopNavLinks(services, inspectorAdapters, savedSearch, isEnhancementsEnabled) + : []; connectStorageToQueryState(services.data.query, osdUrlStateStorage, { filters: opensearchFilters.FilterStateStore.APP_STATE, @@ -88,22 +95,46 @@ export const TopNav = ({ opts, showSaveQuery }: TopNavProps) => { }; return ( - + <> + {isEnhancementsEnabled && + !!opts?.optionalRef?.topLinkRef?.current && + createPortal( + + {topNavLinks.map((topNavLink) => ( + + + { + topNavLink.run(event.currentTarget); + }} + iconType={topNavLink.iconType} + aria-label={topNavLink.ariaLabel} + /> + + + ))} + , + opts.optionalRef.topLinkRef.current + )} + + ); }; 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 c7aaa2763695..7bce0e01470d 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 @@ -55,6 +55,7 @@ export type TopNavMenuProps = StatefulSearchBarProps & showDataSourceMenu?: boolean; data?: DataPublicPluginStart; className?: string; + datePickerRef?: any; /** * If provided, the menu part of the component will be rendered as a portal inside the given mount point. * diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx index 322ed11d6390..c7a3220a896e 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx @@ -30,7 +30,6 @@ import { EuiButtonProps } from '@elastic/eui'; import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { string } from 'mathjs'; export type TopNavMenuAction = (anchorElement: HTMLElement) => void;