diff --git a/src/plugins/data/public/ui/query_editor/dataset_navigator.tsx b/src/plugins/data/public/ui/query_editor/dataset_navigator.tsx index a850c1690e3b..ca0617d7c35c 100644 --- a/src/plugins/data/public/ui/query_editor/dataset_navigator.tsx +++ b/src/plugins/data/public/ui/query_editor/dataset_navigator.tsx @@ -2,3 +2,254 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ + +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 { 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: { + rawResponse: any; +}) => { + 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; +}; + +const getIndices = 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(() => []); +}; + +interface DataSetOption { + id: string; + name: string; + dataSourceRef?: string; +} + +interface DataSetNavigatorProps { + savedObjectsClient: SavedObjectsClientContract; + indexPatterns: Array; +} + +export const DataSetNavigator = ({ savedObjectsClient, indexPatterns }: DataSetNavigatorProps) => { + 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 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); + closePopover(); + }; + + useEffect(() => { + const subscription = uiService.Settings.getSelectedDataSet$().subscribe((dataSet) => { + if (dataSet) { + setSelectedDataSet(dataSet); + } + }); + 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 : []); + }); + }, [savedObjectsClient]); + + useEffect(() => { + if (selectedCluster) { + // Get all indexes + getIndices(search, selectedCluster.id).then((res) => { + setIndexList( + res.map((index: { name: string }) => ({ + name: index.name, + id: index.name, + dataSourceRef: selectedCluster.id, + })) + ); + }); + } + }, [search, selectedCluster, setIndexList]); + + const dataSetButton = ( + + {selectedDataSet ? selectedDataSet.name : 'Datasets'} + + ); + + 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', + }, + ]} + /> + + ); +}; 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 98ac22470bdb..de69532dafbe 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 { diff --git a/src/plugins/query_enhancements/public/query_assist/components/query_assist_bar.tsx b/src/plugins/query_enhancements/public/query_assist/components/query_assist_bar.tsx index 84ac854c980f..e37afcc4fab0 100644 --- a/src/plugins/query_enhancements/public/query_assist/components/query_assist_bar.tsx +++ b/src/plugins/query_enhancements/public/query_assist/components/query_assist_bar.tsx @@ -12,7 +12,7 @@ import { } from '../../../../data/public'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { QueryAssistParameters } from '../../../common/query_assist'; -import { ConnectionsService } from '../../data_source_connection'; +import { ConnectionsService } from '../../services'; import { getStorage } from '../../services'; import { useGenerateQuery } from '../hooks'; import { getPersistedLog, ProhibitedQueryError } from '../utils'; diff --git a/src/plugins/query_enhancements/public/query_assist/utils/create_extension.test.tsx b/src/plugins/query_enhancements/public/query_assist/utils/create_extension.test.tsx index ea568959152d..0b797025ab17 100644 --- a/src/plugins/query_enhancements/public/query_assist/utils/create_extension.test.tsx +++ b/src/plugins/query_enhancements/public/query_assist/utils/create_extension.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { coreMock } from '../../../../../core/public/mocks'; import { IIndexPattern } from '../../../../data/public'; import { ConfigSchema } from '../../../common/config'; -import { ConnectionsService } from '../../data_source_connection'; +import { ConnectionsService } from '../../services'; import { Connection } from '../../types'; import { createQueryAssistExtension } from './create_extension'; diff --git a/src/plugins/query_enhancements/public/query_assist/utils/create_extension.tsx b/src/plugins/query_enhancements/public/query_assist/utils/create_extension.tsx index bd990b57f5a7..7971b0822d3a 100644 --- a/src/plugins/query_enhancements/public/query_assist/utils/create_extension.tsx +++ b/src/plugins/query_enhancements/public/query_assist/utils/create_extension.tsx @@ -13,7 +13,7 @@ import { } from '../../../../data/public'; import { API } from '../../../common'; import { ConfigSchema } from '../../../common/config'; -import { ConnectionsService } from '../../data_source_connection'; +import { ConnectionsService } from '../../services'; import { QueryAssistBar, QueryAssistBanner } from '../components'; /** diff --git a/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts b/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts index aac50de3bb98..b169aed40d78 100644 --- a/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts +++ b/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts @@ -33,7 +33,7 @@ import { fetchDataFrame, } from '../../common'; import { QueryEnhancementsPluginStartDependencies } from '../types'; -import { ConnectionsService } from '../data_source_connection'; +import { ConnectionsService } from '../services'; export class PPLSearchInterceptor extends SearchInterceptor { protected queryService!: DataPublicPluginStart['query']; diff --git a/src/plugins/query_enhancements/public/search/sql_async_search_interceptor.ts b/src/plugins/query_enhancements/public/search/sql_async_search_interceptor.ts index 9232ef146cdb..4dafdab49628 100644 --- a/src/plugins/query_enhancements/public/search/sql_async_search_interceptor.ts +++ b/src/plugins/query_enhancements/public/search/sql_async_search_interceptor.ts @@ -26,7 +26,7 @@ import { fetchDataFramePolling, } from '../../common'; import { QueryEnhancementsPluginStartDependencies } from '../types'; -import { ConnectionsService } from '../data_source_connection'; +import { ConnectionsService } from '../services'; export class SQLAsyncSearchInterceptor extends SearchInterceptor { protected queryService!: DataPublicPluginStart['query'];