Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle index pattern selector on new discover (#6438)
* Added useIndexPattern from AppState on discover component * Added factory, handler and data source class with unit tests * Improved data source management * Added classes and components to select pattern * Add select feature and unit tests * Added unit tests * Change wz-pattern-selector props, data source like dependency * Update unit tests * Fix data source selector import * Update CHANGELOG * Fix dissapear selector when init app * Updated CHANGELOG
- Loading branch information
Showing
20 changed files
with
1,033 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
plugins/main/public/components/common/data-source/data-source-factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { tDataSource } from './index' | ||
export type tDataSourceFactory = { | ||
create(item: tDataSource): tDataSource; | ||
createAll(items: tDataSource[]): tDataSource[]; | ||
} |
8 changes: 8 additions & 0 deletions
8
plugins/main/public/components/common/data-source/data-source-repository.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { tDataSource } from "./data-source"; | ||
|
||
export interface DataSourceRepository { | ||
get(id: string): Promise<tDataSource>; | ||
getAll(): Promise<tDataSource[]>; | ||
setDefault(dataSource: tDataSource): Promise<void> | void; | ||
getDefault(): Promise<tDataSource | null> | tDataSource | null; | ||
} |
181 changes: 181 additions & 0 deletions
181
plugins/main/public/components/common/data-source/data-source-selector.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import { DataSourceSelector } from './data-source-selector'; | ||
import { DataSourceRepository } from './data-source-repository'; | ||
import { tDataSourceFactory } from './data-source-factory'; | ||
import { tDataSource } from './data-source'; | ||
|
||
class ExampleRepository implements DataSourceRepository { | ||
getDefault = jest.fn(); | ||
setDefault = jest.fn(); | ||
get = jest.fn(); | ||
getAll = jest.fn(); | ||
} | ||
|
||
class ExampleFactory implements tDataSourceFactory { | ||
create = jest.fn(); | ||
createAll = jest.fn(); | ||
} | ||
|
||
let repository; | ||
let factory; | ||
let dataSourceSelector; | ||
|
||
const dataSourcesMocked: tDataSource[] = [ | ||
{ id: '1', title: 'DataSource 1', select: (): Promise<void> => Promise.resolve() }, | ||
{ id: '2', title: 'DataSource 2', select: (): Promise<void> => Promise.resolve() }, | ||
{ id: '3', title: 'DataSource 3', select: (): Promise<void> => Promise.resolve() }, | ||
]; | ||
|
||
describe('DataSourceSelector', () => { | ||
|
||
beforeEach(() => { | ||
repository = new ExampleRepository(); | ||
factory = new ExampleFactory(); | ||
dataSourceSelector = new DataSourceSelector(repository, factory); | ||
}); | ||
|
||
describe('constructor', () => { | ||
it('should return ERROR when the selector not receive a repository', () => { | ||
try { | ||
new DataSourceSelector(null as any, factory); | ||
} catch (error) { | ||
expect(error.message).toBe('Data source repository is required'); | ||
} | ||
}) | ||
|
||
it('should return ERROR when the selector not receive a valid repository', () => { | ||
try { | ||
new DataSourceSelector({} as any, factory); | ||
} catch (error) { | ||
expect(error.message).toBe('Invalid data source factory'); | ||
} | ||
}) | ||
|
||
|
||
it('should return ERROR when the selector not receive a factory', () => { | ||
try { | ||
new DataSourceSelector(repository, null as any); | ||
} catch (error) { | ||
expect(error.message).toBe('Data source factory is required'); | ||
} | ||
}) | ||
|
||
it('should return ERROR when the selector not receive a valid factory', () => { | ||
try { | ||
new DataSourceSelector(repository, {} as any); | ||
} catch (error) { | ||
expect(error.message).toBe('Invalid data source factory'); | ||
} | ||
}) | ||
|
||
|
||
}) | ||
|
||
describe('existsDataSource', () => { | ||
it('should return TRUE when the data source exists', async () => { | ||
jest.spyOn(repository, 'get').mockResolvedValue({ id: '1', name: 'DataSource 1' }); | ||
const result = await dataSourceSelector.existsDataSource('1'); | ||
expect(result).toBe(true); | ||
expect(repository.get).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return FALSE when the data source does not exist', async () => { | ||
jest.spyOn(repository, 'get').mockResolvedValue(null); | ||
const result = await dataSourceSelector.existsDataSource('fake-id'); | ||
expect(result).toBe(false); | ||
expect(repository.get).toHaveBeenCalledTimes(1); | ||
}); | ||
}) | ||
|
||
describe('getFirstValidDataSource', () => { | ||
it('should return the first valid data source from the repository', async () => { | ||
jest.spyOn(repository, 'getAll').mockResolvedValue([]); | ||
jest.spyOn(factory, 'createAll').mockResolvedValue(dataSourcesMocked); | ||
jest.spyOn(dataSourceSelector, 'existsDataSource').mockReturnValueOnce(false).mockReturnValueOnce(true); | ||
const result = await dataSourceSelector.getFirstValidDataSource(); | ||
expect(result).toEqual(dataSourcesMocked[1]); | ||
expect(repository.getAll).toHaveBeenCalledTimes(1); | ||
expect(factory.createAll).toHaveBeenCalledTimes(1); | ||
expect(dataSourceSelector.existsDataSource).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
it('should throw an error when no valid data source is found', async () => { | ||
jest.spyOn(repository, 'getAll').mockResolvedValue([]); | ||
jest.spyOn(factory, 'createAll').mockResolvedValue(dataSourcesMocked); | ||
jest.spyOn(dataSourceSelector, 'existsDataSource').mockReturnValue(false); | ||
try { | ||
await dataSourceSelector.getFirstValidDataSource(); | ||
} catch (error) { | ||
expect(error.message).toBe('No valid data sources found'); | ||
} | ||
}); | ||
}) | ||
|
||
describe('getAllDataSources', () => { | ||
it('should return all data sources from the repository when the map is empty', async () => { | ||
jest.spyOn(repository, 'getAll').mockResolvedValue([]); | ||
jest.spyOn(factory, 'createAll').mockResolvedValue(dataSourcesMocked); | ||
const result = await dataSourceSelector.getAllDataSources(); | ||
expect(result).toEqual(dataSourcesMocked); | ||
}); | ||
|
||
it('should return all data sources from the map when was loaded previously', async () => { | ||
jest.spyOn(repository, 'getAll').mockResolvedValue([]); | ||
jest.spyOn(factory, 'createAll').mockResolvedValue(dataSourcesMocked); | ||
await dataSourceSelector.getAllDataSources(); | ||
const result = await dataSourceSelector.getAllDataSources(); | ||
expect(result).toEqual(dataSourcesMocked); | ||
expect(factory.createAll).toHaveBeenCalledTimes(1); | ||
expect(repository.getAll).toHaveBeenCalledTimes(1); | ||
}); | ||
}) | ||
|
||
describe('getDataSource', () => { | ||
|
||
it('should return the selected data source from the repository', async () => { | ||
const dataSourceId = '1'; | ||
jest.spyOn(repository, 'getDefault').mockResolvedValue({ id: dataSourceId, name: 'Selected DataSource' }); | ||
const result = await dataSourceSelector.getSelectedDataSource(); | ||
expect(result.id).toEqual(dataSourceId); | ||
expect(repository.getDefault).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return the first data source when the repository does not have a selected data source', async () => { | ||
jest.spyOn(repository, 'getDefault').mockResolvedValue(null); | ||
jest.spyOn(repository, 'getAll').mockResolvedValue([]); | ||
jest.spyOn(factory, 'createAll').mockResolvedValue(dataSourcesMocked); | ||
// mock spyon existsDataSource method to return 2 times differents values | ||
jest.spyOn(dataSourceSelector, 'existsDataSource').mockReturnValueOnce(false).mockReturnValueOnce(true); | ||
jest.spyOn(dataSourceSelector, 'selectDataSource').mockResolvedValue(true); | ||
const result = await dataSourceSelector.getSelectedDataSource(); | ||
expect(result.id).toEqual(dataSourcesMocked[1].id); | ||
expect(repository.getDefault).toHaveBeenCalledTimes(1); | ||
expect(repository.getAll).toHaveBeenCalledTimes(1); | ||
expect(factory.createAll).toHaveBeenCalledTimes(1); | ||
expect(dataSourceSelector.existsDataSource).toHaveBeenCalledTimes(2); | ||
expect(dataSourceSelector.selectDataSource).toHaveBeenCalledTimes(1); | ||
}) | ||
|
||
}) | ||
|
||
describe('selectDataSource', () => { | ||
|
||
it('should select a data source by ID when exists', async () => { | ||
jest.spyOn(repository, 'getAll').mockResolvedValue([]); | ||
jest.spyOn(factory, 'createAll').mockResolvedValue(dataSourcesMocked); | ||
jest.spyOn(repository, 'setDefault').mockResolvedValue(true); | ||
await dataSourceSelector.selectDataSource('1'); | ||
expect(repository.setDefault).toHaveBeenCalledTimes(1); | ||
expect(repository.setDefault).toHaveBeenCalledWith(dataSourcesMocked[0]); | ||
}); | ||
|
||
it('should throw an error when selecting a non-existing data source', async () => { | ||
jest.spyOn(repository, 'getAll').mockResolvedValue([]); | ||
jest.spyOn(factory, 'createAll').mockResolvedValue(dataSourcesMocked); | ||
try { | ||
await dataSourceSelector.selectDataSource('fake-id'); | ||
} catch (error) { | ||
expect(error.message).toBe('Data source not found'); | ||
} | ||
}); | ||
}) | ||
}) |
138 changes: 138 additions & 0 deletions
138
plugins/main/public/components/common/data-source/data-source-selector.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { DataSourceRepository } from './data-source-repository'; | ||
import { tDataSourceFactory } from './data-source-factory'; | ||
import { tDataSource } from "./data-source"; | ||
|
||
export type tDataSourceSelector = { | ||
existsDataSource: (id: string) => Promise<boolean>; | ||
getFirstValidDataSource: () => Promise<tDataSource>; | ||
getAllDataSources: () => Promise<tDataSource[]>; | ||
getDataSource: (id: string) => Promise<tDataSource>; | ||
getSelectedDataSource: () => Promise<tDataSource>; | ||
selectDataSource: (id: string) => Promise<void>; | ||
} | ||
|
||
|
||
export class DataSourceSelector implements tDataSourceSelector { | ||
// add a map to store locally the data sources | ||
private dataSources: Map<string, tDataSource> = new Map(); | ||
|
||
constructor(private repository: DataSourceRepository, private factory: tDataSourceFactory) { | ||
if (!repository) { | ||
throw new Error('Data source repository is required'); | ||
} | ||
if (!factory) { | ||
throw new Error('Data source factory is required'); | ||
} | ||
} | ||
|
||
/** | ||
* Check if the data source exists in the repository. | ||
* @param id | ||
*/ | ||
async existsDataSource(id: string): Promise<boolean> { | ||
try { | ||
if (!id) { | ||
throw new Error('Error checking data source. ID is required'); | ||
} | ||
const dataSource = await this.repository.get(id); | ||
return !!dataSource; | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Get the first valid data source from the repository. | ||
* Loop through the data sources and return the first valid data source. | ||
* Break the while when the valid data source is found | ||
*/ | ||
async getFirstValidDataSource(): Promise<tDataSource> { | ||
const dataSources = await this.getAllDataSources(); | ||
if (dataSources.length === 0) { | ||
throw new Error('No data sources found'); | ||
} | ||
let index = 0; | ||
do { | ||
const dataSource = dataSources[index]; | ||
if (await this.existsDataSource(dataSource.id)) { | ||
return dataSource; | ||
} | ||
index++; | ||
} while (index < dataSources.length); | ||
throw new Error('No valid data sources found'); | ||
} | ||
|
||
/** | ||
* Get all the data sources from the repository. | ||
* When the map of the data sources is empty, get all the data sources from the repository. | ||
*/ | ||
|
||
async getAllDataSources(): Promise<tDataSource[]> { | ||
if (this.dataSources.size === 0) { | ||
const dataSources = await this.factory.createAll(await this.repository.getAll()); | ||
dataSources.forEach(dataSource => { | ||
this.dataSources.set(dataSource.id, dataSource); | ||
}); | ||
} | ||
return Array.from(this.dataSources.values()); | ||
} | ||
|
||
/** | ||
* Get a data source by a received ID. | ||
* When the map of the data sources is empty, get all the data sources from the repository. | ||
* When the data source is not found, an error is thrown. | ||
* @param id | ||
*/ | ||
async getDataSource(id: string): Promise<tDataSource> { | ||
// when the map of the data sources is empty, get all the data sources from the repository | ||
if (this.dataSources.size === 0) { | ||
await this.getAllDataSources(); | ||
} | ||
const dataSource = this.dataSources.get(id); | ||
if (!dataSource) { | ||
throw new Error('Data source not found'); | ||
} | ||
return dataSource; | ||
} | ||
|
||
/** | ||
* Select a data source by a received ID. | ||
* When the data source is not found, an error is thrown. | ||
* When the data source is found, it is selected and set as the default data source. | ||
* @param id | ||
*/ | ||
async selectDataSource(id: string): Promise<void> { | ||
if (!id) { | ||
throw new Error('Error selecting data source. ID is required'); | ||
} | ||
const dataSource = await this.getDataSource(id); | ||
if (!dataSource) { | ||
throw new Error('Data source not found'); | ||
} | ||
await dataSource.select(); | ||
await this.repository.setDefault(dataSource); | ||
} | ||
|
||
/** | ||
* Get the selected data source from the repository. | ||
* When the repository has a data source, return the selected data source. | ||
* When the repository does not have a selected data source, return the first valid data source. | ||
* When the repository throws an error, return the first valid data source. | ||
*/ | ||
async getSelectedDataSource(): Promise<tDataSource> { | ||
try { | ||
const defaultDataSource = await this.repository.getDefault(); | ||
if (!defaultDataSource) { | ||
const validDataSource = await this.getFirstValidDataSource(); | ||
await this.selectDataSource(validDataSource.id); | ||
return validDataSource; | ||
} | ||
return defaultDataSource; | ||
} catch (error) { | ||
const validateDataSource = await this.getFirstValidDataSource(); | ||
await this.selectDataSource(validateDataSource.id); | ||
return validateDataSource; | ||
} | ||
} | ||
|
||
} |
5 changes: 5 additions & 0 deletions
5
plugins/main/public/components/common/data-source/data-source.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export type tDataSource = { | ||
id: string; | ||
title: string; | ||
select(): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * from './data-source'; | ||
export * from './data-source-repository'; | ||
export * from './data-source-factory'; | ||
export * from './data-source-selector'; | ||
export * from './pattern'; |
Oops, something went wrong.