Skip to content

Commit

Permalink
Vulnerability dashboard error loading for read only user (#6993)
Browse files Browse the repository at this point in the history
* 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 <federico.rodriguez@wazuh.com>
  • Loading branch information
guidomodarelli and asteriscos authored Sep 16, 2024
1 parent d7ee5e8 commit 952bf49
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -56,7 +55,6 @@ type tUseDataSourceLoadedReturns<K> = {
fetchData: (params: Omit<tSearchParams, 'filters'>) => Promise<any>;
setFilters: (filters: tFilter[]) => void;
filterManager: PatternDataSourceFilterManager;
fetchDateRange: TimeRange;
};

type tUseDataSourceNotLoadedReturns = {
Expand Down Expand Up @@ -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],
Expand All @@ -118,7 +118,6 @@ export function useDataSource<
const [allFilters, setAllFilters] = useState<tFilter[]>([]);
const pinnedAgentManager = new PinnedAgentManager();
const pinnedAgent = pinnedAgentManager.getPinnedAgent();
const [fetchDateRange, setFetchDateRange] = useState<TimeRange>();
const { isComponentMounted, getAbortController } = useIsMounted();

const setFilters = (filters: tFilter[]) => {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -218,7 +221,6 @@ export function useDataSource<
fetchData,
setFilters,
filterManager: dataSourceFilterManager as PatternDataSourceFilterManager,
fetchDateRange,
};
}
}
Original file line number Diff line number Diff line change
@@ -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<IndexPatternsService>;
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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions plugins/main/test/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type RecordMock<T> = {
[K in keyof T]: T[K] extends Function
? jest.Mock
: T[K] extends {}
? RecordMock<T[K]>
: T[K];
};
export type PartialRecordMock<T> = Partial<{
[K in keyof T]: T[K] extends Function
? jest.Mock
: T[K] extends {}
? PartialRecordMock<T[K]>
: T[K];
}>;

0 comments on commit 952bf49

Please sign in to comment.