From 952bf49c81d6cfe9572d505c3a992ed381c0fe17 Mon Sep 17 00:00:00 2001 From: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:56:30 -0300 Subject: [PATCH] Vulnerability dashboard error loading for read only user (#6993) * Refactor useDataSource hook for improved error handling * Add comment for vulnerability dashboard error handling * Refactor data source hook and remove unused TimeRange * Fix issue causing vulnerability dashboard load failure for read-only users * Fix Prettier issues * Add tests for PatternDataSource class and RecordMock types * Fix syntax error in PatternDataSource file * Fix Prettier issues * Prettier --------- Co-authored-by: Federico Rodriguez --- CHANGELOG.md | 1 + .../data-source/hooks/use-data-source.ts | 86 ++++++++++--------- .../pattern/pattern-data-source.test.ts | 47 ++++++++++ .../pattern/pattern-data-source.ts | 27 +++--- plugins/main/test/types/index.ts | 14 +++ 5 files changed, 121 insertions(+), 54 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts create mode 100644 plugins/main/test/types/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c85d6083d8..fe7bcd7abb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the Wazuh app project will be documented in this file. ### Fixed +- Fixed issue causing vulnerability dashboard to fail loading for read-only users [#6933](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6993) - Fixed the temporal directory variable on the the command to deploy a new Windows agent [#6905](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6905) - Fixed an error on the command to deploy a new macOS agent that could cause the registration password had a wrong value because a `\n` could be included [#6906](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6906) - Fixed rendering an active response as disabled when is active [#6901](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6901) diff --git a/plugins/main/public/components/common/data-source/hooks/use-data-source.ts b/plugins/main/public/components/common/data-source/hooks/use-data-source.ts index 1795411bb6..6a026e7b3a 100644 --- a/plugins/main/public/components/common/data-source/hooks/use-data-source.ts +++ b/plugins/main/public/components/common/data-source/hooks/use-data-source.ts @@ -11,7 +11,6 @@ import { PatternDataSourceFilterManager, tFilterManager, } from '../index'; -import { TimeRange } from '../../../../../../../src/plugins/data/public'; import { createOsdUrlStateStorage } from '../../../../../../../src/plugins/opensearch_dashboards_utils/public'; import NavigationService from '../../../../react-services/navigation-service'; import { OSD_URL_STATE_STORAGE_ID } from '../../../../../common/constants'; @@ -56,7 +55,6 @@ type tUseDataSourceLoadedReturns = { fetchData: (params: Omit) => Promise; setFilters: (filters: tFilter[]) => void; filterManager: PatternDataSourceFilterManager; - fetchDateRange: TimeRange; }; type tUseDataSourceNotLoadedReturns = { @@ -92,8 +90,10 @@ export function useDataSource< useHash: config.get(OSD_URL_STATE_STORAGE_ID), history: history, }); - const appDefaultFilters = osdUrlStateStorage.get('_a')?.filters ?? []; - const globalDefaultFilters = osdUrlStateStorage.get('_g')?.filters ?? []; + const appDefaultFilters = + osdUrlStateStorage.get<{ filters: [] }>('_a')?.filters ?? []; + const globalDefaultFilters = + osdUrlStateStorage.get<{ filters: [] }>('_g')?.filters ?? []; const defaultFilters = [...appDefaultFilters, ...globalDefaultFilters]; const { filters: initialFilters = [...defaultFilters], @@ -118,7 +118,6 @@ export function useDataSource< const [allFilters, setAllFilters] = useState([]); const pinnedAgentManager = new PinnedAgentManager(); const pinnedAgent = pinnedAgentManager.getPinnedAgent(); - const [fetchDateRange, setFetchDateRange] = useState(); const { isComponentMounted, getAbortController } = useIsMounted(); const setFilters = (filters: tFilter[]) => { @@ -144,43 +143,47 @@ export function useDataSource< (async () => { setIsLoading(true); - const factory = injectedFactory || new PatternDataSourceFactory(); - const patternsData = await repository.getAll(); - const dataSources = await factory.createAll( - DataSourceConstructor, - patternsData, - ); - const selector = new PatternDataSourceSelector(dataSources, repository); - const dataSource = await selector.getSelectedDataSource(); - if (!dataSource) { - throw new Error('No valid data source found'); + try { + const factory = injectedFactory || new PatternDataSourceFactory(); + const patternsData = await repository.getAll(); + const dataSources = await factory.createAll( + DataSourceConstructor, + patternsData, + ); + const selector = new PatternDataSourceSelector(dataSources, repository); + const dataSource = await selector.getSelectedDataSource(); + if (!dataSource) { + throw new Error('No valid data source found'); + } + if (!isComponentMounted()) return; + setDataSource(dataSource); + const dataSourceFilterManager = new PatternDataSourceFilterManager( + dataSource, + initialFilters, + injectedFilterManager, + initialFetchFilters, + ); + // what the filters update + subscription = dataSourceFilterManager.getUpdates$().subscribe({ + next: () => { + if (!isComponentMounted()) return; + // this is necessary to remove the hidden filters from the filter manager and not show them in the search bar + dataSourceFilterManager.setFilters( + dataSourceFilterManager.getFilters(), + ); + setAllFilters(dataSourceFilterManager.getFilters()); + setFetchFilters(dataSourceFilterManager.getFetchFilters()); + setFixedFilters(dataSourceFilterManager.getFixedFilters()); + }, + }); + setAllFilters(dataSourceFilterManager.getFilters()); + setFetchFilters(dataSourceFilterManager.getFetchFilters()); + setFixedFilters(dataSourceFilterManager.getFixedFilters()); + setDataSourceFilterManager(dataSourceFilterManager); + } catch { + } finally { + setIsLoading(false); } - if (!isComponentMounted()) return; - setDataSource(dataSource); - const dataSourceFilterManager = new PatternDataSourceFilterManager( - dataSource, - initialFilters, - injectedFilterManager, - initialFetchFilters, - ); - // what the filters update - subscription = dataSourceFilterManager.getUpdates$().subscribe({ - next: () => { - if (!isComponentMounted()) return; - // this is necessary to remove the hidden filters from the filter manager and not show them in the search bar - dataSourceFilterManager.setFilters( - dataSourceFilterManager.getFilters(), - ); - setAllFilters(dataSourceFilterManager.getFilters()); - setFetchFilters(dataSourceFilterManager.getFetchFilters()); - setFixedFilters(dataSourceFilterManager.getFixedFilters()); - }, - }); - setAllFilters(dataSourceFilterManager.getFilters()); - setFetchFilters(dataSourceFilterManager.getFetchFilters()); - setFixedFilters(dataSourceFilterManager.getFixedFilters()); - setDataSourceFilterManager(dataSourceFilterManager); - setIsLoading(false); })(); return () => { @@ -218,7 +221,6 @@ export function useDataSource< fetchData, setFilters, filterManager: dataSourceFilterManager as PatternDataSourceFilterManager, - fetchDateRange, }; } } 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..abaa0ce30e --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts @@ -0,0 +1,47 @@ +import { IndexPatternsService } from '../../../../../../../src/plugins/data/common'; +import { RecordMock } from '../../../../../test/types'; +import { PatternDataSource } from './pattern-data-source'; + +let patternService: RecordMock; +let patternDataSource: PatternDataSource; +const TEST_ID = 'test-id'; +const TEST_TITLE = 'test-title'; + +describe('PatternDataSource', () => { + beforeEach(() => { + patternDataSource = new PatternDataSource(TEST_ID, TEST_TITLE); + // @ts-expect-error + patternService = { + get: jest.fn().mockImplementation(() => ({ + getScriptedFields: jest.fn().mockImplementation(() => []), + fields: { + replaceAll: jest.fn(), + }, + })), + getFieldsForIndexPattern: jest.fn().mockResolvedValue([]), + updateSavedObject: jest.fn(), + }; + // @ts-expect-error + patternDataSource.patternService = patternService; + }); + + it('should throw error when pattern not found', () => { + patternService.get.mockResolvedValue(undefined); + expect(async () => { + await patternDataSource.select(); + }).rejects.toThrow( + 'Error selecting index pattern: Error: Error selecting index pattern: pattern not found', + ); + }); + + it('should throw error when get fields for index pattern rejects', () => { + patternService.getFieldsForIndexPattern.mockRejectedValue(null); + expect(async () => { + await patternDataSource.select(); + }).rejects.toThrow('Error selecting index pattern: null'); + }); + + it('should not throw error when selecting from pattern data source', async () => { + await expect(patternDataSource.select()).resolves.not.toThrow(); + }); +}); 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 index 0301a3aee2..6cc4b22a23 100644 --- 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 @@ -6,7 +6,7 @@ import { } from '../index'; import { getDataPlugin } from '../../../../kibana-services'; import { - IndexPatternsContract, + IndexPatternsService, IndexPattern, } from '../../../../../../../src/plugins/data/public'; import { search } from '../../search-bar/search-bar-service'; @@ -16,7 +16,7 @@ export class PatternDataSource implements tDataSource { id: string; title: string; fields: any[]; - patternService: IndexPatternsContract; + patternService: IndexPatternsService; indexPattern: IndexPattern; constructor(id: string, title: string) { @@ -44,21 +44,24 @@ export class PatternDataSource implements tDataSource { } async select() { + let pattern: IndexPattern; try { - const pattern = await this.patternService.get(this.id); - if (pattern) { - const fields = await this.patternService.getFieldsForIndexPattern( - pattern, - ); - const scripted = pattern.getScriptedFields().map(field => field.spec); - pattern.fields.replaceAll([...fields, ...scripted]); - await this.patternService.updateSavedObject(pattern); - } else { + pattern = await this.patternService.get(this.id); + if (!pattern) throw new Error('Error selecting index pattern: pattern not found'); - } + + const fields = await this.patternService.getFieldsForIndexPattern( + pattern, + ); + const scripted = pattern.getScriptedFields().map(field => field.spec); + pattern.fields.replaceAll([...fields, ...scripted]); } catch (error) { throw new Error(`Error selecting index pattern: ${error}`); } + try { + // Vulnerability dashboard error loading for read only user + await this.patternService.updateSavedObject(pattern); + } catch {} } async fetch(params: tSearchParams) { diff --git a/plugins/main/test/types/index.ts b/plugins/main/test/types/index.ts new file mode 100644 index 0000000000..f10772d5ea --- /dev/null +++ b/plugins/main/test/types/index.ts @@ -0,0 +1,14 @@ +export type RecordMock = { + [K in keyof T]: T[K] extends Function + ? jest.Mock + : T[K] extends {} + ? RecordMock + : T[K]; +}; +export type PartialRecordMock = Partial<{ + [K in keyof T]: T[K] extends Function + ? jest.Mock + : T[K] extends {} + ? PartialRecordMock + : T[K]; +}>;