diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f91bd870..e03809bb52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/plugins/main/public/components/common/data-source/data-source-factory.ts b/plugins/main/public/components/common/data-source/data-source-factory.ts new file mode 100644 index 0000000000..993a78141a --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-factory.ts @@ -0,0 +1,5 @@ +import { tDataSource } from './index' +export type tDataSourceFactory = { + create(item: tDataSource): tDataSource; + createAll(items: tDataSource[]): tDataSource[]; +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-repository.ts b/plugins/main/public/components/common/data-source/data-source-repository.ts new file mode 100644 index 0000000000..7a7dc3bebe --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-repository.ts @@ -0,0 +1,8 @@ +import { tDataSource } from "./data-source"; + +export interface DataSourceRepository { + get(id: string): Promise; + getAll(): Promise; + setDefault(dataSource: tDataSource): Promise | void; + getDefault(): Promise | tDataSource | null; +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-selector.test.ts b/plugins/main/public/components/common/data-source/data-source-selector.test.ts new file mode 100644 index 0000000000..3360119aeb --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-selector.test.ts @@ -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 => Promise.resolve() }, + { id: '2', title: 'DataSource 2', select: (): Promise => Promise.resolve() }, + { id: '3', title: 'DataSource 3', select: (): Promise => 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'); + } + }); + }) +}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-selector.ts b/plugins/main/public/components/common/data-source/data-source-selector.ts new file mode 100644 index 0000000000..d35c0c87d0 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -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; + getFirstValidDataSource: () => Promise; + getAllDataSources: () => Promise; + getDataSource: (id: string) => Promise; + getSelectedDataSource: () => Promise; + selectDataSource: (id: string) => Promise; +} + + +export class DataSourceSelector implements tDataSourceSelector { + // add a map to store locally the data sources + private dataSources: Map = 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 { + 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 { + 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 { + 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 { + // 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 { + 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 { + 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; + } + } + +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source.ts b/plugins/main/public/components/common/data-source/data-source.ts new file mode 100644 index 0000000000..e9a9f87f48 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source.ts @@ -0,0 +1,5 @@ +export type tDataSource = { + id: string; + title: string; + select(): Promise; +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/index.ts b/plugins/main/public/components/common/data-source/index.ts new file mode 100644 index 0000000000..ef95f4ee82 --- /dev/null +++ b/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'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source-repository.ts new file mode 100644 index 0000000000..b7048c65c5 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source-repository.ts @@ -0,0 +1,38 @@ +import { PatternDataSourceRepository } from "../pattern-data-source-repository"; + +const ALERTS_REQUIRED_FIELDS = ['timestamp', 'rule.groups', 'manager.name', 'agent.id']; + +export class AlertsDataSourceRepository extends PatternDataSourceRepository { + constructor() { + super(); + } + + async getAll() { + const indexPatterns = await super.getAll(); + return indexPatterns.filter(this.checkIfAlertsIndexPattern); + } + + async get(id: string) { + const dataSource = await super.get(id); + if (this.checkIfAlertsIndexPattern(dataSource)) { + return dataSource; + } else { + throw new Error('Alerts index pattern not found'); + } + } + + /** + * Validate if the data source is an alerts index pattern + * The alerts index pattern must have the following fields: + * - timestamp + * - rule.groups + * - manager.name + * - agent.id + * + * @param dataSource + * @returns boolean + */ + checkIfAlertsIndexPattern(dataSource): boolean { + return ALERTS_REQUIRED_FIELDS.every(reqField => dataSource._fields.some(field => field.name === reqField)); + } +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/index.ts b/plugins/main/public/components/common/data-source/pattern/alerts/index.ts new file mode 100644 index 0000000000..e15062c403 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/alerts/index.ts @@ -0,0 +1 @@ +export * from './alerts-data-source-repository'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/index.ts b/plugins/main/public/components/common/data-source/pattern/index.ts new file mode 100644 index 0000000000..cf3b71dd1d --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/index.ts @@ -0,0 +1,5 @@ +export * from './pattern-data-source-repository'; +export * from './pattern-data-source'; +export * from './pattern-data-source-factory'; +export * from './pattern-data-source'; +export * from './alerts'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.test.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.test.ts new file mode 100644 index 0000000000..1151586539 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.test.ts @@ -0,0 +1,47 @@ +import { PatternDataSourceFactory } from './pattern-data-source-factory'; +import { PatternDataSource } from '../'; +import { tDataSource } from '../data-source'; +import { tParsedIndexPattern } from './pattern-data-source-repository'; + +const mockedItem: tParsedIndexPattern = { + attributes: { + title: 'test-pattern-title', + fields: '[{"name":"timestamp","type":"date","count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', + }, + title: 'test-pattern-title', + id: 'test-pattern-id', + migrationVersion: { + 'index-pattern': '7.10.0', + }, + namespace: ['default'], + references: [], + score: 0, + type: 'index-pattern', + updated_at: '2021-08-23T14:05:54.000Z', + version: 'WzIwMjEsM', + _fields: [], + select: (): Promise => Promise.resolve() +}; + +describe('PatternDataSourceFactory', () => { + let factory: PatternDataSourceFactory; + + beforeEach(() => { + factory = new PatternDataSourceFactory(); + }); + + it('should create a single pattern data source', () => { + const createdItem = factory.create(mockedItem); + expect(createdItem).toBeInstanceOf(PatternDataSource); + }); + + it('should create an array of pattern data sources', () => { + const items: PatternDataSource[] = [mockedItem]; + const createdItems = factory.createAll(items); + expect(createdItems).toBeInstanceOf(Array); + expect(createdItems.length).toBe(items.length); + createdItems.forEach((createdItem, index) => { + expect(createdItem).toBeInstanceOf(PatternDataSource); + }); + }); +}); diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.ts new file mode 100644 index 0000000000..fea41e6065 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.ts @@ -0,0 +1,16 @@ +import { tDataSourceFactory, PatternDataSource } from '../'; +export class PatternDataSourceFactory implements tDataSourceFactory { + + create(item: PatternDataSource): PatternDataSource { + if(!item){ + throw new Error('Cannot create data source from null or undefined'); + }; + return new PatternDataSource(item.id, item.title); + } + createAll(items: PatternDataSource[]): PatternDataSource[] { + if(!items){ + throw new Error('Cannot create data source from null or undefined'); + }; + return items.map(item => this.create(item)); + } +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.test.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.test.ts new file mode 100644 index 0000000000..351c909bd9 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.test.ts @@ -0,0 +1,255 @@ +import { PatternDataSourceRepository, tSavedObjectResponse } from './pattern-data-source-repository'; +import { tDataSource } from '../data-source'; + +import { GenericRequest } from '../../../../react-services/generic-request'; +jest.mock('../../../../react-services/generic-request'); +import { AppState } from '../../../../react-services/app-state'; +jest.mock('../../../../react-services/app-state'); + +jest.mock('../../../../kibana-services', () => ({ + ...(jest.requireActual('../../../../kibana-services') as object), + getHttp: jest.fn().mockReturnValue({ + basePath: { + get: () => { + return 'http://localhost:5601'; + }, + prepend: url => { + return `http://localhost:5601${url}`; + }, + }, + }), + getCookies: jest.fn().mockReturnValue({ + set: (name, value, options) => { + return true; + }, + get: () => { + return '{}'; + }, + remove: () => { + return; + }, + }), + getUiSettings: jest.fn().mockReturnValue({ + get: name => { + return true; + }, + }), +})); + +const mockedSavedObject = { + data: { + attributes: { + title: 'test-pattern-title', + fields: '[{"name":"timestamp","type":"date","count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', + }, + id: 'test-pattern-id', + migrationVersion: { + 'index-pattern': '7.10.0', + }, + namespace: ['default'], + references: [], + score: 0, + type: 'index-pattern', + updated_at: '2021-08-23T14:05:54.000Z', + version: 'WzIwMjEsM', + } +} as tSavedObjectResponse; + +function createMockedSavedObjectListResponse(list: { id: string, title: string }[]): tSavedObjectResponse[] { + return list.map(item => { + return { + data: { + attributes: { + title: item.title, + fields: '[{"name":"timestamp","type":"date","count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', + }, + id: item.id, + migrationVersion: { + 'index-pattern': '7.10.0', + }, + namespace: ['default'], + references: [], + score: 0, + type: 'index-pattern', + updated_at: '2021-08-23T14:05:54.000Z', + version: 'WzIwMjEsM', + } + } as tSavedObjectResponse; + }); + +} + +describe('PatternDataSourceRepository', () => { + let repository: PatternDataSourceRepository; + + beforeEach(() => { + repository = new PatternDataSourceRepository(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should create a new instance', () => { + expect(repository).toBeInstanceOf(PatternDataSourceRepository); + }); + + it('should return a pattern by id', async () => { + const mockId = 'test-pattern-id'; + const mockTitle = 'test-pattern-title'; + const expectedResponse = { + data: { + ...mockedSavedObject.data, + id: mockId, + attributes: { + ...mockedSavedObject.data.attributes, + title: mockTitle, + } + } + }; + const mockRequest = jest.fn().mockResolvedValue(expectedResponse); + GenericRequest.request = mockRequest; + + const result = await repository.get(mockId) as tDataSource; + + expect(result.id).toEqual(expectedResponse.data.id); + expect(result.title).toEqual(expectedResponse.data.attributes.title); + expect(result._fields).toEqual(JSON.parse(expectedResponse.data.attributes.fields)); + }); + + it('should return an ERROR when the request throw an error', async () => { + const id = 'not-exists'; + const expectedError = new Error(`Error 404 or any other error in response`); + const mockRequest = jest.fn().mockRejectedValue(expectedError); + GenericRequest.request = mockRequest; + + await expect(repository.get(id)).rejects.toThrow(`Error getting index pattern: ${expectedError.message}`); + }); + + it('should return an ERROR when the response not return a saved object data', async () => { + const id = 'test-id'; + const expectedResponse = { + }; + const mockRequest = jest.fn().mockResolvedValue(expectedResponse); + GenericRequest.request = mockRequest; + try { + await repository.get(id); + } catch (error) { + expect(error.message).toBe(`Error getting index pattern: Index pattern "${id}" not found`); + } + }); + + + it('should return an ERROR when the request not return and id or a title', async () => { + const id = 'test-id'; + const expectedResponse = { + data: { + ...mockedSavedObject.data, + id: null, + attributes: { + ...mockedSavedObject.data.attributes, + title: null, + } + } + }; + const mockRequest = jest.fn().mockResolvedValue(expectedResponse); + GenericRequest.request = mockRequest; + try { + await repository.get(id); + } catch (error) { + expect(error.message).toBe('Error getting index pattern: Invalid index pattern data'); + } + }); + + it('should return all the index patterns', async () => { + const listOfMockedPatterns = createMockedSavedObjectListResponse([ + { id: 'id-1', title: 'title-1' }, + { id: 'id-2', title: 'title-2' }, + { id: 'id-3', title: 'title-3' }, + ]); + + const mockRequest = jest.fn().mockResolvedValue(listOfMockedPatterns); + GenericRequest.request = mockRequest; + const result = await repository.getAll(); + // check if the response have the same id and title and exists the _fields property + result.forEach((item, index) => { + expect(item.id).toBe(listOfMockedPatterns[index].data.id); + expect(item.title).toBe(listOfMockedPatterns[index].data.attributes.title); + expect(item._fields).toBeDefined(); + }); + + }); + + it('should return ERROR when the getAll request throw an error ', async () => { + const expectedError = new Error('Error 404 or any other error in response'); + const mockRequest = jest.fn().mockRejectedValue(expectedError); + GenericRequest.request = mockRequest; + + await expect(repository.getAll()).rejects.toThrow(`Error getting index patterns: ${expectedError.message}`); + }); + + it('should parse index pattern data', () => { + const mockedIndexPatternData = { + ...mockedSavedObject.data, + id: 'test-pattern-id', + attributes: { + ...mockedSavedObject.data.attributes, + title: 'test-pattern-title', + } + } + const result = repository.parseIndexPattern(mockedIndexPatternData); + expect(result.id).toEqual(mockedIndexPatternData.id); + expect(result.title).toEqual(mockedIndexPatternData.attributes.title); + expect(result._fields).toBeDefined(); + }); + + it('should set default pattern in storage', async () => { + const mockedIndexPattern = { + ...mockedSavedObject.data, + id: 'test-id', + attributes: { + ...mockedSavedObject.data.attributes, + title: 'test-title', + } + } + const parsedIndexPatternData = repository.parseIndexPattern(mockedIndexPattern); + await repository.setDefault(parsedIndexPatternData); + expect(AppState.setCurrentPattern).toHaveBeenCalledWith(mockedIndexPattern.id); + }); + + it('should return ERROR when not receive an index pattern to setting like default', async () => { + try { + repository.setDefault(null as any) + }catch(error){ + expect(error.message).toBe('Index pattern is required'); + } + }); + + it('should return default index pattern stored', async () => { + AppState.getCurrentPattern = jest.fn().mockReturnValue('test-pattern-id'); + const mockedDefaultIndexPattern = { + data: { + ...mockedSavedObject.data, + id: 'test-pattern-id', + attributes: { + ...mockedSavedObject.data.attributes, + title: 'test-pattern-title', + } + } + } + const mockRequest = jest.fn().mockResolvedValue(mockedDefaultIndexPattern); + GenericRequest.request = mockRequest; + const result = await repository.getDefault(); + // mock the get method to return the current pattern + expect(result.id).toEqual('test-pattern-id'); + }); + + it('should return an ERROR when default index pattern is not saved in storage', async () => { + AppState.getCurrentPattern = jest.fn().mockReturnValue(null); + try { + await repository.getDefault(); + }catch(error){ + expect(error.message).toBe('No default pattern set'); + } + }); +}); diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.ts new file mode 100644 index 0000000000..05feaaef86 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.ts @@ -0,0 +1,108 @@ +import { tDataSource } from "../data-source"; +import { DataSourceRepository } from "../data-source-repository"; +import { GenericRequest } from '../../../../react-services/generic-request'; +import { AppState } from '../../../../react-services'; + +export type tSavedObjectResponse = { + data: { + attributes: { + fields: string; + title: string; + }; + id: string; + migrationVersion: { + 'index-pattern': string; + }; + namespace: string[]; + references: any[]; + score: number; + type: string; + updated_at: string; + version: string; + } +} + +export type tParsedIndexPattern = { + attributes: { + fields: string; + title: string; + }; + title: string; + id: string; + migrationVersion: { + 'index-pattern': string; + }; + namespace: string[]; + references: any[]; + score: number; + type: string; + updated_at: string; + version: string; + _fields: any[]; + //ToDo: make sure that the following properties are not required + select: () => Promise +} + +export class PatternDataSourceRepository implements DataSourceRepository { + async get(id: string): Promise { + try { + const savedObjectResponse = await GenericRequest.request( + 'GET', + `/api/saved_objects/index-pattern/${id}?fields=title&fields=fields`, + ) as tSavedObjectResponse; + + const indexPatternData = (savedObjectResponse || {}).data; + if(!indexPatternData){ + throw new Error(`Index pattern "${id}" not found`); + } + const parsedIndexPatternData = this.parseIndexPattern(indexPatternData); + if (!parsedIndexPatternData.title || !parsedIndexPatternData.id) { + throw new Error(`Invalid index pattern data`); + } + return parsedIndexPatternData; + } catch (error) { + throw new Error(`Error getting index pattern: ${error.message}`); + } + } + async getAll(): Promise { + try { + const savedObjects = await GenericRequest.request( + 'GET', + `/api/saved_objects/_find?type=index-pattern&fields=title&fields=fields&per_page=9999`, + ); + const indexPatterns = ((savedObjects || {}).data || {}).saved_objects || []; + return indexPatterns.map(this.parseIndexPattern); + } catch (error) { + throw new Error(`Error getting index patterns: ${error.message}`); + } + } + + parseIndexPattern(indexPatternData): tParsedIndexPattern { + const title = ((indexPatternData || {}).attributes || {}).title; + const id = (indexPatternData || {}).id; + return { + ...indexPatternData, + id: id, + title: title, + _fields: indexPatternData?.attributes?.fields + ? JSON.parse(indexPatternData.attributes.fields) + : [], + }; + } + + setDefault(dataSource: tDataSource): Promise { + if(!dataSource){ + throw new Error('Index pattern is required'); + } + AppState.setCurrentPattern(dataSource.id); + return Promise.resolve(); + } + getDefault(): Promise | tDataSource | null { + const currentPattern = AppState.getCurrentPattern(); + if(!currentPattern){ + return null; + } + return this.get(currentPattern); + } + +} diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts new file mode 100644 index 0000000000..36eee0e8ec --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts @@ -0,0 +1,47 @@ +import { PatternDataSource } from './pattern-data-source'; +import { getDataPlugin } from '../../../../kibana-services'; + +jest.mock('../../../../kibana-services', () => ({ + ...(jest.requireActual('../../../../kibana-services') as object), + getDataPlugin: () => ({ + // mock indexPatterns getter + indexPatterns: { + get: jest.fn().mockResolvedValue({ + fields: { + replaceAll: jest.fn(), + map: jest.fn().mockReturnValue([]), + }, + getScriptedFields: jest.fn().mockReturnValue([]), + }), + getFieldsForIndexPattern: jest.fn().mockResolvedValue([]), + updateSavedObject: jest.fn().mockResolvedValue({}), + }, + }), +})); + +describe('PatternDataSource', () => { + it('should create a new data source handler', () => { + const patternDataSource = new PatternDataSource('id', 'title'); + expect(patternDataSource).toEqual({ id: 'id', title: 'title' }); + }); + + it('should have the correct id', () => { + const patternDataSource = new PatternDataSource('id', 'title'); + expect(patternDataSource.id).toEqual('id'); + }); + + it('should have the correct title', () => { + const patternDataSource = new PatternDataSource('id', 'title'); + expect(patternDataSource.title).toEqual('title'); + }); + + it.skip('should select the data source', async () => { + const patternDataSource = new PatternDataSource('id', 'title'); + (getDataPlugin().indexPatterns.getFieldsForIndexPattern as jest.Mock).mockResolvedValue([]); + await patternDataSource.select(); + expect(getDataPlugin().indexPatterns.get).toHaveBeenCalledWith('id'); + expect(getDataPlugin().indexPatterns.getFieldsForIndexPattern).toHaveBeenCalledWith({}); + expect(getDataPlugin().indexPatterns.updateSavedObject).toHaveBeenCalledWith({}); + + }); +}); diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts new file mode 100644 index 0000000000..a5b2bbdf13 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts @@ -0,0 +1,30 @@ +import { tDataSource } from "../data-source"; +import { getDataPlugin } from '../../../../kibana-services'; + + +export class PatternDataSource implements tDataSource { + id: string; + title: string; + constructor(id: string, title: string) { + this.id = id; + this.title = title; + } + + async select(){ + try { + const pattern = await getDataPlugin().indexPatterns.get(this.id); + if(pattern){ + const fields = await getDataPlugin().indexPatterns.getFieldsForIndexPattern( + pattern, + ); + const scripted = pattern.getScriptedFields().map(field => field.spec); + pattern.fields.replaceAll([...fields, ...scripted]); + await getDataPlugin().indexPatterns.updateSavedObject(pattern); + }else{ + throw new Error('Error selecting index pattern: pattern not found'); + } + }catch(error){ + throw new Error(`Error selecting index pattern: ${error}`); + } + } +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx b/plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx new file mode 100644 index 0000000000..b4271e4fca --- /dev/null +++ b/plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { useEffect, useState } from 'react'; +import { + EuiFormRow, + EuiSelect, +} from '@elastic/eui'; +import { + tDataSourceSelector +} from '../common/data-source'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../../react-services/error-management'; + +type tWzDataSourceSelector = { + name: 'string'; + onChange?: (dataSource: tDataSource) => void; + dataSourceSelector: tDataSourceSelector; +} + +const WzDataSourceSelector = (props: tWzDataSourceSelector) => { + const { onChange, dataSourceSelector, name = 'data source' } = props; + const [dataSourceList, setDataSourceList] = useState([]); + const [selectedPattern, setSelectedPattern] = useState(); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + loadDataSources(); + }, []) + + async function loadDataSources() { + setIsLoading(true); + const dataSourcesList = await dataSourceSelector.getAllDataSources(); + const defaultIndexPattern = await dataSourceSelector.getSelectedDataSource(); + setSelectedPattern(defaultIndexPattern); + setDataSourceList(dataSourcesList); + setIsLoading(false); + } + + async function selectDataSource(e) { + const dataSourceId = e.target.value; + try { + await dataSourceSelector.selectDataSource(dataSourceId); + setSelectedPattern(await dataSourceSelector.getDataSource(dataSourceId)); + onChange && onChange(selectedPattern); + } catch (error) { + const searchError = ErrorFactory.create(HttpError, { + error, + message: `Error selecting the ${name.toLowerCase()} '${dataSourceId}` + }); + ErrorHandler.handleError(searchError); + } + } + + return ( + + { + return { value: item.id, text: item.title }; + })} + value={selectedPattern?.id} + onChange={selectDataSource} + aria-label={name} + /> + + ); +} + +export default WzDataSourceSelector; \ No newline at end of file diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.tsx similarity index 90% rename from plugins/main/public/components/common/modules/modules-defaults.js rename to plugins/main/public/components/common/modules/modules-defaults.tsx index 7b1705b0e4..80de04594c 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.js +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -44,22 +44,23 @@ import { malwareDetectionColumns } from '../../overview/malware-detection/events import { WAZUH_VULNERABILITIES_PATTERN } from '../../../../common/constants'; import { withVulnerabilitiesStateDataSource } from '../../overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern'; +const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*'; +const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN; + const DashboardTab = { id: 'dashboard', name: 'Dashboard', buttons: [ButtonModuleExploreAgent, ButtonModuleGenerateReport], component: Dashboard, }; -const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*'; -const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN; -const renderDiscoverTab = (indexName = DEFAULT_INDEX_PATTERN, columns) => { +const renderDiscoverTab = (columns, indexPattern) => { return { id: 'events', name: 'Events', buttons: [ButtonModuleExploreAgent], component: () => ( - + ), }; }; @@ -72,7 +73,7 @@ const RegulatoryComplianceTabs = columns => [ buttons: [ButtonModuleExploreAgent], component: ComplianceTable, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, columns), + renderDiscoverTab(columns), ]; export const ModulesDefaults = { @@ -80,7 +81,7 @@ export const ModulesDefaults = { init: 'events', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, threatHuntingColumns), + renderDiscoverTab(threatHuntingColumns), ], availableFor: ['manager', 'agent'], }, @@ -99,7 +100,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: InventoryFim, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, fileIntegrityMonitoringColumns), + renderDiscoverTab(fileIntegrityMonitoringColumns), ], availableFor: ['manager', 'agent'], }, @@ -107,7 +108,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, amazonWebServicesColumns), + renderDiscoverTab(amazonWebServicesColumns), ], availableFor: ['manager', 'agent'], }, @@ -115,7 +116,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, googleCloudColumns), + renderDiscoverTab(googleCloudColumns), ], availableFor: ['manager', 'agent'], }, @@ -143,7 +144,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: MainSca, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, configurationAssessmentColumns), + renderDiscoverTab(configurationAssessmentColumns), ], buttons: ['settings'], availableFor: ['manager', 'agent'], @@ -164,10 +165,9 @@ export const ModulesDefaults = { component: withModuleNotForAgent(OfficePanel), }, { - ...renderDiscoverTab(DEFAULT_INDEX_PATTERN, office365Columns), + ...renderDiscoverTab(office365Columns), component: withModuleNotForAgent(() => ( )), @@ -185,7 +185,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: GitHubPanel, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, githubColumns), + renderDiscoverTab(githubColumns), ], availableFor: ['manager', 'agent'], }, @@ -221,10 +221,9 @@ export const ModulesDefaults = { ], }, { - ...renderDiscoverTab(ALERTS_INDEX_PATTERN, vulnerabilitiesColumns), + ...renderDiscoverTab(vulnerabilitiesColumns), component: withVulnerabilitiesStateDataSource(() => ( )), @@ -264,7 +263,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, dockerColumns), + renderDiscoverTab(dockerColumns), ], availableFor: ['manager', 'agent'], }, diff --git a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx index 0eec91a272..3f8934d464 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -38,6 +38,7 @@ import { search } from '../search-bar'; import { getPlugins } from '../../../kibana-services'; import { histogramChartInput } from './config/histogram-chart'; import { getWazuhCorePlugin } from '../../../kibana-services'; +import { useIndexPattern } from '../hooks/use-index-pattern'; const DashboardByRenderer = getPlugins().dashboard.DashboardContainerByValueRenderer; import './discover.scss'; @@ -46,12 +47,12 @@ import { withErrorBoundary } from '../hocs'; export const MAX_ENTRIES_PER_QUERY = 10000; type WazuhDiscoverProps = { - indexPatternName: string; + defaultIndexPattern?: IndexPattern; tableColumns: tDataGridColumn[]; }; const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { - const { indexPatternName, tableColumns: defaultTableColumns } = props; + const { defaultIndexPattern, tableColumns: defaultTableColumns } = props; const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); const [inspectedHit, setInspectedHit] = useState(undefined); @@ -61,6 +62,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const [isSearching, setIsSearching] = useState(false); const [isExporting, setIsExporting] = useState(false); const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav(); + const [indexPatternTitle, setIndexPatternTitle] = useState(''); const onClickInspectDoc = useMemo( () => (index: number) => { @@ -86,7 +88,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { }; const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: indexPatternName, + defaultIndexPatternID: indexPatternTitle, }); const { isLoading, @@ -117,12 +119,20 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { indexPattern: indexPattern as IndexPattern, }); + const currentIndexPattern = useIndexPattern(); + + useEffect(() => { + if (currentIndexPattern) { + setIndexPattern(currentIndexPattern); + setIndexPatternTitle(currentIndexPattern.title); + } + }, [currentIndexPattern]) + useEffect(() => { - if (!isLoading) { + if (!isLoading && indexPattern) { setIsSearching(true); - setIndexPattern(indexPatterns?.[0] as IndexPattern); search({ - indexPattern: indexPatterns?.[0] as IndexPattern, + indexPattern: indexPattern as IndexPattern, filters, query, pagination, @@ -157,7 +167,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const onClickExportResults = async () => { const params = { - indexPattern: indexPatterns?.[0] as IndexPattern, + indexPattern: indexPattern as IndexPattern, filters, query, fields: columnVisibility.visibleColumns, @@ -214,7 +224,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { { - try { - const newPattern = event.target; - if (!AppState.getPatternSelector()) return; - await PatternHandler.changePattern(newPattern.value); - this.setState({ currentSelectedPattern: newPattern.value }); - if (this.state.currentMenuTab !== 'wazuh-dev') { - this.router.reload(); - } - - if (newPattern?.id === 'selectIndexPatternBar') { - this.updatePatternAndApi(); - } - } catch (error) { - const options = { - context: `${WzMenu.name}.changePattern`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - store: false, - display: true, - error: { - error: error, - message: error.message || error, - title: `Error changing the Index Pattern`, - }, - }; - getErrorOrchestrator().handleError(options); - } - }; - updatePatternAndApi = async () => { this.setState({ menuOpened: false, @@ -429,38 +405,6 @@ export const WzMenu = withWindowSize( } }; - buildPatternSelector() { - return ( - - { - return { value: item.id, text: item.title }; - })} - value={this.state.currentSelectedPattern} - onChange={this.changePattern} - aria-label='Index pattern selector' - /> - - ); - } - - buildApiSelector() { - return ( - - { - return { value: item.id, text: item.id }; - })} - value={this.state.currentAPI} - onChange={this.changeAPI} - aria-label='API selector' - /> - - ); - } - buildWazuhNotReadyYet() { const container = document.getElementsByClassName('wazuhNotReadyYet'); return ReactDOM.createPortal( @@ -517,20 +461,6 @@ export const WzMenu = withWindowSize( }); } - thereAreSelectors() { - return ( - (AppState.getAPISelector() && - this.state.currentAPI && - this.state.APIlist && - this.state.APIlist.length > 1) || - !this.state.currentAPI || - (AppState.getPatternSelector() && - this.state.theresPattern && - this.state.patternList && - this.state.patternList.length > 1) - ); - } - getApiSelectorComponent() { let style = { minWidth: 100, textOverflow: 'ellipsis' }; if (this.showSelectorsInPopover) { @@ -560,6 +490,30 @@ export const WzMenu = withWindowSize( ); } + onChangePattern = async pattern => { + try { + this.setState({ currentSelectedPattern: pattern.id }); + if (this.state.currentMenuTab !== 'wazuh-dev') { + this.router.reload(); + } + await this.updatePatternAndApi(); + } catch (error) { + const options = { + context: `${WzMenu.name}.onChangePattern`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: false, + display: true, + error: { + error: error, + message: error.message || error, + title: `Error changing the Index Pattern`, + }, + }; + getErrorOrchestrator().handleError(options); + } + }; + getIndexPatternSelectorComponent() { let style = { maxWidth: 200, maxHeight: 50 }; if (this.showSelectorsInPopover) { @@ -571,19 +525,12 @@ export const WzMenu = withWindowSize(

Index pattern

-
- { - return { value: item.id, text: item.title }; - })} - value={this.state.currentSelectedPattern} - onChange={this.changePattern} - aria-label='Index pattern selector' - /> +