From cd5caa5e8835c8254281523aeff274a9b5a91b3b Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Fri, 18 Oct 2024 13:36:33 -0700 Subject: [PATCH] Improve Empty State Handling: Add No Index Patterns Panel with Data Selection in Discover View (#8613) * Improve Empty State Handling: Add No Index Patterns Panel with Data Selection in Discover View This PR primarily addresses the scenario when no index patterns (general) is available in the Discover view. Instead of redirecting users to the index management page, it introduces a new "No Index Patterns" panel. This panel provides users with the option to open a data selector and add index patterns directly from the Discover view, improving the user experience for new or empty deployments. To achieve, we move the selectedDataset state from ConnectedDatasetSelector to the app container's state management. This allows the AdvancedSelector, opened from the AppContainer, to update the dataset state effectively. Key changes include: * Implementing NoIndexPatternsPanel and AdvancedSelector components. * Refactoring dataset state management in AppContainer and Sidebar. * Modifying DiscoverCanvas to conditionally render NoIndexPatternsPanel. * Updating ConnectedDatasetSelector to use shared state and dataset change handling. Signed-off-by: Anan Zhuang Signed-off-by: Miki * Update design of no data selected Signed-off-by: Miki * use i18n Signed-off-by: Anan Zhuang Signed-off-by: Miki * fix comments Signed-off-by: Anan Zhuang * Update design of no data selected Signed-off-by: Miki * fix lint error Signed-off-by: Anan Zhuang --------- Signed-off-by: Anan Zhuang Signed-off-by: Miki Co-authored-by: Miki --- .../ensure_default_index_pattern.ts | 12 +- src/plugins/data/public/index.ts | 3 +- .../dataset_service/dataset_service.ts | 5 + src/plugins/data/public/ui/_common.scss | 11 ++ src/plugins/data/public/ui/_index.scss | 1 + .../ui/dataset_selector/advanced_selector.tsx | 38 ++++- .../ui/dataset_selector/configurator.tsx | 5 +- .../ui/dataset_selector/dataset_selector.tsx | 18 ++- .../public/ui/dataset_selector/index.test.tsx | 51 +++++- .../data/public/ui/dataset_selector/index.tsx | 29 ++-- src/plugins/data/public/ui/index.ts | 3 +- .../data/public/ui/no_index_patterns/index.ts | 6 + .../no_index_patterns_panel.tsx | 145 ++++++++++++++++++ .../public/components/sidebar/index.tsx | 41 +++-- src/plugins/data_explorer/public/index.ts | 1 + .../utils/state_management/metadata_slice.ts | 22 ++- .../redux_persistence.test.tsx | 1 + .../public/utils/state_management/store.ts | 7 +- .../components/top_nav/get_top_nav_links.tsx | 17 +- .../view_components/canvas/index.tsx | 107 +++++++++---- .../view_components/canvas/top_nav.tsx | 48 ++++-- .../edit_index_pattern/edit_index_pattern.tsx | 2 + 22 files changed, 479 insertions(+), 94 deletions(-) create mode 100644 src/plugins/data/public/ui/_common.scss create mode 100644 src/plugins/data/public/ui/no_index_patterns/index.ts create mode 100644 src/plugins/data/public/ui/no_index_patterns/no_index_patterns_panel.tsx diff --git a/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts index e64b0bf33f63..9fed0e1d0519 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts @@ -32,7 +32,9 @@ import { includes } from 'lodash'; import { IndexPatternsContract } from './index_patterns'; import { UiSettingsCommon } from '../types'; -export type EnsureDefaultIndexPattern = () => Promise | undefined; +export type EnsureDefaultIndexPattern = ( + shouldRedirect?: boolean +) => Promise | undefined; export const createEnsureDefaultIndexPattern = ( uiSettings: UiSettingsCommon, @@ -42,7 +44,10 @@ export const createEnsureDefaultIndexPattern = ( * Checks whether a default index pattern is set and exists and defines * one otherwise. */ - return async function ensureDefaultIndexPattern(this: IndexPatternsContract) { + return async function ensureDefaultIndexPattern( + this: IndexPatternsContract, + shouldRedirect: boolean = true + ) { const patterns = await this.getIds(); let defaultId = await uiSettings.get('defaultIndex'); let defined = !!defaultId; @@ -62,7 +67,8 @@ export const createEnsureDefaultIndexPattern = ( defaultId = patterns[0]; await uiSettings.set('defaultIndex', defaultId); } else { - return onRedirectNoIndexPattern(); + if (shouldRedirect) return onRedirectNoIndexPattern(); + else return; } }; }; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index f986cd59e0eb..773f24118907 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -62,13 +62,14 @@ import { } from '../common'; import { FilterLabel } from './ui'; - export { createEditor, DefaultInput, DQLBody, SingleLineInput, DatasetSelector, + AdvancedSelector, + NoIndexPatternsPanel, DatasetSelectorAppearance, } from './ui'; diff --git a/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts b/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts index 41d1547e8eeb..d8414a33779e 100644 --- a/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts +++ b/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts @@ -207,6 +207,11 @@ export class DatasetService { return Number(this.sessionStorage.get('lastCacheTime')) || undefined; } + public removeFromRecentDatasets(datasetId: string): void { + this.recentDatasets.del(datasetId); + this.serializeRecentDatasets(); + } + private setLastCacheTime(time: number): void { this.sessionStorage.set('lastCacheTime', time); } diff --git a/src/plugins/data/public/ui/_common.scss b/src/plugins/data/public/ui/_common.scss new file mode 100644 index 000000000000..e74a1ccca6d9 --- /dev/null +++ b/src/plugins/data/public/ui/_common.scss @@ -0,0 +1,11 @@ +.dataUI-centerPanel { + height: 100%; + width: 100%; + + // Push the centralized child up, just like ouiOverlayMask + padding-bottom: 10vh; + + & > * { + @include euiLegibilityMaxWidth(100%); + } +} diff --git a/src/plugins/data/public/ui/_index.scss b/src/plugins/data/public/ui/_index.scss index 9ab4ca672b38..cdbe539a2e19 100644 --- a/src/plugins/data/public/ui/_index.scss +++ b/src/plugins/data/public/ui/_index.scss @@ -1,3 +1,4 @@ +@import "./common"; @import "./filter_bar/index"; @import "./typeahead/index"; @import "./saved_query_management/index"; diff --git a/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx b/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx index 734153452eea..25434062de8e 100644 --- a/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx +++ b/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx @@ -13,19 +13,27 @@ import { } from '../../../common'; import { DatasetExplorer } from './dataset_explorer'; import { Configurator } from './configurator'; -import { getQueryService } from '../../services'; import { IDataPluginServices } from '../../types'; export const AdvancedSelector = ({ services, onSelect, onCancel, + selectedDataset, + setSelectedDataset, + setIndexPattern, + direct = false, }: { services: IDataPluginServices; onSelect: (dataset: Dataset) => void; onCancel: () => void; + selectedDataset?: Dataset; + setSelectedDataset: (data: Dataset | undefined) => void; + setIndexPattern: (id: string | undefined) => void; + direct?: boolean; }) => { - const queryString = getQueryService().queryString; + const queryService = services.data.query; + const queryString = queryService.queryString; const [path, setPath] = useState([ { @@ -48,14 +56,21 @@ export const AdvancedSelector = ({ }), }, ]); - const [selectedDataset, setSelectedDataset] = useState(); - return selectedDataset ? ( + const [currentSelectedDataset, setCurrentSelectedDataset] = useState( + selectedDataset + ); + + return currentSelectedDataset ? ( setSelectedDataset(undefined)} + onPrevious={() => { + setSelectedDataset(undefined); + setCurrentSelectedDataset(undefined); + }} + queryService={queryService} /> ) : ( setSelectedDataset(dataset)} + onNext={(dataset) => { + setSelectedDataset(dataset); + setIndexPattern(dataset.id); + setCurrentSelectedDataset(dataset); + if (direct) { + const query = queryString.getInitialQueryByDataset(dataset); + queryString.setQuery(query); + queryString.getDatasetService().addRecentDataset(dataset); + } + }} onCancel={onCancel} /> ); diff --git a/src/plugins/data/public/ui/dataset_selector/configurator.tsx b/src/plugins/data/public/ui/dataset_selector/configurator.tsx index db1cb80dd6e3..b8a74a9353e0 100644 --- a/src/plugins/data/public/ui/dataset_selector/configurator.tsx +++ b/src/plugins/data/public/ui/dataset_selector/configurator.tsx @@ -20,20 +20,21 @@ import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; import React, { useEffect, useMemo, useState } from 'react'; import { BaseDataset, DEFAULT_DATA, Dataset, DatasetField } from '../../../common'; -import { getIndexPatterns, getQueryService } from '../../services'; +import { getIndexPatterns } from '../../services'; export const Configurator = ({ baseDataset, onConfirm, onCancel, onPrevious, + queryService, }: { baseDataset: BaseDataset; onConfirm: (dataset: Dataset) => void; onCancel: () => void; onPrevious: () => void; + queryService: any; }) => { - const queryService = getQueryService(); const queryString = queryService.queryString; const languageService = queryService.queryString.getLanguageService(); const indexPatternsService = getIndexPatterns(); diff --git a/src/plugins/data/public/ui/dataset_selector/dataset_selector.tsx b/src/plugins/data/public/ui/dataset_selector/dataset_selector.tsx index 691f477a5818..6755645020d1 100644 --- a/src/plugins/data/public/ui/dataset_selector/dataset_selector.tsx +++ b/src/plugins/data/public/ui/dataset_selector/dataset_selector.tsx @@ -33,7 +33,9 @@ type EuiSmallButtonEmptyProps = React.ComponentProps interface DatasetSelectorProps { selectedDataset?: Dataset; - setSelectedDataset: (dataset: Dataset) => void; + setSelectedDataset: (data: Dataset | undefined) => void; + setIndexPattern: (id: string | undefined) => void; + handleDatasetChange: (dataset: Dataset) => void; services: IDataPluginServices; } @@ -71,6 +73,8 @@ const RootComponent: React.FC< export const DatasetSelector = ({ selectedDataset, setSelectedDataset, + setIndexPattern, + handleDatasetChange, services, appearance, buttonProps, @@ -102,7 +106,7 @@ export const DatasetSelector = ({ // If no dataset is selected, select the first one if (!selectedDataset && fetchedDatasets.length > 0) { - setSelectedDataset(fetchedDatasets[0]); + handleDatasetChange(fetchedDatasets[0]); } }; @@ -179,11 +183,11 @@ export const DatasetSelector = ({ indexPatterns.find((dataset) => dataset.id === selectedOption.key); if (foundDataset) { closePopover(); - setSelectedDataset(foundDataset); + handleDatasetChange(foundDataset); } } }, - [recentDatasets, indexPatterns, setSelectedDataset, closePopover] + [recentDatasets, indexPatterns, handleDatasetChange, closePopover] ); const datasetTitle = useMemo(() => { @@ -266,10 +270,14 @@ export const DatasetSelector = ({ onSelect={(dataset?: Dataset) => { overlay?.close(); if (dataset) { - setSelectedDataset(dataset); + handleDatasetChange(dataset); } }} onCancel={() => overlay?.close()} + selectedDataset={undefined} + setSelectedDataset={setSelectedDataset} + setIndexPattern={setIndexPattern} + direct={true} /> ), { diff --git a/src/plugins/data/public/ui/dataset_selector/index.test.tsx b/src/plugins/data/public/ui/dataset_selector/index.test.tsx index db78461b96c1..9e486beb5310 100644 --- a/src/plugins/data/public/ui/dataset_selector/index.test.tsx +++ b/src/plugins/data/public/ui/dataset_selector/index.test.tsx @@ -49,40 +49,77 @@ describe('ConnectedDatasetSelector', () => { }); it('should render DatasetSelector with correct props', () => { - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper.find(DatasetSelector).props()).toEqual({ selectedDataset: undefined, setSelectedDataset: expect.any(Function), + setIndexPattern: expect.any(Function), + handleDatasetChange: expect.any(Function), services: mockServices, }); }); it('should initialize selectedDataset correctly', () => { const mockDataset: Dataset = { id: 'initial', title: 'Initial Dataset', type: 'test' }; - mockQueryString.getQuery.mockReturnValueOnce({ dataset: mockDataset }); - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper.find(DatasetSelector).prop('selectedDataset')).toEqual(mockDataset); }); it('should call handleDatasetChange only once when dataset changes', () => { - const wrapper = mount(); - const setSelectedDataset = wrapper.find(DatasetSelector).prop('setSelectedDataset') as ( + const setSelectedDataset = jest.fn(); + const setIndexPattern = jest.fn(); + const wrapper = mount( + + ); + const handleDatasetChange = wrapper.find(DatasetSelector).prop('handleDatasetChange') as ( dataset?: Dataset ) => void; const newDataset: Dataset = { id: 'test', title: 'Test Dataset', type: 'test' }; act(() => { - setSelectedDataset(newDataset); + handleDatasetChange(newDataset); }); expect(mockQueryString.getInitialQueryByDataset).toHaveBeenCalledTimes(1); expect(mockQueryString.setQuery).toHaveBeenCalledTimes(1); expect(mockOnSubmit).toHaveBeenCalledTimes(1); + expect(setSelectedDataset).toHaveBeenCalledWith(newDataset); + expect(setIndexPattern).toHaveBeenCalledWith(newDataset.id); }); it('should subscribe to queryString.getUpdates$ and unsubscribe on unmount', () => { - const wrapper = mount(); + const wrapper = mount( + + ); expect(mockQueryString.getUpdates$).toHaveBeenCalledTimes(1); expect(mockSubscribe).toHaveBeenCalledTimes(1); diff --git a/src/plugins/data/public/ui/dataset_selector/index.tsx b/src/plugins/data/public/ui/dataset_selector/index.tsx index 405e4a672a4d..48cd2926de22 100644 --- a/src/plugins/data/public/ui/dataset_selector/index.tsx +++ b/src/plugins/data/public/ui/dataset_selector/index.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { useCallback, useState, useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import React from 'react'; import { Dataset, Query, TimeRange } from '../../../common'; import { @@ -12,37 +12,42 @@ import { DatasetSelectorUsingButtonProps, DatasetSelectorAppearance, } from './dataset_selector'; -import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; -import { IDataPluginServices } from '../../types'; +import { AdvancedSelector } from './advanced_selector'; interface ConnectedDatasetSelectorProps { onSubmit: ((query: Query, dateRange?: TimeRange | undefined) => void) | undefined; + selectedDataset?: Dataset; + setSelectedDataset: (data: Dataset | undefined) => void; + setIndexPattern: (id: string | undefined) => void; + services?: any; } const ConnectedDatasetSelector = ({ onSubmit, + selectedDataset, + setSelectedDataset, + setIndexPattern, + services, ...datasetSelectorProps }: ConnectedDatasetSelectorProps & (DatasetSelectorUsingButtonProps | DatasetSelectorUsingButtonEmptyProps)) => { - const { services } = useOpenSearchDashboards(); const queryString = services.data.query.queryString; - const [selectedDataset, setSelectedDataset] = useState( - () => queryString.getQuery().dataset || queryString.getDefaultQuery().dataset - ); useEffect(() => { const subscription = queryString.getUpdates$().subscribe((query) => { setSelectedDataset(query.dataset); + setIndexPattern(query.dataset?.id); }); return () => { subscription.unsubscribe(); }; - }, [queryString]); + }, [queryString, setSelectedDataset, setIndexPattern]); const handleDatasetChange = useCallback( (dataset?: Dataset) => { setSelectedDataset(dataset); + setIndexPattern(dataset?.id); if (dataset) { const query = queryString.getInitialQueryByDataset(dataset); queryString.setQuery(query); @@ -50,17 +55,19 @@ const ConnectedDatasetSelector = ({ queryString.getDatasetService().addRecentDataset(dataset); } }, - [onSubmit, queryString] + [onSubmit, queryString, setSelectedDataset, setIndexPattern] ); return ( ); }; -export { ConnectedDatasetSelector as DatasetSelector, DatasetSelectorAppearance }; +export { ConnectedDatasetSelector as DatasetSelector, AdvancedSelector, DatasetSelectorAppearance }; diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 89bd87ae39f8..00ac361bb26e 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -51,4 +51,5 @@ export { useQueryStringManager, } from './search_bar'; export { SuggestionsComponent } from './typeahead'; -export { DatasetSelector, DatasetSelectorAppearance } from './dataset_selector'; +export { DatasetSelector, AdvancedSelector, DatasetSelectorAppearance } from './dataset_selector'; +export { NoIndexPatternsPanel } from './no_index_patterns'; diff --git a/src/plugins/data/public/ui/no_index_patterns/index.ts b/src/plugins/data/public/ui/no_index_patterns/index.ts new file mode 100644 index 000000000000..4f7a76b5baa2 --- /dev/null +++ b/src/plugins/data/public/ui/no_index_patterns/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './no_index_patterns_panel'; diff --git a/src/plugins/data/public/ui/no_index_patterns/no_index_patterns_panel.tsx b/src/plugins/data/public/ui/no_index_patterns/no_index_patterns_panel.tsx new file mode 100644 index 000000000000..6dd7d2a8614c --- /dev/null +++ b/src/plugins/data/public/ui/no_index_patterns/no_index_patterns_panel.tsx @@ -0,0 +1,145 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiText, + EuiSmallButton, + EuiSpacer, + EuiTitle, + EuiButtonEmpty, +} from '@elastic/eui'; + +interface NoIndexPatternsPanelProps { + onOpenDataSelector: () => void; +} + +export const NoIndexPatternsPanel: React.FC = ({ + onOpenDataSelector, +}) => ( + + + + + + + + + +

+ {i18n.translate('data.noIndexPatterns.selectDataTitle', { + defaultMessage: 'Select data', + })} +

+
+
+ + + {i18n.translate('data.noIndexPatterns.selectDataDescription', { + defaultMessage: + 'Select an available data source and choose a query language to use for running queries. You can use the data dropdown or use the enhanced data selector to select data.', + })} + + + + + {i18n.translate('data.noIndexPatterns.openDataSelectorButton', { + defaultMessage: 'Open data selector', + })} + + + + + +

+ {i18n.translate('data.noIndexPatterns.learnMoreAboutQueryLanguages', { + defaultMessage: 'Learn more about query languages', + })} +

+
+
+ + + + + + {i18n.translate('data.noIndexPatterns.pplDocumentation', { + defaultMessage: 'PPL documentation', + })} + + + + + + + {i18n.translate('data.noIndexPatterns.sqlDocumentation', { + defaultMessage: 'SQL documentation', + })} + + + + + + + {i18n.translate('data.noIndexPatterns.luceneDocumentation', { + defaultMessage: 'Lucene documentation', + })} + + + + + + + {i18n.translate('data.noIndexPatterns.dqlDocumentation', { + defaultMessage: 'DQL documentation', + })} + + + + + +
+
+
+
+); diff --git a/src/plugins/data_explorer/public/components/sidebar/index.tsx b/src/plugins/data_explorer/public/components/sidebar/index.tsx index 56250228e559..3c5975d9452e 100644 --- a/src/plugins/data_explorer/public/components/sidebar/index.tsx +++ b/src/plugins/data_explorer/public/components/sidebar/index.tsx @@ -17,26 +17,35 @@ import { DatasetSelector, DatasetSelectorAppearance, } from '../../../../data/public/'; +import { Dataset } from '../../../../data/common'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { DataExplorerServices } from '../../types'; -import { setIndexPattern, useTypedDispatch, useTypedSelector } from '../../utils/state_management'; +import { + setIndexPattern, + useTypedDispatch, + useTypedSelector, + setSelectedDataset, +} from '../../utils/state_management'; import './index.scss'; +type HandleSetIndexPattern = (id: string | undefined) => void; +type HandleSelectedDataset = (data: Dataset | undefined) => void; + export const Sidebar: FC = ({ children }) => { - const { indexPattern: indexPatternId } = useTypedSelector((state) => state.metadata); + const { indexPattern: indexPatternId, selectedDataset } = useTypedSelector( + (state) => state.metadata + ); const dispatch = useTypedDispatch(); const [selectedSources, setSelectedSources] = useState([]); const [dataSourceOptionList, setDataSourceOptionList] = useState([]); const [activeDataSources, setActiveDataSources] = useState([]); - + const { services } = useOpenSearchDashboards(); const { - services: { - data: { indexPatterns, dataSources }, - notifications: { toasts }, - application, - uiSettings, - }, - } = useOpenSearchDashboards(); + data: { indexPatterns, dataSources }, + notifications: { toasts }, + application, + uiSettings, + } = services; const handleDatasetSubmit = useCallback( (query: any) => { @@ -128,6 +137,14 @@ export const Sidebar: FC = ({ children }) => { dataSources.dataSourceService.reload(); }, [dataSources.dataSourceService]); + const handleSetIndexPattern: HandleSetIndexPattern = (id: string | undefined) => { + dispatch(setIndexPattern(id)); + }; + + const handleSelectedDataset: HandleSelectedDataset = (data: Dataset | undefined) => { + dispatch(setSelectedDataset(data)); + }; + return ( { {isEnhancementEnabled ? ( ) => { state.view = action.payload; }, + setSelectedDataset: (state, action: PayloadAction) => { + state.selectedDataset = action.payload; + }, setState: (_state, action: PayloadAction) => { return action.payload; }, @@ -58,4 +66,10 @@ export const slice = createSlice({ }); export const { reducer } = slice; -export const { setIndexPattern, setOriginatingApp, setView, setState } = slice.actions; +export const { + setIndexPattern, + setOriginatingApp, + setView, + setState, + setSelectedDataset, +} = slice.actions; diff --git a/src/plugins/data_explorer/public/utils/state_management/redux_persistence.test.tsx b/src/plugins/data_explorer/public/utils/state_management/redux_persistence.test.tsx index 62159558a0c4..9af3bcf3d491 100644 --- a/src/plugins/data_explorer/public/utils/state_management/redux_persistence.test.tsx +++ b/src/plugins/data_explorer/public/utils/state_management/redux_persistence.test.tsx @@ -26,6 +26,7 @@ describe('test redux state persistence', () => { "metadata": Object { "indexPattern": "id", "originatingApp": undefined, + "selectedDataset": undefined, }, } `); diff --git a/src/plugins/data_explorer/public/utils/state_management/store.ts b/src/plugins/data_explorer/public/utils/state_management/store.ts index daf0b3d7e369..1ac3564e34ce 100644 --- a/src/plugins/data_explorer/public/utils/state_management/store.ts +++ b/src/plugins/data_explorer/public/utils/state_management/store.ts @@ -116,4 +116,9 @@ export type RenderState = Omit; // Remaining state after export type Store = ReturnType; export type AppDispatch = Store['dispatch']; -export { MetadataState, setIndexPattern, setOriginatingApp } from './metadata_slice'; +export { + MetadataState, + setIndexPattern, + setOriginatingApp, + setSelectedDataset, +} from './metadata_slice'; 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 647b989f42e4..d87e1e47f702 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 @@ -277,7 +277,8 @@ export const getTopNavLinks = ( services: DiscoverViewServices, inspectorAdapters: Adapters, savedSearch: SavedSearch, - isEnhancementEnabled: boolean = false + isEnhancementEnabled: boolean = false, + useNoIndexPatternsTopNav: boolean = false ) => { const { history, @@ -503,8 +504,18 @@ export const getTopNavLinks = ( // Order their appearance return ['save', 'open', 'new', 'inspect', 'share'].reduce((acc, item) => { const itemDef = topNavLinksMap.get(item); - if (itemDef) acc.push(itemDef); - + if (itemDef) { + if (useNoIndexPatternsTopNav && item !== 'open') { + // Disable all buttons except 'open' when in no index patterns mode + acc.push({ + ...itemDef, + disabled: true, + run: () => {}, // Empty function for disabled buttons + }); + } else { + acc.push(itemDef); + } + } return acc; }, [] as TopNavMenuData[]); }; 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 fcad13342e07..4cf6a15dc7d5 100644 --- a/src/plugins/discover/public/application/view_components/canvas/index.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/index.tsx @@ -27,18 +27,27 @@ import { OpenSearchSearchHit } from '../../../application/doc_views/doc_views_ty import { buildColumns } from '../../utils/columns'; import './discover_canvas.scss'; import { HeaderVariant } from '../../../../../../core/public'; +import { setIndexPattern, setSelectedDataset } from '../../../../../data_explorer/public'; +import { NoIndexPatternsPanel, AdvancedSelector } from '../../../../../data/public'; +import { Dataset } from '../../../../../data/common'; +import { toMountPoint } from '../../../../../opensearch_dashboards_react/public'; // eslint-disable-next-line import/no-default-export export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalRef }: ViewProps) { + const { indexPattern: currentIndexPattern, selectedDataset } = useSelector( + (state) => state.metadata + ); + const [loadedIndexPattern, setLoadedIndexPattern] = useState(selectedDataset?.id); const panelRef = useRef(null); const { data$, refetch$, indexPattern } = useDiscoverContext(); + const { services } = useOpenSearchDashboards(); const { - services: { - uiSettings, - capabilities, - chrome: { setHeaderVariant }, - }, - } = useOpenSearchDashboards(); + uiSettings, + capabilities, + chrome: { setHeaderVariant }, + data, + overlays, + } = services; const { columns } = useSelector((state) => { const stateColumns = state.discover.columns; @@ -122,10 +131,48 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR }; const showSaveQuery = !!capabilities.discover?.saveQuery; + const handleDatasetChange = (dataset: Dataset) => { + dispatch(setSelectedDataset(dataset)); + + // Update query and other necessary state + const queryString = data.query.queryString; + const query = queryString.getInitialQueryByDataset(dataset); + queryString.setQuery(query); + queryString.getDatasetService().addRecentDataset(dataset); + }; + + const handleOpenDataSelector = () => { + const overlay = overlays?.openModal( + toMountPoint( + { + overlay?.close(); + if (dataset) { + handleDatasetChange(dataset); + } + }} + onCancel={() => overlay?.close()} + selectedDataset={undefined} + setSelectedDataset={setSelectedDataset} + setIndexPattern={setIndexPattern} + dispatch={dispatch} + /> + ), + { + maxWidth: false, + className: 'datasetSelector__advancedModal', + } + ); + }; + + const hasNoDataset = !currentIndexPattern && !loadedIndexPattern && isEnhancementsEnabled; + return ( - - {fetchState.status === ResultStatus.NO_RESULTS && ( - - )} - {fetchState.status === ResultStatus.ERROR && ( - - )} - {fetchState.status === ResultStatus.UNINITIALIZED && ( - refetch$.next()} /> - )} - {fetchState.status === ResultStatus.LOADING && } - {fetchState.status === ResultStatus.READY && isEnhancementsEnabled && ( + {hasNoDataset ? ( + + ) : ( <> - - + {fetchState.status === ResultStatus.NO_RESULTS && ( + + )} + {fetchState.status === ResultStatus.ERROR && ( + + )} + {fetchState.status === ResultStatus.UNINITIALIZED && ( + refetch$.next()} /> + )} + {fetchState.status === ResultStatus.LOADING && } + {fetchState.status === ResultStatus.READY && isEnhancementsEnabled && ( + <> + + + + )} + {fetchState.status === ResultStatus.READY && !isEnhancementsEnabled && ( + + + + + )} )} - {fetchState.status === ResultStatus.READY && !isEnhancementsEnabled && ( - - - - - )} ); } 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 2aa288b9bdf1..0b3f1275d7ff 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 @@ -35,9 +35,15 @@ export interface TopNavProps { }; showSaveQuery: boolean; isEnhancementsEnabled?: boolean; + useNoIndexPatternsTopNav?: boolean; } -export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavProps) => { +export const TopNav = ({ + opts, + showSaveQuery, + isEnhancementsEnabled, + useNoIndexPatternsTopNav = false, +}: TopNavProps) => { const { services } = useOpenSearchDashboards(); const { data$, inspectorAdapters, savedSearch, indexPattern } = useDiscoverContext(); const [indexPatterns, setIndexPatterns] = useState(undefined); @@ -62,7 +68,13 @@ export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavPro const showActionsInGroup = uiSettings.get('home:useNewHomePage'); const topNavLinks = savedSearch - ? getTopNavLinks(services, inspectorAdapters, savedSearch, isEnhancementsEnabled) + ? getTopNavLinks( + services, + inspectorAdapters, + savedSearch, + isEnhancementsEnabled, + useNoIndexPatternsTopNav + ) : []; connectStorageToQueryState( @@ -88,7 +100,7 @@ export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavPro useEffect(() => { let isMounted = true; const initializeDataset = async () => { - await data.indexPatterns.ensureDefaultIndexPattern(); + await data.indexPatterns.ensureDefaultIndexPattern(isEnhancementsEnabled ? false : true); const defaultIndexPattern = await data.indexPatterns.getDefault(); // TODO: ROCKY do we need this? // const queryString = data.query.queryString; @@ -107,7 +119,7 @@ export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavPro return () => { isMounted = false; }; - }, [data.indexPatterns, data.query]); + }, [data.indexPatterns, data.query, isEnhancementsEnabled]); useEffect(() => { const pageTitleSuffix = savedSearch?.id && savedSearch.title ? `: ${savedSearch.title}` : ''; @@ -164,18 +176,30 @@ export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavPro {} : opts.onQuerySubmit} + savedQueryId={useNoIndexPatternsTopNav ? undefined : state.savedQuery} + onSavedQueryIdChange={useNoIndexPatternsTopNav ? () => {} : updateSavedQueryId} + datePickerRef={useNoIndexPatternsTopNav ? undefined : opts?.optionalRef?.datePickerRef} groupActions={showActionsInGroup} - screenTitle={screenTitle} + screenTitle={ + useNoIndexPatternsTopNav + ? i18n.translate('discover.noIndexPatterns.screenTitle', { + defaultMessage: 'Select data', + }) + : screenTitle + } queryStatus={queryStatus} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index e5f87af2e974..4beafd982013 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -162,6 +162,8 @@ export const EditIndexPattern = withRouter( } if (indexPattern.id) { Promise.resolve(data.indexPatterns.delete(indexPattern.id)).then(function () { + const datasetService = data.query.queryString.getDatasetService(); + datasetService.removeFromRecentDatasets(indexPattern.id); history.push(''); }); }