Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.11] [Feature] Datasource selector of multiple datasources #5236

Merged
merged 1 commit into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/plugins/data/public/data_sources/datasource/datasource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Abstract class representing a data source. This class provides foundational
* interfaces for specific data sources. Any data source connection needs to extend
* and implement from this base class
*
* DataSourceMetaData: Represents metadata associated with the data source.
* SourceDataSet: Represents the dataset associated with the data source.
* DataSourceQueryResult: Represents the result from querying the data source.
*/

import { ConnectionStatus } from './types';

/**
* @experimental this class is experimental and might change in future releases.
*/
export abstract class DataSource<
DataSourceMetaData,
DataSetParams,
SourceDataSet,
DataSourceQueryParams,
DataSourceQueryResult
> {
constructor(
private readonly name: string,
private readonly type: string,
private readonly metadata: DataSourceMetaData
) {}

getName() {
return this.name;
}

getType() {
return this.type;
}

getMetadata() {
return this.metadata;
}

/**
* Abstract method to get the dataset associated with the data source.
* Implementing classes need to provide the specific implementation.
*
* Data source selector needs to display data sources with pattern
* group (connection name) - a list of datasets. For example, get
* all available tables for flint datasources, and get all index
* patterns for OpenSearch data source
*
* @experimental This API is experimental and might change in future releases.
* @returns {SourceDataSet} Dataset associated with the data source.
*/
abstract getDataSet(dataSetParams?: DataSetParams): SourceDataSet;

/**
* Abstract method to run a query against the data source.
* Implementing classes need to provide the specific implementation.
*
* @experimental This API is experimental and might change in future releases.
* @returns {DataSourceQueryResult} Result from querying the data source.
*/
abstract runQuery(queryParams: DataSourceQueryParams): DataSourceQueryResult;

/**
* Abstract method to test the connection to the data source.
* Implementing classes should provide the specific logic to determine
* the connection status, typically indicating success or failure.
*
* @experimental This API is experimental and might change in future releases.
* @returns {ConnectionStatus | Promise<void>} Status of the connection test.
* @experimental
*/
abstract testConnection(): ConnectionStatus | Promise<boolean>;
}
93 changes: 93 additions & 0 deletions src/plugins/data/public/data_sources/datasource/factory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { DataSourceFactory } from './factory';
import { DataSource } from './datasource';
import { IndexPattern, IndexPatternsService } from '../../index_patterns';

class MockDataSource extends DataSource<any, any, any, any, any> {
private readonly indexPatterns;

constructor({
name,
type,
metadata,
indexPatterns,
}: {
name: string;
type: string;
metadata: any;
indexPatterns: IndexPatternsService;
}) {
super(name, type, metadata);
this.indexPatterns = indexPatterns;
}

async getDataSet(dataSetParams?: any) {
await this.indexPatterns.ensureDefaultIndexPattern();
return await this.indexPatterns.getCache();
}

async testConnection(): Promise<boolean> {
return true;
}

async runQuery(queryParams: any) {
return undefined;
}
}

describe('DataSourceFactory', () => {
beforeEach(() => {
// Reset the DataSourceFactory's singleton instance before each test for isolation
(DataSourceFactory as any).factory = undefined;
});

it('returns a singleton instance', () => {
const instance1 = DataSourceFactory.getInstance();
const instance2 = DataSourceFactory.getInstance();
expect(instance1).toBe(instance2);
});

it('registers a new data source type correctly', () => {
const factory = DataSourceFactory.getInstance();
expect(() => {
factory.registerDataSourceType('mock', MockDataSource);
}).not.toThrow();
});

it('throws error when registering an already registered data source type', () => {
const factory = DataSourceFactory.getInstance();
factory.registerDataSourceType('mock', MockDataSource);
expect(() => {
factory.registerDataSourceType('mock', MockDataSource);
}).toThrow('This data source type has already been registered');
});

it('creates and returns an instance of the registered data source type', () => {
const factory = DataSourceFactory.getInstance();
const mockIndexPattern = {} as IndexPattern;
const config = {
name: 'test_datasource',
type: 'mock',
metadata: null,
indexPattern: mockIndexPattern,
};
factory.registerDataSourceType('mock', MockDataSource);

const instance = factory.getDataSourceInstance('mock', config);
expect(instance).toBeInstanceOf(MockDataSource);
expect(instance.getName()).toEqual(config.name);
expect(instance.getType()).toEqual(config.type);
expect(instance.getMetadata()).toEqual(config.metadata);
});

it('throws error when trying to get an instance of an unregistered data source type', () => {
const factory = DataSourceFactory.getInstance();
expect(() => {
factory.getDataSourceInstance('unregistered', {});
}).toThrow('Unsupported data source type');
});
});
80 changes: 80 additions & 0 deletions src/plugins/data/public/data_sources/datasource/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

/**
* 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';
import { DataSource } from '../datasource';

type DataSourceClass<
MetaData = any,
SetParams = any,
DataSet = any,
QueryParams = any,
QueryResult = any
> = new (config: any) => DataSource<MetaData, SetParams, DataSet, QueryParams, QueryResult>;

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]: DataSourceClass } = {};

/**
* Private constructor to ensure only one instance of DataSourceFactory is created.
*/
private constructor() {}

/**
* Returns the singleton instance of the DataSourceFactory. If it doesn't exist, it creates one.
*
* @experimental This API is experimental and might change in future releases.
* @returns {DataSourceFactory} The single instance of DataSourceFactory.
*/
static getInstance(): DataSourceFactory {
if (!this.factory) {
this.factory = new DataSourceFactory();
}
return this.factory;
}

/**
* Registers a new data source type with its associated class.
* If the type has already been registered, an error is thrown.
*
* @experimental This API is experimental and might change in future releases.
* @param {string} type - The identifier for the data source type.
* @param {DataSourceClass} 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: DataSourceClass): void {
if (this.dataSourceClasses[type]) {
throw new Error('This data source type has already been registered');
}
this.dataSourceClasses[type] = dataSourceClass;
}

/**
* 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.
*
* @experimental This API is experimental and might change in future releases.
* @param {string} type - 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.
* @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) {
throw new Error('Unsupported data source type');
}
return new DataSourceClass(config);
}
}
17 changes: 17 additions & 0 deletions src/plugins/data/public/data_sources/datasource/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { DataSource } from './datasource';
export {
IDataSourceMetaData,
ISourceDataSet,
IDataSetParams,
IDataSourceQueryParams,
IDataSourceQueryResult,
ConnectionStatus,
DataSourceConfig,
IndexPatternOption,
} from './types';
export { DataSourceFactory } from './factory';
51 changes: 51 additions & 0 deletions src/plugins/data/public/data_sources/datasource/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @experimental These interfaces are experimental and might change in future releases.
*/

import { IndexPatternsService } from '../../index_patterns';
import { DataSourceType } from '../datasource_services';

export interface IndexPatternOption {
title: string;
id: string;
}

export interface IDataSourceMetaData {
name: string;
}

export interface IDataSourceGroup {
name: string;
}

export interface ISourceDataSet {
ds: DataSourceType;
data_sets: Array<string | IndexPatternOption>;
}

// to-dos: add common interfaces for datasource
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IDataSetParams {}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IDataSourceQueryParams {}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IDataSourceQueryResult {}

export interface ConnectionStatus {
success: boolean;
info: string;
}

export interface DataSourceConfig {
name: string;
type: string;
metadata: any;
indexPatterns: IndexPatternsService;
}
Loading
Loading