diff --git a/CHANGELOG.md b/CHANGELOG.md index 92bffd4dd271..a85951d8a5eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -667,7 +667,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 📈 Features/Enhancements -- Add datasource selector to discover ([#5167](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5167)) +- Add DataSource service and DataSourceSelector for multiple datasource support ([#5167](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5167)) - Add extension point in saved object management to register namespaces and show filter ([#2656](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2656)) - Add updated_at column to Saved Objects' tables ([#1218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1218)) - Change the links in the visualize plugin to use `href` rather than `onClick` ([#2395](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2395)) diff --git a/src/plugins/data/public/data_sources/datasource/datasource.ts b/src/plugins/data/public/data_sources/datasource/datasource.ts index 8fe720b1cee0..c0257a94ddd3 100644 --- a/src/plugins/data/public/data_sources/datasource/datasource.ts +++ b/src/plugins/data/public/data_sources/datasource/datasource.ts @@ -13,14 +13,21 @@ * DataSourceQueryResult: Represents the result from querying the data source. */ -import { ConnectionStatus } from './types'; +import { + ConnectionStatus, + IDataSetParams, + IDataSourceMetaData, + IDataSourceQueryParams, + IDataSourceQueryResult, + ISourceDataSet, +} from './types'; export abstract class DataSource< - DataSourceMetaData, - DataSetParams, - SourceDataSet, - DataSourceQueryParams, - DataSourceQueryResult + DataSourceMetaData extends IDataSourceMetaData = IDataSourceMetaData, + DataSetParams extends IDataSetParams = IDataSetParams, + SourceDataSet extends ISourceDataSet = ISourceDataSet, + DataSourceQueryParams extends IDataSourceQueryParams = IDataSourceQueryParams, + DataSourceQueryResult extends IDataSourceQueryResult = IDataSourceQueryResult > { constructor( private readonly name: string, diff --git a/src/plugins/data/public/data_sources/datasource/factory.ts b/src/plugins/data/public/data_sources/datasource/factory.ts index c9ed8d91e041..244d89f82d3f 100644 --- a/src/plugins/data/public/data_sources/datasource/factory.ts +++ b/src/plugins/data/public/data_sources/datasource/factory.ts @@ -3,19 +3,25 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { DataSource } from '.'; +import { DataSourceType } from '../datasource_services/types'; + /** * The DataSourceFactory is responsible for managing the registration and creation of data source classes. * It serves as a registry for different data source types and provides a way to instantiate them. */ -import { DataSourceType } from '../datasource_services'; - export class DataSourceFactory { // Holds the singleton instance of the DataSourceFactory. private static factory: DataSourceFactory; // A dictionary holding the data source type as the key and its corresponding class constructor as the value. - private dataSourceClasses: { [type: string]: new (config: any) => DataSourceType } = {}; + private dataSources: { + [key: string]: { + dataSourceClass: new (config: any) => DataSource; + dataSourceType: DataSourceType; + }; + } = {}; /** * Private constructor to ensure only one instance of DataSourceFactory is created. @@ -38,31 +44,37 @@ export class DataSourceFactory { * Registers a new data source type with its associated class. * If the type has already been registered, an error is thrown. * - * @param {string} type - The identifier for the data source type. - * @param {new (config: any) => DataSourceType} dataSourceClass - The constructor of the data source class. + * @param {DataSourceType} type - The identifier for the data source type. + * @param {new (config: any) => DataSource} dataSourceClass - The constructor of the data source class. * @throws {Error} Throws an error if the data source type has already been registered. */ - registerDataSourceType(type: string, dataSourceClass: new (config: any) => DataSourceType): void { - if (this.dataSourceClasses[type]) { + registerDataSourceType( + dataSourceType: DataSourceType, + dataSourceClass: new (config: any) => DataSource + ): void { + if (this.dataSources[dataSourceType.key]) { throw new Error('This data source type has already been registered'); } - this.dataSourceClasses[type] = dataSourceClass; + this.dataSources[dataSourceType.key] = { + dataSourceClass, + dataSourceType, + }; } /** * Creates and returns an instance of the specified data source type with the given configuration. * If the type hasn't been registered, an error is thrown. * - * @param {string} type - The identifier for the data source type. + * @param {string} key - The identifier for the data source type. * @param {any} config - The configuration for the data source instance. - * @returns {DataSourceType} An instance of the specified data source type. + * @returns {DataSource} An instance of the specified data source type. * @throws {Error} Throws an error if the data source type is not supported. */ - getDataSourceInstance(type: string, config: any): DataSourceType { - const DataSourceClass = this.dataSourceClasses[type]; - if (!DataSourceClass) { + getDataSourceInstance(key: string, config: any): DataSource { + const dataSource = this.dataSources[key]; + if (!dataSource) { throw new Error('Unsupported data source type'); } - return new DataSourceClass(config); + return new dataSource.dataSourceClass(config); } } diff --git a/src/plugins/data/public/data_sources/datasource/types.ts b/src/plugins/data/public/data_sources/datasource/types.ts index 26e5023c2a3d..6fe12768f8cc 100644 --- a/src/plugins/data/public/data_sources/datasource/types.ts +++ b/src/plugins/data/public/data_sources/datasource/types.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { DataSource } from '.'; import { IndexPattern } from '../../index_patterns'; -import { DataSourceType } from '../datasource_services'; export interface IDataSourceMetaData { name: string; @@ -15,7 +15,7 @@ export interface IDataSourceGroup { } export interface ISourceDataSet { - ds: DataSourceType; + ds: DataSource; data_sets: string[] | IndexPattern; } diff --git a/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.test.tsx b/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.test.tsx index 9205f4652be2..015cf364bbbd 100644 --- a/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.test.tsx +++ b/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, act } from '@testing-library/react'; import { DataSourceSelectable } from './datasource_selectable'; -import { DataSourceType } from '../datasource_services'; +import { DataSource } from '../datasource_services'; describe('DataSourceSelectable', () => { let dataSourcesMock; @@ -22,7 +22,7 @@ describe('DataSourceSelectable', () => { getDataSet: jest.fn().mockResolvedValue([]), getType: jest.fn().mockReturnValue('DEFAULT_INDEX_PATTERNS'), getName: jest.fn().mockReturnValue('SomeName'), - } as unknown) as DataSourceType, + } as unknown) as DataSource, ]; dataSourceOptionListMock = []; diff --git a/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx b/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx index bbc679809d0c..ef85264111d4 100644 --- a/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx +++ b/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx @@ -5,10 +5,10 @@ import React, { useEffect } from 'react'; import { DataSourceSelector } from './datasource_selector'; -import { DataSourceType } from '../datasource_services'; import { DataSourceGroup, DataSourceOption, DataSourceSelectableProps } from './types'; import { ISourceDataSet } from '../datasource/types'; import { IndexPattern } from '../../index_patterns'; +import { DataSource } from '../..'; // Mapping between datasource type and its display name. const DATASOURCE_TYPE_DISPLAY_NAME_MAP = { @@ -31,7 +31,7 @@ export const DataSourceSelectable = ({ // This effect fetches datasets and prepares the datasource list for UI rendering. useEffect(() => { // Fetches datasets for a given datasource and returns it along with the source. - const fetchDataSetWithSource = async (ds: DataSourceType): Promise => { + const fetchDataSetWithSource = async (ds: DataSource): Promise => { const dataSet = await ds.getDataSet(); return { ds, @@ -40,13 +40,13 @@ export const DataSourceSelectable = ({ }; // Map through all data sources and fetch their respective datasets. - const fetchDataSets = () => dataSources.map((ds: DataSourceType) => fetchDataSetWithSource(ds)); + const fetchDataSets = () => dataSources.map((ds: DataSource) => fetchDataSetWithSource(ds)); const isIndexPatterns = (dataset: string | IndexPattern) => dataset.attributes?.title && dataset.id; // Get the option format for the combo box from the dataSource and dataSet. - const getSourceOptions = (dataSource: DataSourceType, dataSet: DataSetType) => { + const getSourceOptions = (dataSource: DataSource, dataSet: DataSetType) => { const optionContent = { type: dataSource.getType(), name: dataSource.getName(), 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 a919625cade6..48f7c6cc691a 100644 --- a/src/plugins/data/public/data_sources/datasource_selector/types.ts +++ b/src/plugins/data/public/data_sources/datasource_selector/types.ts @@ -4,7 +4,7 @@ */ import { OuiComboBoxOptionOption } from '@elastic/eui'; -import { DataSourceType } from '../datasource_services'; +import { DataSource } from '../..'; export interface DataSourceGroup { label: string; @@ -19,7 +19,7 @@ export interface DataSourceOption { export type DataSourceOptionType = OuiComboBoxOptionOption; export interface DataSourceSelectableProps { - dataSources: DataSourceType[]; + dataSources: DataSource[]; dataSourceOptionList: DataSourceGroup[]; selectedSources: DataSourceOption[]; onDataSourceSelect: (dataSourceOption: DataSourceOption[]) => void; diff --git a/src/plugins/data/public/data_sources/datasource_services/datasource_service.ts b/src/plugins/data/public/data_sources/datasource_services/datasource_service.ts index 83d8cbc7aa13..23a80d770dc7 100644 --- a/src/plugins/data/public/data_sources/datasource_services/datasource_service.ts +++ b/src/plugins/data/public/data_sources/datasource_services/datasource_service.ts @@ -3,35 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { forEach, isEmpty } from 'lodash'; import { BehaviorSubject } from 'rxjs'; -import { isEmpty, forEach } from 'lodash'; -import { - DataSource, - IDataSetParams, - IDataSourceMetaData, - IDataSourceQueryParams, - IDataSourceQueryResult, - ISourceDataSet, -} from '../datasource'; +import { DataSource } from '../datasource'; import { + DataSourceRegistrationError, IDataSourceFilters, - IDataSourceRegisterationResult, - DataSourceRegisterationError, + IDataSourceRegistrationResult, } from './types'; -export type DataSourceType = DataSource< - IDataSourceMetaData, - IDataSetParams, - ISourceDataSet, - IDataSourceQueryParams, - IDataSourceQueryResult ->; - export class DataSourceService { private static dataSourceService: DataSourceService; // A record to store all registered data sources, using the data source name as the key. - private dataSources: Record = {}; - private _dataSourcesSubject: BehaviorSubject>; + private dataSources: Record = {}; + private _dataSourcesSubject: BehaviorSubject>; private constructor() { this._dataSourcesSubject = new BehaviorSubject(this.dataSources); @@ -51,8 +36,8 @@ export class DataSourceService { * @returns An array of registration results, one for each data source. */ async registerMultipleDataSources( - datasources: DataSourceType[] - ): Promise { + datasources: DataSource[] + ): Promise { return Promise.all(datasources.map((ds) => this.registerDataSource(ds))); } @@ -62,12 +47,12 @@ export class DataSourceService { * * @param ds - The data source to be registered. * @returns A registration result indicating success or failure. - * @throws {DataSourceRegisterationError} Throws an error if a data source with the same name already exists. + * @throws {DataSourceRegistrationError} Throws an error if a data source with the same name already exists. */ - async registerDataSource(ds: DataSourceType): Promise { + async registerDataSource(ds: DataSource): Promise { const dsName = ds.getName(); if (dsName in this.dataSources) { - throw new DataSourceRegisterationError( + throw new DataSourceRegistrationError( `Unable to register datasource ${dsName}, error: datasource name exists.` ); } else { @@ -76,7 +61,7 @@ export class DataSourceService { [dsName]: ds, }; this._dataSourcesSubject.next(this.dataSources); - return { success: true, info: '' } as IDataSourceRegisterationResult; + return { success: true, info: '' } as IDataSourceRegistrationResult; } } @@ -91,9 +76,9 @@ export class DataSourceService { * @param filters - An optional object with filter criteria (e.g., names of data sources). * @returns A record of filtered data sources. */ - getDataSources(filters?: IDataSourceFilters): Record { + getDataSources(filters?: IDataSourceFilters): Record { if (!filters || isEmpty(filters.names)) return this.dataSources; - const filteredDataSources: Record = {}; + const filteredDataSources: Record = {}; forEach(filters.names, (dsName) => { if (dsName in this.dataSources) { filteredDataSources[dsName] = this.dataSources[dsName]; diff --git a/src/plugins/data/public/data_sources/datasource_services/index.ts b/src/plugins/data/public/data_sources/datasource_services/index.ts index fdd07ba9dcc9..87a911d20972 100644 --- a/src/plugins/data/public/data_sources/datasource_services/index.ts +++ b/src/plugins/data/public/data_sources/datasource_services/index.ts @@ -6,7 +6,6 @@ export { DataSourceService } from './datasource_service'; export { IDataSourceFilters, - IDataSourceRegisterationResult, - DataSourceRegisterationError, - DataSourceType, + IDataSourceRegistrationResult as IDataSourceRegisterationResult, + DataSourceRegistrationError as DataSourceRegisterationError, } from './types'; diff --git a/src/plugins/data/public/data_sources/datasource_services/types.ts b/src/plugins/data/public/data_sources/datasource_services/types.ts index b0f4bf7d80d9..b51111b338e2 100644 --- a/src/plugins/data/public/data_sources/datasource_services/types.ts +++ b/src/plugins/data/public/data_sources/datasource_services/types.ts @@ -3,27 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - DataSource, - DataSourceFactory, - IDataSetParams, - IDataSourceMetaData, - IDataSourceQueryParams, - IDataSourceQueryResult, - ISourceDataSet, -} from '../datasource'; +import { DataSourceFactory } from '../datasource'; import { DataSourceService } from './datasource_service'; export interface IDataSourceFilters { names: string[]; } -export interface IDataSourceRegisterationResult { +export interface IDataSourceRegistrationResult { success: boolean; info: string; } -export class DataSourceRegisterationError extends Error { +export class DataSourceRegistrationError extends Error { success: boolean; info: string; constructor(message: string) { @@ -33,15 +25,14 @@ export class DataSourceRegisterationError extends Error { } } +export interface DataSourceType { + key: string; + label: string; + // name used in backend that will not be displayed on UI + backendName: string; +} + export interface DataSourceStart { dataSourceService: DataSourceService; dataSourceFactory: DataSourceFactory; } - -export type DataSourceType = DataSource< - IDataSourceMetaData, - IDataSetParams, - ISourceDataSet, - IDataSourceQueryParams, - IDataSourceQueryResult ->; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 897695d85efd..c7c83132fb1b 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -511,7 +511,6 @@ export { export { DataSourceRegisterationError, DataSourceService, - DataSourceType, IDataSourceFilters, IDataSourceRegisterationResult, } from './data_sources/datasource_services'; diff --git a/src/plugins/data_explorer/public/components/sidebar/index.tsx b/src/plugins/data_explorer/public/components/sidebar/index.tsx index 2c01bf084c56..7cffde5bd35d 100644 --- a/src/plugins/data_explorer/public/components/sidebar/index.tsx +++ b/src/plugins/data_explorer/public/components/sidebar/index.tsx @@ -8,7 +8,7 @@ import { EuiSplitPanel, EuiPageSideBar } from '@elastic/eui'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { DataExplorerServices } from '../../types'; import { useTypedDispatch, useTypedSelector, setIndexPattern } from '../../utils/state_management'; -import { DataSourceGroup, DataSourceSelectable, DataSourceType } from '../../../../data/public'; +import { DataSourceGroup, DataSourceSelectable, DataSource } from '../../../../data/public'; import { DataSourceOption } from '../../../../data/public/'; export const Sidebar: FC = ({ children }) => { @@ -16,7 +16,7 @@ export const Sidebar: FC = ({ children }) => { const dispatch = useTypedDispatch(); const [selectedSources, setSelectedSources] = useState([]); const [dataSourceOptionList, setDataSourceOptionList] = useState([]); - const [activeDataSources, setActiveDataSources] = useState([]); + const [activeDataSources, setActiveDataSources] = useState([]); const { services: { @@ -58,7 +58,7 @@ export const Sidebar: FC = ({ children }) => { }, [indexPatternId, activeDataSources, dataSourceOptionList]); const handleSourceSelection = (selectedDataSources: DataSourceOption[]) => { - // Temperary redirection solution for 2.11, where clicking non-index-pattern datasource + // Temporary redirection solution for 2.11, where clicking non-index-pattern datasource // will redirect user to Observability event explorer if (selectedDataSources[0].ds?.getType() !== 'DEFAULT_INDEX_PATTERNS') { return application.navigateToUrl( diff --git a/src/plugins/discover/common/index.ts b/src/plugins/discover/common/index.ts index fe7353217fef..3b330769f649 100644 --- a/src/plugins/discover/common/index.ts +++ b/src/plugins/discover/common/index.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '@osd/i18n'; + export const PLUGIN_ID = 'discover'; export const DEFAULT_COLUMNS_SETTING = 'defaultColumns'; export const SAMPLE_SIZE_SETTING = 'discover:sampleSize'; @@ -15,6 +17,10 @@ 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 DEFAULT_DATASOURCE_TYPE = 'DEFAULT_INDEX_PATTERNS'; -export const DEFAULT_DATASOURCE_NAME = 'OpenSearch Default'; -export const INDEX_PATTERN_DATASOURCE_TYPE = 'Index patterns'; +export const DEFAULT_DATASOURCE_TYPE = { + key: 'DEFAULT_INDEX_PATTERNS', + backendName: 'OpenSearch Default', + label: i18n.translate('data.datasource.type.indexPattern', { + defaultMessage: 'Index Patterns', + }), +}; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 8b84b2fce499..da03f11043d3 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -60,12 +60,7 @@ import { DiscoverUrlGenerator, } from './url_generator'; import { SearchEmbeddableFactory } from './embeddable'; -import { - DEFAULT_DATASOURCE_TYPE, - PLUGIN_ID, - DEFAULT_DATASOURCE_NAME, - INDEX_PATTERN_DATASOURCE_TYPE, -} from '../common'; +import { DEFAULT_DATASOURCE_TYPE, PLUGIN_ID } from '../common'; import { DataExplorerPluginSetup } from '../../data_explorer/public'; import { registerFeature } from './register_feature'; import { @@ -376,9 +371,9 @@ export class DiscoverPlugin const { dataSourceService, dataSourceFactory } = plugins.data.dataSources; dataSourceFactory.registerDataSourceType(DEFAULT_DATASOURCE_TYPE, DefaultDslDataSource); dataSourceService.registerDataSource( - dataSourceFactory.getDataSourceInstance(DEFAULT_DATASOURCE_TYPE, { - name: DEFAULT_DATASOURCE_NAME, - type: DEFAULT_DATASOURCE_TYPE, + dataSourceFactory.getDataSourceInstance(DEFAULT_DATASOURCE_TYPE.key, { + name: DEFAULT_DATASOURCE_TYPE.backendName, + type: DEFAULT_DATASOURCE_TYPE.key, metadata: null, indexPatterns: plugins.data.indexPatterns, })