diff --git a/changelogs/fragments/7289.yml b/changelogs/fragments/7289.yml new file mode 100644 index 00000000000..b181433dbec --- /dev/null +++ b/changelogs/fragments/7289.yml @@ -0,0 +1,2 @@ +feat: +- Add DataSet dropdown with index patterns and indices ([#7289](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7289)) \ No newline at end of file diff --git a/src/plugins/data/public/data_sources/datasource_selector/types.ts b/src/plugins/data/public/data_sources/datasource_selector/types.ts index e3547046aec..fe1e4360e96 100644 --- a/src/plugins/data/public/data_sources/datasource_selector/types.ts +++ b/src/plugins/data/public/data_sources/datasource_selector/types.ts @@ -24,7 +24,6 @@ export interface DataSourceOption { value: string; type: string; ds: DataSource; - checked?: boolean; } export interface DataSourceSelectableProps extends Pick, 'fullWidth'> { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 208359352e4..46390b81982 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -446,6 +446,7 @@ export { // for BWC, keeping the old name IUiStart as DataPublicPluginStartUi, DataSetNavigator, + DataSetOption, } from './ui'; /** diff --git a/src/plugins/data/public/ui/dataset_navigator/create_dataset_navigator.tsx b/src/plugins/data/public/ui/dataset_navigator/create_dataset_navigator.tsx new file mode 100644 index 00000000000..552411067c9 --- /dev/null +++ b/src/plugins/data/public/ui/dataset_navigator/create_dataset_navigator.tsx @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { SavedObjectsClientContract } from 'src/core/public'; +import { IndexPatternsContract } from 'src/plugins/data/public'; +import { DataSetNavigator, DataSetNavigatorProps } from './'; +import { Settings } from '../settings'; + +// Updated function signature to include additional dependencies +export function createDataSetNavigator( + settings: Settings, + savedObjectsClient: SavedObjectsClientContract, + indexPatternsService: IndexPatternsContract, + search: any, + onDataSetSelected: any, +) { + // Return a function that takes props, omitting the dependencies from the props type + return ( + props: Omit + ) => ( + + ); +} diff --git a/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx b/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx index ef6be6b0294..c8e1e5abd18 100644 --- a/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx +++ b/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx @@ -4,166 +4,84 @@ */ import React, { useEffect, useState } from 'react'; - import { EuiButtonEmpty, EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { SavedObjectsClientContract, SimpleSavedObject } from 'opensearch-dashboards/public'; -import { map, scan } from 'rxjs/operators'; -import { ISearchStart } from '../../search/types'; +import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; +import _ from 'lodash'; import { IIndexPattern } from '../..'; -import { getUiService, getIndexPatterns, getSearchService } from '../../services'; - -const getClusters = async (savedObjectsClient: SavedObjectsClientContract) => { - return await savedObjectsClient.find({ - type: 'data-source', - perPage: 10000, - }); -}; - -export const searchResponseToArray = (showAllIndices: boolean) => (response) => { - const { rawResponse } = response; - if (!rawResponse.aggregations) { - return []; - } else { - return rawResponse.aggregations.indices.buckets - .map((bucket: { key: string }) => { - return bucket.key; - }) - .filter((indexName: string) => { - if (showAllIndices) { - return true; - } else { - return !indexName.startsWith('.'); - } - }) - .map((indexName: string) => { - return { - name: indexName, - // item: {}, - }; - }); - } -}; - -const buildSearchRequest = (showAllIndices: boolean, pattern: string, dataSourceId?: string) => { - const request = { - params: { - ignoreUnavailable: true, - expand_wildcards: showAllIndices ? 'all' : 'open', - index: pattern, - body: { - size: 0, // no hits - aggs: { - indices: { - terms: { - field: '_index', - size: 100, - }, - }, - }, - }, - }, - dataSourceId, - }; - - return request; -}; +import { fetchClusters } from './fetch_clusters'; +import { fetchIndices } from './fetch_indices'; +import { Settings } from '../settings'; -const getIndices = async (search: ISearchStart, dataSourceId: string) => { - const request = buildSearchRequest(true, '*', dataSourceId); - return search - .search(request) - .pipe(map(searchResponseToArray(true))) - .pipe(scan((accumulator = [], value) => accumulator.join(value))) - .toPromise() - .catch(() => []); -}; - -interface DataSetOption { +export interface DataSetOption { id: string; name: string; dataSourceRef?: string; } -interface DataSetNavigatorProps { +export interface DataSetNavigatorProps { + settings: Settings; savedObjectsClient: SavedObjectsClientContract; - indexPatterns: Array; + indexPattern?: Array; + dataSetId?: string; + onDataSetSelected: (dataSet: DataSetOption) => void; + indexPatternsService: any; + search: any; +} + +interface DataSetNavigatorState { + isLoading: boolean; + isOpen: boolean; + clusters: DataSetOption[]; + indices: DataSetOption[]; + indexPatterns: DataSetOption[]; + selectedDataSet: DataSetOption | undefined; + selectedCluster: DataSetOption | undefined; + searchValue: string | undefined; + dataSourceIdToTitle: Map; } -export const DataSetNavigator = ({ savedObjectsClient, indexPatterns }: DataSetNavigatorProps) => { +export const DataSetNavigator = (props: DataSetNavigatorProps) => { + const { settings, indexPatternsService, savedObjectsClient, search, onDataSetSelected } = props; + const [indexPatternList, setIndexPatternList] = useState([]); + const [clusterList, setClusterList] = useState([]); + const [indexList, setIndexList] = useState([]); + const [selectedCluster, setSelectedCluster] = useState(null); + const [selectedDataSet, setSelectedDataSet] = useState(null); const [isDataSetNavigatorOpen, setIsDataSetNavigatorOpen] = useState(false); - const [clusterList, setClusterList] = useState([]); - const [indexList, setIndexList] = useState([]); - const [selectedCluster, setSelectedCluster] = useState(); - const [selectedDataSet, setSelectedDataSet] = useState({ - id: indexPatterns[0]?.id, - name: indexPatterns[0]?.title, - }); - const [indexPatternList, setIndexPatternList] = useState([]); - const search = getSearchService(); - const uiService = getUiService(); - const indexPatternsService = getIndexPatterns(); - const onButtonClick = () => setIsDataSetNavigatorOpen((isOpen) => !isOpen); + const onButtonClick = () => setIsDataSetNavigatorOpen(!isDataSetNavigatorOpen); const closePopover = () => setIsDataSetNavigatorOpen(false); - - const onDataSetClick = async (ds: DataSetOption) => { - const existingIndexPattern = indexPatternsService.getByTitle(ds.id, true); - const dataSet = await indexPatternsService.create( - { id: ds.id, title: ds.name }, - !existingIndexPattern?.id - ); - // save to cache by title because the id is not unique for temporary index pattern created - indexPatternsService.saveToCache(dataSet.title, dataSet); - uiService.Settings.setSelectedDataSet({ - id: dataSet.id, - name: dataSet.title, - dataSourceRef: selectedCluster?.id ?? undefined, - }); - setSelectedDataSet(ds); + const onDataSetClick = async (dataSet) => { + setSelectedDataSet(dataSet); + onDataSetSelected(dataSet); + settings.setSelectedDataSet(dataSet); closePopover(); }; useEffect(() => { - const subscription = uiService.Settings.getSelectedDataSet$().subscribe((dataSet) => { - if (dataSet) { - setSelectedDataSet(dataSet); - } + // Fetch index patterns + indexPatternsService.getIdsWithTitle().then((res) => { + setIndexPatternList(res.map(({ id, title }) => ({ id, name: title }))); }); - return () => subscription.unsubscribe(); - }, [uiService]); - - // get all index patterns - useEffect(() => { - indexPatternsService.getIdsWithTitle().then((res) => - setIndexPatternList( - res.map((indexPattern: { id: string; title: string }) => ({ - id: indexPattern.id, - name: indexPattern.title, - })) - ) - ); - }, [indexPatternsService]); - useEffect(() => { - Promise.all([getClusters(savedObjectsClient)]).then((res) => { - setClusterList(res.length > 0 ? res?.[0].savedObjects : []); + // Fetch clusters + fetchClusters(savedObjectsClient).then((res) => { + setClusterList(res.savedObjects); }); - }, [savedObjectsClient]); - useEffect(() => { + // Fetch indices if a cluster is selected if (selectedCluster) { - // Get all indexes - getIndices(search, selectedCluster.id).then((res) => { + fetchIndices(search, selectedCluster.id).then((res) => { setIndexList( - res.map((index: { name: string }) => ({ - name: index.name, - id: index.name, + res.map(({ name }) => ({ + name, + id: name, dataSourceRef: selectedCluster.id, })) ); }); } - }, [search, selectedCluster, setIndexList]); + }, [indexPatternsService, savedObjectsClient, search, selectedCluster]); const dataSetButton = ( ); + const contextMenuPanels = [ + { + id: 0, + title: 'DATA', + items: [ + { + name: 'Index Patterns', + panel: 1, + }, + ...clusterList.map((cluster) => ({ + name: cluster.attributes.title, + panel: 2, + onClick: () => setSelectedCluster(cluster), + })), + ], + }, + { + id: 1, + title: 'Index Patterns', + items: indexPatternList.map((indexPattern) => ({ + name: indexPattern.name, + onClick: () => onDataSetClick(indexPattern), + })), + }, + { + id: 2, + title: selectedCluster ? selectedCluster.attributes.title : 'Cluster', + items: [ + { + name: 'Indexes', + panel: 3, + }, + ], + }, + { + id: 3, + title: selectedCluster ? selectedCluster.attributes.title : 'Cluster', + items: indexList.map((index) => ({ + name: index.name, + onClick: () => onDataSetClick(index), + })), + }, + { + id: 4, + title: 'clicked', + }, + ]; + return ( - ({ - name: cluster.attributes.title, - panel: 2, - onClick: () => { - setSelectedCluster(cluster); - }, - })) - : []), - ], - }, - { - id: 1, - title: 'Index Patterns', - items: [ - ...(indexPatternList - ? indexPatternList.map((indexPattern) => ({ - name: indexPattern.name, - onClick: () => onDataSetClick(indexPattern), - })) - : []), - ], - }, - { - id: 2, - title: selectedCluster ? selectedCluster.attributes.title : 'Cluster', - items: [ - { - name: 'Indexes', - panel: 3, - }, - ], - }, - { - id: 3, - title: selectedCluster ? selectedCluster.attributes.title : 'Cluster', - items: [ - ...(indexList - ? indexList.map((index) => ({ - name: index.name, - onClick: () => onDataSetClick(index), - })) - : []), - ], - }, - { - id: 4, - title: 'clicked', - }, - ]} - /> + ); }; + +// eslint-disable-next-line import/no-default-export +export default DataSetNavigator; diff --git a/src/plugins/data/public/ui/dataset_navigator/fetch_clusters.ts b/src/plugins/data/public/ui/dataset_navigator/fetch_clusters.ts new file mode 100644 index 00000000000..54519d062fe --- /dev/null +++ b/src/plugins/data/public/ui/dataset_navigator/fetch_clusters.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; + +export const fetchClusters = async (savedObjectsClient: SavedObjectsClientContract) => { + return await savedObjectsClient.find({ + type: 'data-source', + perPage: 10000, + }); +}; diff --git a/src/plugins/data/public/ui/dataset_navigator/fetch_index_patterns.ts b/src/plugins/data/public/ui/dataset_navigator/fetch_index_patterns.ts new file mode 100644 index 00000000000..fd1d893565e --- /dev/null +++ b/src/plugins/data/public/ui/dataset_navigator/fetch_index_patterns.ts @@ -0,0 +1,21 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; + +export const fetchIndexPatterns = async ( + client: SavedObjectsClientContract, + search: string, + fields: string[] +) => { + const resp = await client.find({ + type: 'index-pattern', + fields, + search: `${search}*`, + searchFields: ['title'], + perPage: 100, + }); + return resp.savedObjects; +}; diff --git a/src/plugins/data/public/ui/dataset_navigator/fetch_indices.ts b/src/plugins/data/public/ui/dataset_navigator/fetch_indices.ts new file mode 100644 index 00000000000..db08a3f9625 --- /dev/null +++ b/src/plugins/data/public/ui/dataset_navigator/fetch_indices.ts @@ -0,0 +1,67 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { map, scan } from 'rxjs/operators'; +import { ISearchStart } from '../../search'; + +export const fetchIndices = async (search: ISearchStart, dataSourceId: string) => { + const request = buildSearchRequest(true, '*', dataSourceId); + return search + .getDefaultSearchInterceptor() + .search(request) + .pipe(map(searchResponseToArray(true))) + .pipe(scan((accumulator = [], value) => accumulator.join(value))) + .toPromise() + .catch(() => []); +}; + +const searchResponseToArray = (showAllIndices: boolean) => (response) => { + const { rawResponse } = response; + if (!rawResponse.aggregations) { + return []; + } else { + return rawResponse.aggregations.indices.buckets + .map((bucket: { key: string }) => { + return bucket.key; + }) + .filter((indexName: string) => { + if (showAllIndices) { + return true; + } else { + return !indexName.startsWith('.'); + } + }) + .map((indexName: string) => { + return { + name: indexName, + // item: {}, + }; + }); + } +}; + +const buildSearchRequest = (showAllIndices: boolean, pattern: string, dataSourceId?: string) => { + const request = { + params: { + ignoreUnavailable: true, + expand_wildcards: showAllIndices ? 'all' : 'open', + index: pattern, + body: { + size: 0, // no hits + aggs: { + indices: { + terms: { + field: '_index', + size: 100, + }, + }, + }, + }, + }, + dataSourceId, + }; + + return request; +}; diff --git a/src/plugins/data/public/ui/dataset_navigator/index.tsx b/src/plugins/data/public/ui/dataset_navigator/index.tsx index ea04b4d206d..fdded3c18a6 100644 --- a/src/plugins/data/public/ui/dataset_navigator/index.tsx +++ b/src/plugins/data/public/ui/dataset_navigator/index.tsx @@ -3,4 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { DataSetNavigator } from './dataset_navigator'; +export { DataSetNavigator, DataSetNavigatorProps, DataSetOption } from './dataset_navigator'; +export { fetchClusters } from './fetch_clusters'; +export { fetchIndexPatterns } from './fetch_index_patterns'; +export { fetchIndices } from './fetch_indices'; diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 400887e51d5..f0824978966 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -49,4 +49,4 @@ export { } from './query_editor'; export { SearchBar, SearchBarProps, StatefulSearchBarProps } from './search_bar'; export { SuggestionsComponent } from './typeahead'; -export { DataSetNavigator } from './dataset_navigator'; +export { DataSetNavigator, DataSetOption } from './dataset_navigator'; diff --git a/src/plugins/data/public/ui/query_editor/dataset_navigator.tsx b/src/plugins/data/public/ui/query_editor/dataset_navigator.tsx deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/plugins/data/public/ui/query_editor/query_editor.tsx b/src/plugins/data/public/ui/query_editor/query_editor.tsx index 98ac22470bd..c2ee341bf74 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator, PopoverAnchorPosition } from '@elastic/eui'; import classNames from 'classnames'; import { isEqual } from 'lodash'; -import React, { Component, createRef, RefObject, useCallback } from 'react'; +import React, { Component, createRef, RefObject } from 'react'; import { DataSetNavigator, Settings } from '..'; import { DataSource, IDataPluginServices, IIndexPattern, Query, TimeRange } from '../..'; import { @@ -39,7 +39,7 @@ export interface QueryEditorProps { onChange?: (query: Query, dateRange?: TimeRange) => void; onChangeQueryEditorFocus?: (isFocused: boolean) => void; onSubmit?: (query: Query, dateRange?: TimeRange) => void; - getQueryStringInitialValue?: (language: string) => string; + getQueryStringInitialValue?: (language: string, dataSetName?: string) => string; dataTestSubj?: string; size?: SuggestionsListSize; className?: string; @@ -183,6 +183,32 @@ export default class QueryEditorUI extends Component { } }; + // private onSelectDataSet = (dataSetName: string) => { + // const newQuery = { + // query: this.props.getQueryStringInitialValue?.(this.props.query.language, dataSetName) ?? '', + // language: this.props.query.language, + // }; + + // const enhancement = this.props.settings.getQueryEnhancements(newQuery.language); + // const fields = enhancement?.fields; + // const newSettings: DataSettings = { + // userQueryLanguage: newQuery.language, + // userQueryString: newQuery.query, + // ...(fields && { uiOverrides: { fields } }), + // }; + // this.props.settings?.updateSettings(newSettings); + + // const dateRangeEnhancement = enhancement?.searchBar?.dateRange; + // const dateRange = dateRangeEnhancement + // ? { + // from: dateRangeEnhancement.initialFrom!, + // to: dateRangeEnhancement.initialTo!, + // } + // : undefined; + // this.onChange(newQuery, dateRange); + // this.onSubmit(newQuery, dateRange); + // }; + // TODO: MQL consider moving language select language of setting search source here private onSelectLanguage = (language: string) => { // Send telemetry info every time the user opts in or out of kuery @@ -215,10 +241,6 @@ export default class QueryEditorUI extends Component { : undefined; this.onChange(newQuery, dateRange); this.onSubmit(newQuery, dateRange); - this.setState({ - isDataSourcesVisible: enhancement?.searchBar?.showDataSourcesSelector ?? true, - isDataSetsVisible: enhancement?.searchBar?.showDataSetsSelector ?? true, - }); }; private initPersistedLog = () => { @@ -256,10 +278,6 @@ export default class QueryEditorUI extends Component { this.initPersistedLog(); // this.fetchIndexPatterns().then(this.updateSuggestions); - this.setState({ - isDataSourcesVisible: this.initDataSourcesVisibility() || true, - isDataSetsVisible: this.initDataSetsVisibility() || true, - }); } public componentDidUpdate(prevProps: Props) { @@ -294,12 +312,6 @@ export default class QueryEditorUI extends Component { {this.props.prepend} - - - { appName={this.services.appName} /> + {this.state.isDataSetsVisible && ( + +
+ + )} 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 a7c8e0af3e5..c12b9e338f5 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 @@ -206,11 +206,11 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { ) return ''; - const defaultDataSource = indexPatterns[0]; - const dataSource = - typeof defaultDataSource === 'string' ? defaultDataSource : defaultDataSource.title; + console.log('settings ds:', settings?.getSelectedDataSet()); + const defaultDataSet = settings?.getSelectedDataSet() ?? indexPatterns[0]; + const dataSet = typeof defaultDataSet === 'string' ? defaultDataSet : defaultDataSet.title; - return input.replace('', dataSource); + return input.replace('', dataSet); } function renderQueryEditor() { diff --git a/src/plugins/data/public/ui/settings/settings.ts b/src/plugins/data/public/ui/settings/settings.ts index d7f7cd104c8..ebdde370db2 100644 --- a/src/plugins/data/public/ui/settings/settings.ts +++ b/src/plugins/data/public/ui/settings/settings.ts @@ -42,11 +42,18 @@ export class Settings { this.setSelectedDataSet(this.getSelectedDataSet()); } + /** + * @experimental - Sets the dataset BehaviorSubject + */ setSelectedDataSet = (dataSet: any) => { + console.log('dataSet in settings:', dataSet); this.storage.set('opensearchDashboards.userQueryDataSet', dataSet); this.selectedDataSet$.next(dataSet); }; + /** + * @experimental - Gets the dataset Observable + */ getSelectedDataSet$ = () => { return this.selectedDataSet$.asObservable(); }; @@ -103,8 +110,10 @@ export class Settings { } setUserQueryLanguage(language: string) { + if (language !== this.getUserQueryLanguage()) { + this.search.df.clear(); + } this.storage.set('opensearchDashboards.userQueryLanguage', language); - this.search.df.clear(); const queryEnhancement = this.queryEnhancements.get(language); this.search.__enhance({ searchInterceptor: queryEnhancement diff --git a/src/plugins/data/public/ui/types.ts b/src/plugins/data/public/ui/types.ts index afa0c813050..a5938e6aa12 100644 --- a/src/plugins/data/public/ui/types.ts +++ b/src/plugins/data/public/ui/types.ts @@ -5,6 +5,7 @@ import { Observable } from 'rxjs'; import { SearchInterceptor } from '../search'; +import { DataSetNavigatorProps } from './dataset_navigator'; import { IndexPatternSelectProps } from './index_pattern_select'; import { StatefulSearchBarProps } from './search_bar'; import { QueryEditorExtensionConfig } from './query_editor/query_editor_extensions'; @@ -66,7 +67,10 @@ export interface IUiStart { IndexPatternSelect: React.ComponentType; SearchBar: React.ComponentType; SuggestionsComponent: React.ComponentType; + /** + * @experimental - Subject to change + */ Settings: Settings; - dataSourceContainer$: Observable; + DataSetNavigator: (onDataSetSelected: any) => React.ComponentType; container$: Observable; } diff --git a/src/plugins/data/public/ui/ui_service.ts b/src/plugins/data/public/ui/ui_service.ts index 1e0e6be8b78..9d7d8455ef3 100644 --- a/src/plugins/data/public/ui/ui_service.ts +++ b/src/plugins/data/public/ui/ui_service.ts @@ -14,6 +14,7 @@ import { createSearchBar } from './search_bar/create_search_bar'; import { createSettings } from './settings'; import { SuggestionsComponent } from './typeahead'; import { IUiSetup, IUiStart, QueryEnhancement, UiEnhancements } from './types'; +import { createDataSetNavigator } from './dataset_navigator/create_dataset_navigator'; /** @internal */ // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -81,10 +82,16 @@ export class UiService implements Plugin { return { IndexPatternSelect: createIndexPatternSelect(core.savedObjects.client), + DataSetNavigator: (onSelectedDataSet) => createDataSetNavigator( + Settings, + core.savedObjects.client, + dataServices.indexPatterns, + dataServices.search, + onSelectedDataSet + ), SearchBar, SuggestionsComponent, Settings, - dataSourceContainer$: this.dataSourceContainer$, container$: this.container$, }; } diff --git a/src/plugins/data_explorer/public/components/sidebar/index.tsx b/src/plugins/data_explorer/public/components/sidebar/index.tsx index db2ac781bbf..413293ca6e4 100644 --- a/src/plugins/data_explorer/public/components/sidebar/index.tsx +++ b/src/plugins/data_explorer/public/components/sidebar/index.tsx @@ -3,30 +3,32 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import React, { ComponentType, FC, useCallback, useEffect, useRef, useState } from 'react'; import { EuiPageSideBar, EuiPortal, EuiSplitPanel } from '@elastic/eui'; import { i18n } from '@osd/i18n'; -import { DataSource, DataSourceGroup, DataSetNavigator, DataSourceSelectable } from '../../../../data/public'; +import { DataSource, DataSourceGroup, DataSourceSelectable } from '../../../../data/public'; import { DataSourceOption } from '../../../../data/public/'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { DataExplorerServices } from '../../types'; import { setIndexPattern, - setDataset, + setDataSet, useTypedDispatch, useTypedSelector, } from '../../utils/state_management'; import './index.scss'; +import { DataSetNavigatorProps } from '../../../../data/public/ui/dataset_navigator'; +import { batch } from 'react-redux'; export const Sidebar: FC = ({ children }) => { const { indexPattern: indexPatternId } = useTypedSelector((state) => state.metadata); const dispatch = useTypedDispatch(); const [selectedSources, setSelectedSources] = useState([]); - const [selectedCluster, setSelectedCluster] = useState(); - const [indexPatternOptionList, setIndexPatternOptionList] = useState([]); const [dataSourceOptionList, setDataSourceOptionList] = useState([]); const [activeDataSources, setActiveDataSources] = useState([]); const [isEnhancementsEnabled, setIsEnhancementsEnabled] = useState(false); + const [DataSetNavigator, setDataSetNavigator] = useState(); + const [selectedDataSet, setSelectedDataSet] = useState(); const containerRef = useRef(null); const { @@ -37,17 +39,40 @@ export const Sidebar: FC = ({ children }) => { }, } = useOpenSearchDashboards(); + const handleDataSetSelection = useCallback( + (selectedDataSet: any) => { + setSelectedDataSet(selectedDataSet); + setSelectedSources([ + { + key: selectedDataSet.id, + name: selectedDataSet.name, + label: selectedDataSet.name, + value: selectedDataSet.id, + type: 'OpenSearch Default', + ds: activeDataSources[0], + }, + ]); + batch(() => { + dispatch(setIndexPattern(selectedDataSet.id)); + dispatch(setDataSet(selectedDataSet)); + }); + }, + [dispatch] + ); + useEffect(() => { const subscriptions = ui.Settings.getEnabledQueryEnhancementsUpdated$().subscribe( (enabledQueryEnhancements) => { setIsEnhancementsEnabled(enabledQueryEnhancements); + if (enabledQueryEnhancements) + setDataSetNavigator(ui.DataSetNavigator(handleDataSetSelection)); } ); return () => { subscriptions.unsubscribe(); }; - }, [ui.Settings]); + }, [ui.Settings, handleDataSetSelection]); const setContainerRef = useCallback((uiContainerRef) => { uiContainerRef.appendChild(containerRef.current); @@ -65,13 +90,7 @@ export const Sidebar: FC = ({ children }) => { return () => { subscriptions.unsubscribe(); }; - }, [ - ui.container$, - containerRef, - setContainerRef, - ui.dataSourceContainer$, - isEnhancementsEnabled, - ]); + }, [ui.container$, containerRef, setContainerRef, isEnhancementsEnabled]); useEffect(() => { let isMounted = true; @@ -100,21 +119,10 @@ export const Sidebar: FC = ({ children }) => { useEffect(() => { if (indexPatternId) { const option = getMatchedOption(dataSourceOptionList, indexPatternId); - setSelectedSources((prev) => option ? [option] : prev); + setSelectedSources(option ? [option] : []); } }, [indexPatternId, activeDataSources, dataSourceOptionList]); - const getMatchedIndexPattern = (indexPatternList: DataSourceOption[], ipId: string) => { - return indexPatternList.find((indexPattern) => indexPattern.value === ipId); - }; - - useEffect(() => { - if (indexPatternId) { - const option = getMatchedIndexPattern(indexPatternOptionList, indexPatternId); - setSelectedSources((prev) => option ? [option] : prev); - } - }, [indexPatternId, activeDataSources, indexPatternOptionList]); - const redirectToLogExplorer = useCallback( (dsName: string, dsType: string) => { return application.navigateToUrl( @@ -130,14 +138,14 @@ export const Sidebar: FC = ({ children }) => { setSelectedSources(selectedDataSources); return; } + // Temporary redirection solution for 2.11, where clicking non-index-pattern data sources + // will prompt users with modal explaining they are being redirected to Observability log explorer + if (selectedDataSources[0]?.ds?.getType() !== 'DEFAULT_INDEX_PATTERNS') { + redirectToLogExplorer(selectedDataSources[0].label, selectedDataSources[0].type); + return; + } setSelectedSources(selectedDataSources); dispatch(setIndexPattern(selectedDataSources[0].value)); - // dispatch( - // setDataset({ - // id: selectedDataSources[0].value, - // datasource: { ref: selectedDataSources[0]?.ds?.getId() }, - // }) - // ); }, [dispatch, redirectToLogExplorer, setSelectedSources] ); @@ -179,6 +187,15 @@ export const Sidebar: FC = ({ children }) => { borderRadius="none" color="transparent" > + {isEnhancementsEnabled && ( + { + containerRef.current = node; + }} + > + {DataSetNavigator} + + )} {!isEnhancementsEnabled && ( ) => { state.indexPattern = action.payload; }, - setDataset: (state, action: PayloadAction) => { + setDataSet: (state, action: PayloadAction) => { state.dataset = action.payload; }, setOriginatingApp: (state, action: PayloadAction) => { @@ -75,4 +75,4 @@ export const slice = createSlice({ }); export const { reducer } = slice; -export const { setIndexPattern, setDataset, setOriginatingApp, setView, setState } = slice.actions; +export const { setIndexPattern, setDataSet, setOriginatingApp, setView, setState } = slice.actions; 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 3683d7d201c..9d320de4b54 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,4 @@ export type RenderState = Omit; // Remaining state after export type Store = ReturnType; export type AppDispatch = Store['dispatch']; -export { MetadataState, setIndexPattern, setDataset, setOriginatingApp } from './metadata_slice'; +export { MetadataState, setIndexPattern, setDataSet, setOriginatingApp } from './metadata_slice'; diff --git a/src/plugins/discover/public/application/helpers/get_data_set.ts b/src/plugins/discover/public/application/helpers/get_data_set.ts index b0431ac31c1..762274f5bf0 100644 --- a/src/plugins/discover/public/application/helpers/get_data_set.ts +++ b/src/plugins/discover/public/application/helpers/get_data_set.ts @@ -3,23 +3,26 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { IndexPattern, IndexPatternsContract } from '../../../../data/public'; +import { DataSetOption, IndexPattern, IndexPatternsContract } from '../../../../data/public'; import { SearchData } from '../view_components/utils/use_search'; function getDataSet( - indexPattern: IndexPattern | undefined, + dataSet: IndexPattern | DataSetOption | undefined, state: SearchData, indexPatternsService: IndexPatternsContract ) { - if (!indexPattern) { + if (!dataSet) { return; } - return ( - (state.title && - state.title !== indexPattern?.title && - indexPatternsService.getByTitle(state.title!, true)) || - indexPattern - ); + if (dataSet instanceof IndexPattern) { + return ( + (state.title && + state.title !== dataSet?.title && + indexPatternsService.getByTitle(state.title!, true)) || + dataSet + ); + } + return dataSet; } export { getDataSet }; diff --git a/src/plugins/discover/public/application/view_components/utils/use_index_pattern.ts b/src/plugins/discover/public/application/view_components/utils/use_index_pattern.ts index 74f5165cf42..05b7cc648ec 100644 --- a/src/plugins/discover/public/application/view_components/utils/use_index_pattern.ts +++ b/src/plugins/discover/public/application/view_components/utils/use_index_pattern.ts @@ -59,16 +59,6 @@ export const useIndexPattern = (services: DiscoverViewServices) => { }); }; - data.ui.Settings.getSelectedDataSet$().subscribe((dataSet) => { - if (dataSet) { - batch(() => { - store!.dispatch(updateDataSet(dataSet)); - store!.dispatch(updateIndexPattern(dataSet.id)); - }); - fetchIndexPatternDetails(dataSet.id); - } - }); - if (!indexPatternIdFromState) { data.indexPatterns.getCache().then((indexPatternList) => { const newId = getIndexPatternId('', indexPatternList, config.get('defaultIndex')); @@ -82,14 +72,7 @@ export const useIndexPattern = (services: DiscoverViewServices) => { return () => { isMounted = false; }; - }, [ - indexPatternIdFromState, - data.indexPatterns, - toastNotifications, - config, - store, - data.ui.Settings, - ]); + }, [indexPatternIdFromState, data.indexPatterns, toastNotifications, config, store]); return indexPattern; }; diff --git a/src/plugins/query_enhancements/public/data_source_connection/components/connections_bar.tsx b/src/plugins/query_enhancements/public/data_source_connection/components/connections_bar.tsx deleted file mode 100644 index 196cb5eb401..00000000000 --- a/src/plugins/query_enhancements/public/data_source_connection/components/connections_bar.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useEffect, useRef, useState } from 'react'; -import { EuiPortal } from '@elastic/eui'; -import { distinctUntilChanged } from 'rxjs/operators'; -import { ToastsSetup } from 'opensearch-dashboards/public'; -import { DataPublicPluginStart, QueryEditorExtensionDependencies } from '../../../../data/public'; -import { ConnectionsService } from '../services'; - -interface ConnectionsProps { - dependencies: QueryEditorExtensionDependencies; - toasts: ToastsSetup; - connectionsService: ConnectionsService; -} - -export const ConnectionsBar: React.FC = ({ connectionsService, toasts }) => { - const [isDataSourceEnabled, setIsDataSourceEnabled] = useState(false); - const [uiService, setUiService] = useState(undefined); - const containerRef = useRef(null); - - useEffect(() => { - const uiServiceSubscription = connectionsService.getUiService().subscribe(setUiService); - const dataSourceEnabledSubscription = connectionsService - .getIsDataSourceEnabled$() - .subscribe(setIsDataSourceEnabled); - - return () => { - uiServiceSubscription.unsubscribe(); - dataSourceEnabledSubscription.unsubscribe(); - }; - }, [connectionsService]); - - useEffect(() => { - if (!uiService || !isDataSourceEnabled || !containerRef.current) return; - const subscriptions = uiService.dataSourceContainer$.subscribe((container) => { - if (container && containerRef.current) { - container.append(containerRef.current); - } - }); - - return () => subscriptions.unsubscribe(); - }, [uiService, isDataSourceEnabled]); - - useEffect(() => { - const selectedConnectionSubscription = connectionsService - .getSelectedConnection$() - .pipe(distinctUntilChanged()) - .subscribe((connection) => { - if (connection) { - // Assuming setSelectedConnection$ is meant to update some state or perform an action outside this component - connectionsService.setSelectedConnection$(connection); - } - }); - - return () => selectedConnectionSubscription.unsubscribe(); - }, [connectionsService]); - - const handleSelectedConnection = (id: string | undefined) => { - if (!id) { - connectionsService.setSelectedConnection$(undefined); - return; - } - connectionsService.getConnectionById(id).subscribe((connection) => { - connectionsService.setSelectedConnection$(connection); - }); - }; - - return ( - { - containerRef.current = node; - }} - > -
- - ); -}; diff --git a/src/plugins/query_enhancements/public/data_source_connection/components/index.ts b/src/plugins/query_enhancements/public/data_source_connection/components/index.ts deleted file mode 100644 index 1ee969a1d07..00000000000 --- a/src/plugins/query_enhancements/public/data_source_connection/components/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export { ConnectionsBar } from './connections_bar'; diff --git a/src/plugins/query_enhancements/public/data_source_connection/index.ts b/src/plugins/query_enhancements/public/data_source_connection/index.ts deleted file mode 100644 index e334163d91d..00000000000 --- a/src/plugins/query_enhancements/public/data_source_connection/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export { createDataSourceConnectionExtension } from './utils'; -export * from './services'; diff --git a/src/plugins/query_enhancements/public/data_source_connection/services/index.ts b/src/plugins/query_enhancements/public/data_source_connection/services/index.ts deleted file mode 100644 index 08eeda5a7aa..00000000000 --- a/src/plugins/query_enhancements/public/data_source_connection/services/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export { ConnectionsService } from './connections_service'; diff --git a/src/plugins/query_enhancements/public/data_source_connection/utils/create_extension.tsx b/src/plugins/query_enhancements/public/data_source_connection/utils/create_extension.tsx deleted file mode 100644 index 5b3a603103b..00000000000 --- a/src/plugins/query_enhancements/public/data_source_connection/utils/create_extension.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { ToastsSetup } from 'opensearch-dashboards/public'; -import { QueryEditorExtensionConfig } from '../../../../data/public'; -import { ConfigSchema } from '../../../common/config'; -import { ConnectionsBar } from '../components'; -import { ConnectionsService } from '../services'; -import { of } from 'rxjs'; - -export const createDataSourceConnectionExtension = ( - connectionsService: ConnectionsService, - toasts: ToastsSetup, - config: ConfigSchema -): QueryEditorExtensionConfig => { - return { - id: 'data-source-connection', - order: 2000, - isEnabled$: (dependencies) => { - return of(false); - }, - getComponent: (dependencies) => { - return ( - - ); - }, - }; -}; diff --git a/src/plugins/query_enhancements/public/data_source_connection/utils/index.ts b/src/plugins/query_enhancements/public/data_source_connection/utils/index.ts deleted file mode 100644 index 9eccc9e6f35..00000000000 --- a/src/plugins/query_enhancements/public/data_source_connection/utils/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export * from './create_extension'; diff --git a/src/plugins/query_enhancements/public/plugin.tsx b/src/plugins/query_enhancements/public/plugin.tsx index 29a83e1c4c6..9baec694f1a 100644 --- a/src/plugins/query_enhancements/public/plugin.tsx +++ b/src/plugins/query_enhancements/public/plugin.tsx @@ -7,10 +7,9 @@ import moment from 'moment'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '../../../core/public'; import { IStorageWrapper, Storage } from '../../opensearch_dashboards_utils/public'; import { ConfigSchema } from '../common/config'; -import { ConnectionsService, createDataSourceConnectionExtension } from './data_source_connection'; +import { ConnectionsService, setData, setStorage } from './services'; import { createQueryAssistExtension } from './query_assist'; import { PPLSearchInterceptor, SQLAsyncSearchInterceptor, SQLSearchInterceptor } from './search'; -import { setData, setStorage } from './services'; import { QueryEnhancementsPluginSetup, QueryEnhancementsPluginSetupDependencies, @@ -155,16 +154,6 @@ export class QueryEnhancementsPlugin }, }); - data.__enhance({ - ui: { - queryEditorExtension: createDataSourceConnectionExtension( - this.connectionsService, - core.notifications.toasts, - this.config - ), - }, - }); - return {}; } diff --git a/src/plugins/query_enhancements/public/services.ts b/src/plugins/query_enhancements/public/services.ts deleted file mode 100644 index d11233be2dc..00000000000 --- a/src/plugins/query_enhancements/public/services.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createGetterSetter } from '../../opensearch_dashboards_utils/common'; -import { IStorageWrapper } from '../../opensearch_dashboards_utils/public'; -import { DataPublicPluginStart } from '../../data/public'; - -export const [getStorage, setStorage] = createGetterSetter('storage'); -export const [getData, setData] = createGetterSetter('data'); diff --git a/src/plugins/query_enhancements/public/data_source_connection/services/connections_service.ts b/src/plugins/query_enhancements/public/services/connections_service.ts similarity index 95% rename from src/plugins/query_enhancements/public/data_source_connection/services/connections_service.ts rename to src/plugins/query_enhancements/public/services/connections_service.ts index 6afec4b51a9..97a59c2cd94 100644 --- a/src/plugins/query_enhancements/public/data_source_connection/services/connections_service.ts +++ b/src/plugins/query_enhancements/public/services/connections_service.ts @@ -6,8 +6,8 @@ import { BehaviorSubject, Observable, from } from 'rxjs'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { CoreStart } from 'opensearch-dashboards/public'; -import { API } from '../../../common'; -import { Connection, ConnectionsServiceDeps } from '../../types'; +import { API } from '../../common'; +import { Connection, ConnectionsServiceDeps } from '../types'; export class ConnectionsService { protected http!: ConnectionsServiceDeps['http']; diff --git a/src/plugins/query_enhancements/public/services/index.ts b/src/plugins/query_enhancements/public/services/index.ts new file mode 100644 index 00000000000..bb0284408fa --- /dev/null +++ b/src/plugins/query_enhancements/public/services/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createGetterSetter } from '../../../opensearch_dashboards_utils/common'; +import { IStorageWrapper } from '../../../opensearch_dashboards_utils/public'; +import { DataPublicPluginStart } from '../../../data/public'; + +export const [getStorage, setStorage] = createGetterSetter('storage'); +export const [getData, setData] = createGetterSetter('data'); + +export { ConnectionsService } from './connections_service';