Skip to content

Commit

Permalink
Handle index pattern selector on new discover (#6438)
Browse files Browse the repository at this point in the history
* 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
Machi3mfl committed Mar 20, 2024
1 parent 0760cdd commit a8a459b
Show file tree
Hide file tree
Showing 20 changed files with 1,033 additions and 117 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -12,10 +12,11 @@ All notable changes to the Wazuh app project will be documented in this file.
- Added the ability to manage the API hosts from the Server APIs [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337) [#6519](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6519)
- Added edit agent groups and upgrade agents actions to Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250) [#6476](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6476) [#6274](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6274) [#6501](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6501)
- Added propagation of updates from the table to dashboard visualizations in Endpoints summary [#6460](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6460)
- Handle index pattern selector on new discover [#6499](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6499)

### Changed

- Removed embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) [#6235](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6235) [#6254](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6254) [#6285](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6285) [#6288](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6288) [#6290](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6290) [#6289](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6289) [#6286](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6286) [#6275](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6275) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6297](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6297) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6291](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6459](https://github.com/wazuh/wazuh-dashboard-plugins/pull/#6459)
- Removed embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) [#6235](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6235) [#6254](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6254) [#6285](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6285) [#6288](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6288) [#6290](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6290) [#6289](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6289) [#6286](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6286) [#6275](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6275) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6297](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6297) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6291](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6459](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6459) [#6434](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6434)
- Develop logic of a new index for the fim module [#6227](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6227)
- Allow editing groups for an agent from Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250)
- Changed as the configuration is defined and stored [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337)
Expand Down
@@ -0,0 +1,5 @@
import { tDataSource } from './index'
export type tDataSourceFactory = {
create(item: tDataSource): tDataSource;
createAll(items: tDataSource[]): tDataSource[];
}
@@ -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;
}
@@ -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');
}
});
})
})
@@ -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;
}
}

}
@@ -0,0 +1,5 @@
export type tDataSource = {
id: string;
title: string;
select(): Promise<void>;
}
5 changes: 5 additions & 0 deletions plugins/main/public/components/common/data-source/index.ts
@@ -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';

0 comments on commit a8a459b

Please sign in to comment.