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

[Feature] Datasource selector of multiple datasources #5167

Merged
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
3758626
initial commit for datasource
mengweieric Aug 10, 2023
3f6b467
initial commit for datasource service
mengweieric Aug 10, 2023
634c074
[Data Sources] Move datasource codebase from datasource plugin to dat…
mengweieric Aug 19, 2023
160c73e
[Data Sources] Add datasource factory
mengweieric Aug 19, 2023
12ab226
datasource service
mengweieric Sep 19, 2023
c71e0ea
datasource with factory
mengweieric Sep 19, 2023
ad55a8a
datasource selector
mengweieric Sep 19, 2023
5ce2363
exposes datasources from data plugin
mengweieric Sep 19, 2023
0042959
index pattern datasource registration
mengweieric Sep 19, 2023
1b8a746
add datasource selector to discover
mengweieric Sep 19, 2023
23cf242
remove unused files
mengweieric Sep 19, 2023
23aba4a
add/exposes types for datasources as a set of services
mengweieric Sep 19, 2023
249f139
add datasource selector unit tests
mengweieric Sep 22, 2023
116d1c1
merge datasource selector features with main
mengweieric Sep 30, 2023
2b8f7ff
add metadata to selectable
mengweieric Sep 30, 2023
c8a29b7
redirection to observability for non-index-pattern datasource
mengweieric Sep 30, 2023
b31e16f
add datasource factory tests
mengweieric Oct 1, 2023
4f1dc68
add datasource service test
mengweieric Oct 1, 2023
6e8c2d1
datasources related fixes
mengweieric Oct 2, 2023
e873130
add tests for datasource selectable
mengweieric Oct 2, 2023
b54dedd
added types/interfaces for sidebar selector, and add a couple of enha…
mengweieric Oct 2, 2023
94de201
remove pill effect
mengweieric Oct 2, 2023
53794f0
change type for name display in selector for index patterns
mengweieric Oct 2, 2023
50ace85
remove legacy index selector
mengweieric Oct 2, 2023
8cd2fe3
rename datasource change handler
mengweieric Oct 2, 2023
dd4b542
remove unused constants
mengweieric Oct 2, 2023
467b177
fix a redirection bug
mengweieric Oct 2, 2023
025cff7
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 3, 2023
20487e0
add to change log
mengweieric Oct 3, 2023
4866312
Merge branch 'main' into feature/mul-data-source
joshuarrrr Oct 3, 2023
5532d38
fix bg color issue in source selector
mengweieric Oct 3, 2023
afc8a9f
Merge branch 'main' into feature/mul-data-source
joshuarrrr Oct 3, 2023
e2e0f19
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 3, 2023
7e54205
address oui and missing guard
mengweieric Oct 4, 2023
78b8b83
add test subj
mengweieric Oct 4, 2023
3b43cae
i18 and datasource interface default return
mengweieric Oct 4, 2023
0758f4e
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 4, 2023
422da0b
add default datasource tests
mengweieric Oct 4, 2023
f01ca6c
fix typo
mengweieric Oct 4, 2023
b967036
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 4, 2023
3be325f
modify wording
mengweieric Oct 4, 2023
dcc8b2f
add experimental annotation to datasource
mengweieric Oct 4, 2023
759f3fa
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 4, 2023
46fe28e
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 4, 2023
8da7f55
add type datasource filtering to remove type error hint
mengweieric Oct 4, 2023
55a1f3a
remove unused type
mengweieric Oct 4, 2023
2227733
add 'type' to option type
mengweieric Oct 4, 2023
64872bd
Type fixes
ashwin-pc Oct 4, 2023
00491eb
cherry-pick type changes from remote and add partial fixes
mengweieric Oct 4, 2023
ccbb92b
add one type exports
mengweieric Oct 4, 2023
09f7002
remaining type errors fixed
ashwin-pc Oct 4, 2023
023507b
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 4, 2023
811347c
addressing comments
mengweieric Oct 5, 2023
e8f219b
address dedup
mengweieric Oct 5, 2023
3642ef5
refactor datasource_selectable to address comments
joshuali925 Oct 5, 2023
a27d7e0
remove unnecessary optional chaining
joshuali925 Oct 5, 2023
f188b5f
Merge branch 'main' into feature/mul-data-source
mengweieric Oct 5, 2023
d56bc1d
refactor variable names
joshuali925 Oct 5, 2023
ff8f3ef
move functnions out of selectable component
mengweieric Oct 5, 2023
0a1bfd5
add comments for dedup/options list updates
mengweieric Oct 5, 2023
21af094
add experimental annotation
mengweieric Oct 5, 2023
24e6775
callback and experimental annotation on types
mengweieric Oct 5, 2023
fe8cfeb
handleSourceSelection callback
mengweieric Oct 5, 2023
81ff866
datasource annotation and import adjustment
mengweieric Oct 5, 2023
30fc3d6
remove pill effect
mengweieric Oct 5, 2023
5ab664b
minor changes addressing latest comments
mengweieric Oct 6, 2023
bea8e75
remove unused tests
mengweieric Oct 6, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### 📈 Features/Enhancements

- 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))
Expand Down
79 changes: 79 additions & 0 deletions src/plugins/data/public/data_sources/datasource/datasource.ts
ashwin-pc marked this conversation as resolved.
Show resolved Hide resolved
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> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we type these anys correctly?

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;
}
Comment on lines +40 to +45
Copy link
Collaborator

@AMoo-Miki AMoo-Miki Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a singleton, all you should need is

export const instance = new DataSourceFactory();

... outside the class, and you don't need to export your factory.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it will always be initialized and take memeory. current way it's lazy init

Copy link
Member

@joshuali925 joshuali925 Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline and you mentioned the code will run at the time it is imported, so not always initialized in the beginning. i believe that would be in this file

const dataSourceFactory = DataSourceFactory.getInstance();

But when i tested locally, exporting here

export { DataSourceFactory } from './factory';

is enough to trigger const instance = new DataSourceFactory();. i think the instance is created whenever data plugin is imported, so i still think it's better to initialize at .getInstance() call than initialize when importing the file. so that if something changed that line to not call .getInstance(), no extra instances are created


/**
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets avoid any, cant we use a generic here?

* @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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is too generic and will result in collision.

title: string;
id: string;
}

export interface IDataSourceMetaData {
name: string;
}

export interface IDataSourceGroup {
mengweieric marked this conversation as resolved.
Show resolved Hide resolved
name: string;
}

export interface ISourceDataSet {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is too generic and will result in collision.

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is too generic and will result in collision.

success: boolean;
info: string;
}

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