From 786f4b3f5301ca79e8bb0a5b2608f85afe7064d5 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 21 Feb 2024 15:45:15 -0300 Subject: [PATCH 01/36] Added useIndexPattern from AppState on discover component --- .../common/modules/modules-defaults.js | 26 +++++++++---------- .../common/wazuh-discover/wz-discover.tsx | 26 +++++++++++++------ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.js index 5e4e824109..3325b6485e 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.js +++ b/plugins/main/public/components/common/modules/modules-defaults.js @@ -49,13 +49,13 @@ const DashboardTab = { 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: () => ( - + ), }; }; @@ -75,7 +75,7 @@ const RegulatoryComplianceTabs = columns => [ buttons: [ButtonModuleExploreAgent], component: ComplianceTable, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, columns), + renderDiscoverTab(columns), ]; export const ModulesDefaults = { @@ -83,7 +83,7 @@ export const ModulesDefaults = { init: 'events', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, threatHuntingColumns), + renderDiscoverTab(threatHuntingColumns), ], availableFor: ['manager', 'agent'], }, @@ -102,7 +102,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: InventoryFim, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, fileIntegrityMonitoringColumns), + renderDiscoverTab(fileIntegrityMonitoringColumns), ], availableFor: ['manager', 'agent'], }, @@ -110,7 +110,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, amazonWebServicesColumns), + renderDiscoverTab(amazonWebServicesColumns), ], availableFor: ['manager', 'agent'], }, @@ -118,7 +118,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, googleCloudColumns), + renderDiscoverTab(googleCloudColumns), ], availableFor: ['manager', 'agent'], }, @@ -147,7 +147,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: MainSca, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, configurationAssessmentColumns), + renderDiscoverTab(configurationAssessmentColumns), ], buttons: ['settings'], availableFor: ['manager', 'agent'], @@ -168,10 +168,9 @@ export const ModulesDefaults = { component: withModuleNotForAgent(OfficePanel), }, { - ...renderDiscoverTab(DEFAULT_INDEX_PATTERN, office365Columns), + ...renderDiscoverTab(office365Columns), component: withModuleNotForAgent(() => ( )), @@ -189,7 +188,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: GitHubPanel, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, githubColumns), + renderDiscoverTab(githubColumns), ], availableFor: ['manager', 'agent'], }, @@ -214,10 +213,9 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], }, { - ...renderDiscoverTab(ALERTS_INDEX_PATTERN, vulnerabilitiesColumns), + ...renderDiscoverTab(vulnerabilitiesColumns), component: withModuleNotForAgent(() => ( )), @@ -254,7 +252,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) => { Date: Thu, 29 Feb 2024 19:26:06 -0300 Subject: [PATCH 02/36] Added factory, handler and data source class with unit tests --- .../common/data-source/data-source-factory.ts | 7 ++ .../data-source/data-source-handler.test.ts | 37 +++++++++++ .../common/data-source/data-source-handler.ts | 24 +++++++ .../data-source/data-source-selector.test.tsx | 14 ++++ .../data-source/data-source-selector.tsx | 27 ++++++++ .../common/data-source/data-source.ts | 4 ++ .../components/common/data-source/index.ts | 6 ++ .../indexer-data-source-factory.ts | 15 +++++ .../data-source/indexer-data-source.test.ts | 8 +++ .../common/data-source/indexer-data-source.ts | 65 +++++++++++++++++++ ...dules-defaults.js => modules-defaults.tsx} | 2 - 11 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/data-source-factory.ts create mode 100644 plugins/main/public/components/common/data-source/data-source-handler.test.ts create mode 100644 plugins/main/public/components/common/data-source/data-source-handler.ts create mode 100644 plugins/main/public/components/common/data-source/data-source-selector.test.tsx create mode 100644 plugins/main/public/components/common/data-source/data-source-selector.tsx create mode 100644 plugins/main/public/components/common/data-source/data-source.ts create mode 100644 plugins/main/public/components/common/data-source/index.ts create mode 100644 plugins/main/public/components/common/data-source/indexer-data-source-factory.ts create mode 100644 plugins/main/public/components/common/data-source/indexer-data-source.test.ts create mode 100644 plugins/main/public/components/common/data-source/indexer-data-source.ts rename plugins/main/public/components/common/modules/{modules-defaults.js => modules-defaults.tsx} (98%) 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..b9d1b489bf --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-factory.ts @@ -0,0 +1,7 @@ +import { tDataSource } from './data-source'; + +export abstract class DataSourceFactory { + abstract getDataSources(): tDataSource[]; + abstract createDataSources(): tDataSource[]; + abstract validateDataSources(dataSources: tDataSource[]): boolean; +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-handler.test.ts b/plugins/main/public/components/common/data-source/data-source-handler.test.ts new file mode 100644 index 0000000000..9a68083100 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-handler.test.ts @@ -0,0 +1,37 @@ +import { DataSourceHandler } from './data-source-handler'; +import { DataSourceFactory } from './data-source-factory'; + +describe('DataSourceHandler', () => { + it('should return ERROR when the handler not receive a valid factory', () => { + try { + new DataSourceHandler({} as any); + }catch(error){ + expect(error.message).toBe('Invalid data source factory'); + } + }) + + it('should return ERROR when the handler not receive a factory', () => { + try { + new DataSourceHandler(null as any); + }catch(error){ + expect(error.message).toBe('Data source factory is required'); + } + }) + + it('should create a new data source handler when receive a valid factory', () => { + class ExampleFactory extends DataSourceFactory { + validateDataSources(dataSources: any): boolean { + throw new Error("Method not implemented."); + } + createDataSources(): any { + throw new Error("Method not implemented."); + } + getDataSources(): any { + return []; + } + } + + const dataSourceHandler = new DataSourceHandler(new ExampleFactory()); + expect(dataSourceHandler).toBeTruthy(); + }) +}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-handler.ts b/plugins/main/public/components/common/data-source/data-source-handler.ts new file mode 100644 index 0000000000..1cebd372dc --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-handler.ts @@ -0,0 +1,24 @@ +import { tDataSource } from "./data-source"; +import { DataSourceFactory } from './data-source-factory'; + +export class DataSourceHandler { + private dataSourceFactory: DataSourceFactory; + + constructor(dataSourceFactory: DataSourceFactory) { + if(!dataSourceFactory) { + throw new Error('Data source factory is required'); + } + // check if a DataSourceFactory instance + if(!(dataSourceFactory instanceof DataSourceFactory)){ + throw new Error('Invalid data source factory'); + } + + + this.dataSourceFactory = dataSourceFactory; + } + + getDataSources(): tDataSource[] { + return this.dataSourceFactory.getDataSources(); + } + +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-selector.test.tsx b/plugins/main/public/components/common/data-source/data-source-selector.test.tsx new file mode 100644 index 0000000000..9075b8b05e --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-selector.test.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import DataSourceSelector from './data-source-selector'; +import '@testing-library/jest-dom/extend-expect'; + +describe('DataSourceSelector', () => { + + it('should render', () => { + // use react testing library + const { container } = render(); + expect(container).toBeInTheDocument(); + }) + +}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-selector.tsx b/plugins/main/public/components/common/data-source/data-source-selector.tsx new file mode 100644 index 0000000000..8d85a7aae4 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-selector.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useEffect } from 'react'; +import { DataSourceHandler } from '../data-source'; +import { IndexerDataSourceFactory } from './indexer-data-source-factory'; + +const DataSourceSelector = () => { + + useEffect(() => { + loadIndexPatterns(); + }, []) + + async function loadIndexPatterns() { + // pass to the data source handler the data source factory + // the data source handler will create the data source + const dataSourceHandler = new DataSourceHandler(new IndexerDataSourceFactory()); + const dataSourcesList = dataSourceHandler.getDataSources(); + } + + return ( +
+ select +
+ ) +} + +export default DataSourceSelector; + 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..7548aa579a --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source.ts @@ -0,0 +1,4 @@ +export type tDataSource = { + id: string; + title: string; +} \ 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..c5e6080f22 --- /dev/null +++ b/plugins/main/public/components/common/data-source/index.ts @@ -0,0 +1,6 @@ +export * from './data-source'; +export * from './data-source-handler'; +export * from './data-source-factory'; +export * from './indexer-data-source-factory'; +export * from './indexer-data-source'; +export * from './data-source-selector'; diff --git a/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts b/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts new file mode 100644 index 0000000000..450205092d --- /dev/null +++ b/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts @@ -0,0 +1,15 @@ +import { tDataSource } from "./data-source"; +import { DataSourceFactory } from "./data-source-factory"; +import { IndexerDataSource } from "./indexer-data-source"; + +export class IndexerDataSourceFactory extends DataSourceFactory { + validateDataSources(dataSources: tDataSource[]): boolean { + throw new Error("Method not implemented."); + } + createDataSources(): tDataSource[] { + throw new Error("Method not implemented."); + } + getDataSources(): tDataSource[] { + return [new IndexerDataSource('id','title')]; + } +} diff --git a/plugins/main/public/components/common/data-source/indexer-data-source.test.ts b/plugins/main/public/components/common/data-source/indexer-data-source.test.ts new file mode 100644 index 0000000000..c9b7923a46 --- /dev/null +++ b/plugins/main/public/components/common/data-source/indexer-data-source.test.ts @@ -0,0 +1,8 @@ +import { IndexerDataSource } from './indexer-data-source'; + +describe('IndexerDataSource', () => { + it('should create a new data source handler', () => { + const dataSourceHandler = new IndexerDataSource('id', 'title'); + expect(dataSourceHandler).toEqual({ id: 'id', title: 'title' }); + }) +}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/indexer-data-source.ts b/plugins/main/public/components/common/data-source/indexer-data-source.ts new file mode 100644 index 0000000000..facd468b38 --- /dev/null +++ b/plugins/main/public/components/common/data-source/indexer-data-source.ts @@ -0,0 +1,65 @@ +import { tDataSource } from "./data-source"; + + +/* Use case load DataSource index patterns + +- load index patterns service - indexPatternService from plugin + +- load index patterns available + * await PatternHandler.getPatternList('api') : IndexPattern[] + type indexPattern = { + id: string; + title: string + } + + * receive the default patterns and validate + SavedObject.getListOfWazuhValidIndexPatterns(defaultPatterns, origin) + SavedObject.getListOfWazuhValidIndexPatterns(IndePattern[]], 'api'); + + * to validate request the saved_objects `/api/saved_objects/_find?type=index-pattern&fields=title&fields=fields&per_page=9999` - find all type=index-pattern + type savedObjects = { + attributes: { + fields: "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"cluster.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"configSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"configSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"configSum\"}}},{\"name\":\"dateAdd\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"disconnection_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group\"}}},{\"name\":\"group_config_status\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group_config_status.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group_config_status\"}}},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"lastKeepAlive\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manager\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manager.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"manager\"}}},{\"name\":\"mergedSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"mergedSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"mergedSum\"}}},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"node_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"node_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"node_name\"}}},{\"name\":\"os.arch\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.arch.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.arch\"}}},{\"name\":\"os.build\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.build.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.build\"}}},{\"name\":\"os.major\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.major.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.major\"}}},{\"name\":\"os.minor\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.minor.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.minor\"}}},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.name\"}}},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.platform.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.platform\"}}},{\"name\":\"os.uname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.uname.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.uname\"}}},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.version\"}}},{\"name\":\"registerIP\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"registerIP.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"registerIP\"}}},{\"name\":\"status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"version\"}}}]" + title: "wazuh-monitoring-*" + }, + id: 'wazuh-monitoring-*; + migrationVersion: { + index-pattern: '7.6.0' + }, + namespace: ['default'], + references: [], + score: 0, + type: "index-pattern" + updated_at: "2024-01-09T16:37:31.020Z" + version: "WzQsMV0=" + } + +- get the fields from the indexPattern /api/saved_objects/index-pattern/${patternID}?fields=title&fields=fields. +(this adds the _fields into index patterns list) +- why validateIndexPattern its only check if the index have the following fields ? +this filter and remove the wazuh-monitoring and wazuh-statistics index pattern + +static validateIndexPatterns(list) { + const requiredFields = ['timestamp', 'rule.groups', 'manager.name', 'agent.id']; + return list.filter(item => item && item._fields && requiredFields.every(reqField => item._fields.some(field => field.name === reqField))); +} +- validate comparing list of patterns and saved_objects requested + +- ONLY RETURNS title and id + + +*/ + +// get the valid list of index patterns patternHandler.getPatternList('api'); the flag is to difference between api or health-check + + + + +export class IndexerDataSource implements tDataSource { + id: string; + title: string; + constructor(id: string, title: string) { + this.id = id; + this.title = title; + } +} \ 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 98% rename from plugins/main/public/components/common/modules/modules-defaults.js rename to plugins/main/public/components/common/modules/modules-defaults.tsx index 3325b6485e..b365d7fe74 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.js +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -46,8 +46,6 @@ const DashboardTab = { buttons: [ButtonModuleExploreAgent, ButtonModuleGenerateReport], component: Dashboard, }; -const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*'; -const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN; const renderDiscoverTab = (columns, indexPattern) => { return { From 082c9b32f1bb841993c77088da63ee315f837fbe Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Fri, 1 Mar 2024 18:15:41 -0300 Subject: [PATCH 03/36] Improved data source management --- .../alerts-data-source-repository.ts | 38 +++++++++++++++ .../common/data-source/data-source-factory.ts | 10 ++-- .../data-source/data-source-handler.test.ts | 37 -------------- .../common/data-source/data-source-handler.ts | 24 ---------- .../data-source/data-source-repository.ts | 6 +++ .../data-source/data-source-selector.tsx | 27 ----------- .../data-source/data-source-service.test.ts | 42 ++++++++++++++++ .../common/data-source/data-source-service.ts | 23 +++++++++ .../common/data-source/data-source.ts | 1 - .../components/common/data-source/index.ts | 9 ++-- .../indexer-data-source-factory.ts | 27 ++++++----- .../indexer-data-source-repository.ts | 48 +++++++++++++++++++ .../common/data-source/indexer-data-source.ts | 16 ++++++- ...tor.test.tsx => pattern-selector.test.tsx} | 7 ++- .../common/data-source/pattern-selector.tsx | 48 +++++++++++++++++++ 15 files changed, 246 insertions(+), 117 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/alerts-data-source-repository.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source-handler.test.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source-handler.ts create mode 100644 plugins/main/public/components/common/data-source/data-source-repository.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source-selector.tsx create mode 100644 plugins/main/public/components/common/data-source/data-source-service.test.ts create mode 100644 plugins/main/public/components/common/data-source/data-source-service.ts create mode 100644 plugins/main/public/components/common/data-source/indexer-data-source-repository.ts rename plugins/main/public/components/common/data-source/{data-source-selector.test.tsx => pattern-selector.test.tsx} (52%) create mode 100644 plugins/main/public/components/common/data-source/pattern-selector.tsx diff --git a/plugins/main/public/components/common/data-source/alerts-data-source-repository.ts b/plugins/main/public/components/common/data-source/alerts-data-source-repository.ts new file mode 100644 index 0000000000..9e232544c9 --- /dev/null +++ b/plugins/main/public/components/common/data-source/alerts-data-source-repository.ts @@ -0,0 +1,38 @@ +import { IndexerDataSourceRepository } from "."; + +const ALERTS_REQUIRED_FIELDS = ['timestamp', 'rule.groups', 'manager.name', 'agent.id']; + +export class AlertsDataSourceRepository extends IndexerDataSourceRepository { + 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/data-source-factory.ts b/plugins/main/public/components/common/data-source/data-source-factory.ts index b9d1b489bf..993a78141a 100644 --- a/plugins/main/public/components/common/data-source/data-source-factory.ts +++ b/plugins/main/public/components/common/data-source/data-source-factory.ts @@ -1,7 +1,5 @@ -import { tDataSource } from './data-source'; - -export abstract class DataSourceFactory { - abstract getDataSources(): tDataSource[]; - abstract createDataSources(): tDataSource[]; - abstract validateDataSources(dataSources: tDataSource[]): boolean; +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-handler.test.ts b/plugins/main/public/components/common/data-source/data-source-handler.test.ts deleted file mode 100644 index 9a68083100..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-handler.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { DataSourceHandler } from './data-source-handler'; -import { DataSourceFactory } from './data-source-factory'; - -describe('DataSourceHandler', () => { - it('should return ERROR when the handler not receive a valid factory', () => { - try { - new DataSourceHandler({} as any); - }catch(error){ - expect(error.message).toBe('Invalid data source factory'); - } - }) - - it('should return ERROR when the handler not receive a factory', () => { - try { - new DataSourceHandler(null as any); - }catch(error){ - expect(error.message).toBe('Data source factory is required'); - } - }) - - it('should create a new data source handler when receive a valid factory', () => { - class ExampleFactory extends DataSourceFactory { - validateDataSources(dataSources: any): boolean { - throw new Error("Method not implemented."); - } - createDataSources(): any { - throw new Error("Method not implemented."); - } - getDataSources(): any { - return []; - } - } - - const dataSourceHandler = new DataSourceHandler(new ExampleFactory()); - expect(dataSourceHandler).toBeTruthy(); - }) -}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-handler.ts b/plugins/main/public/components/common/data-source/data-source-handler.ts deleted file mode 100644 index 1cebd372dc..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-handler.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { tDataSource } from "./data-source"; -import { DataSourceFactory } from './data-source-factory'; - -export class DataSourceHandler { - private dataSourceFactory: DataSourceFactory; - - constructor(dataSourceFactory: DataSourceFactory) { - if(!dataSourceFactory) { - throw new Error('Data source factory is required'); - } - // check if a DataSourceFactory instance - if(!(dataSourceFactory instanceof DataSourceFactory)){ - throw new Error('Invalid data source factory'); - } - - - this.dataSourceFactory = dataSourceFactory; - } - - getDataSources(): tDataSource[] { - return this.dataSourceFactory.getDataSources(); - } - -} \ 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..841d465a1f --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-repository.ts @@ -0,0 +1,6 @@ +import { tDataSource } from "./data-source"; + +export interface DataSourceRepository { + get(id: string): Promise; + getAll(): Promise; +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-selector.tsx b/plugins/main/public/components/common/data-source/data-source-selector.tsx deleted file mode 100644 index 8d85a7aae4..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-selector.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { useEffect } from 'react'; -import { DataSourceHandler } from '../data-source'; -import { IndexerDataSourceFactory } from './indexer-data-source-factory'; - -const DataSourceSelector = () => { - - useEffect(() => { - loadIndexPatterns(); - }, []) - - async function loadIndexPatterns() { - // pass to the data source handler the data source factory - // the data source handler will create the data source - const dataSourceHandler = new DataSourceHandler(new IndexerDataSourceFactory()); - const dataSourcesList = dataSourceHandler.getDataSources(); - } - - return ( -
- select -
- ) -} - -export default DataSourceSelector; - diff --git a/plugins/main/public/components/common/data-source/data-source-service.test.ts b/plugins/main/public/components/common/data-source/data-source-service.test.ts new file mode 100644 index 0000000000..f6ee836767 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-service.test.ts @@ -0,0 +1,42 @@ +import { DataSourceService } from './data-source-service'; +import { DataSourceRepository } from './data-source-repository'; +import { tDataSource } from './data-source'; + +describe('DataSourceService', () => { + it('should return ERROR when the handler not receive a valid factory', () => { + try { + new DataSourceService({} as any); + }catch(error){ + expect(error.message).toBe('Invalid data source factory'); + } + }) + + it('should return ERROR when the handler not receive a factory', () => { + try { + new DataSourceService(null as any); + }catch(error){ + expect(error.message).toBe('Data source factory is required'); + } + }) + + it('should create a new data source handler when receive a valid factory', () => { + class ExampleFactory implements DataSourceRepository { + get(id: string): Promise { + throw new Error('Method not implemented.'); + } + getAll(): Promise { + throw new Error('Method not implemented.'); + } + validateAll(dataSources: tDataSource[]): boolean { + throw new Error('Method not implemented.'); + } + validate(dataSource: tDataSource): boolean { + throw new Error('Method not implemented.'); + } + + } + + const dataSourceService = new DataSourceService(new ExampleFactory()); + expect(dataSourceService).toBeTruthy(); + }) +}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-service.ts b/plugins/main/public/components/common/data-source/data-source-service.ts new file mode 100644 index 0000000000..a0037084d3 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-service.ts @@ -0,0 +1,23 @@ +import { tDataSource } from "./data-source"; +import { DataSourceRepository } from './data-source-repository'; + +export class DataSourceService { + constructor(private repository: DataSourceRepository, private factory: DataSourceFactory) { + if(!repository) { + throw new Error('Data source repository is required'); + } + + if(!factory) { + throw new Error('Data source factory is required'); + } + } + + async getAllDataSources(): Promise { + return this.factory.createAll(await this.repository.getAll()); + } + + async getDataSource(id: string): Promise { + return this.factory.create(await this.repository.get(id)); + } + +} \ 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 index 7548aa579a..5d1d821838 100644 --- a/plugins/main/public/components/common/data-source/data-source.ts +++ b/plugins/main/public/components/common/data-source/data-source.ts @@ -1,4 +1,3 @@ export type tDataSource = { id: string; - title: string; } \ 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 index c5e6080f22..a7cdd2989e 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -1,6 +1,9 @@ export * from './data-source'; -export * from './data-source-handler'; +export * from './data-source-service'; +export * from './data-source-repository'; export * from './data-source-factory'; -export * from './indexer-data-source-factory'; +export * from './indexer-data-source-repository'; export * from './indexer-data-source'; -export * from './data-source-selector'; +export * from './indexer-data-source-factory'; +export * from './alerts-data-source-repository'; +export * from './pattern-selector'; diff --git a/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts b/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts index 450205092d..648db9d229 100644 --- a/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts +++ b/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts @@ -1,15 +1,16 @@ -import { tDataSource } from "./data-source"; -import { DataSourceFactory } from "./data-source-factory"; -import { IndexerDataSource } from "./indexer-data-source"; - -export class IndexerDataSourceFactory extends DataSourceFactory { - validateDataSources(dataSources: tDataSource[]): boolean { - throw new Error("Method not implemented."); +import { tDataSourceFactory, IndexerDataSource } from './index' +export class IndexerDataSourceFactory implements tDataSourceFactory { + + create(item: IndexerDataSource): IndexerDataSource { + if(!item){ + throw new Error('Cannot create data source from null or undefined'); + }; + return new IndexerDataSource(item.id, item.title); } - createDataSources(): tDataSource[] { - throw new Error("Method not implemented."); + createAll(items: IndexerDataSource[]): IndexerDataSource[] { + if(!items){ + throw new Error('Cannot create data source from null or undefined'); + }; + return items.map(item => this.create(item)); } - getDataSources(): tDataSource[] { - return [new IndexerDataSource('id','title')]; - } -} +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/indexer-data-source-repository.ts b/plugins/main/public/components/common/data-source/indexer-data-source-repository.ts new file mode 100644 index 0000000000..d577968c87 --- /dev/null +++ b/plugins/main/public/components/common/data-source/indexer-data-source-repository.ts @@ -0,0 +1,48 @@ +import { tDataSource } from "./data-source"; +import { DataSourceRepository } from "./data-source-repository"; +import { IndexerDataSource } from "./indexer-data-source"; +import { GenericRequest } from '../../../react-services/generic-request'; + +export class IndexerDataSourceRepository implements DataSourceRepository { + + async get(id: string): Promise { + try { + const indexPatternData = await GenericRequest.request( + 'GET', + `/api/saved_objects/index-pattern/${id}?fields=title&fields=fields`, + ); + const parsedIndexPatternData = parseIndexPattern(indexPatternData); + if (!parsedIndexPatternData.title || !parsedIndexPatternData.id) { + throw new Error('Index pattern not found'); + } + return parsedIndexPatternData; + } catch (error) { + throw new Error(`Error getting data source: ${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 data source: ${error.message}`); + } + } + + parseIndexPattern(indexPatternData): tDataSource { + const title = ((indexPatternData || {}).attributes || {}).title; + const id = (indexPatternData || {}).id; + return { + ...indexPatternData, + id: id, + title: title, + _fields: indexPatternData?.attributes?.fields + ? JSON.parse(indexPatternData.attributes.fields) + : [], + }; + } +} diff --git a/plugins/main/public/components/common/data-source/indexer-data-source.ts b/plugins/main/public/components/common/data-source/indexer-data-source.ts index facd468b38..4d633d7414 100644 --- a/plugins/main/public/components/common/data-source/indexer-data-source.ts +++ b/plugins/main/public/components/common/data-source/indexer-data-source.ts @@ -51,8 +51,7 @@ static validateIndexPatterns(list) { */ // get the valid list of index patterns patternHandler.getPatternList('api'); the flag is to difference between api or health-check - - +import { getDataPlugin } from '../../../kibana-services'; export class IndexerDataSource implements tDataSource { @@ -62,4 +61,17 @@ export class IndexerDataSource implements tDataSource { this.id = id; this.title = title; } + + async select(){ + // select the current index pattern in the indexPatternService + const pattern = await getDataPlugin().indexPatterns.get(this.id); + console.log('pattern', pattern); + if(pattern){ + await getDataPlugin().indexPatterns.updateSavedObject(pattern); + console.log('pattern default', await getDataPlugin().indexPatterns.getDefault()); + }else{ + throw new Error('Error selecting index pattern'); + } + + } } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-selector.test.tsx b/plugins/main/public/components/common/data-source/pattern-selector.test.tsx similarity index 52% rename from plugins/main/public/components/common/data-source/data-source-selector.test.tsx rename to plugins/main/public/components/common/data-source/pattern-selector.test.tsx index 9075b8b05e..3ba70889ee 100644 --- a/plugins/main/public/components/common/data-source/data-source-selector.test.tsx +++ b/plugins/main/public/components/common/data-source/pattern-selector.test.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { render } from '@testing-library/react'; -import DataSourceSelector from './data-source-selector'; +import PatternSelector from './pattern-selector'; import '@testing-library/jest-dom/extend-expect'; -describe('DataSourceSelector', () => { +describe('PatternSelector', () => { it('should render', () => { - // use react testing library - const { container } = render(); + const { container } = render(); expect(container).toBeInTheDocument(); }) diff --git a/plugins/main/public/components/common/data-source/pattern-selector.tsx b/plugins/main/public/components/common/data-source/pattern-selector.tsx new file mode 100644 index 0000000000..eabc8e9931 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern-selector.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useEffect, useState } from 'react'; +import { DataSourceService, tDataSource, AlertsDataSourceRepository, IndexerDataSourceFactory } from '../data-source'; +import { + EuiFormRow, + EuiSelect, +} from '@elastic/eui'; + +const PatternSelector = () => { + const [dataSourceList, setDataSourceList] = useState([]); + + useEffect(() => { + loadAlertsIndexPatterns(); + }, []) + + async function loadAlertsIndexPatterns() { + // pass to the data source handler the data source factory + // the data source handler will create the data source + const dataSourceHandler = new DataSourceService(new AlertsDataSourceRepository(), new IndexerDataSourceFactory()); + const dataSourcesList = await dataSourceHandler.getAllDataSources(); + setDataSourceList(dataSourcesList); + console.log('list', dataSourcesList); + } + + async function selectDataSource() { + try{ + await dataSourceList[0].select(); + }catch(error){ + console.error('Error selecting index pattern', error); + } + } + + return ( + + { + return { value: item.id, text: item.title }; + })} + value={dataSourceList[0]?.id || null} + onChange={selectDataSource} + aria-label='Index pattern selector' + /> + + ); +} + +export default PatternSelector; \ No newline at end of file From c6a39813fc6dae81b4ab2b0296a0b88cbc5ff912 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Mon, 4 Mar 2024 16:44:57 -0300 Subject: [PATCH 04/36] Added classes and components to select pattern --- .../data-source/data-source-repository.ts | 2 + ...e.test.ts => data-source-selector.test.ts} | 10 +-- .../data-source/data-source-selector.ts | 63 ++++++++++++++++ .../common/data-source/data-source-service.ts | 23 ------ .../common/data-source/data-source.ts | 2 + .../components/common/data-source/index.ts | 8 +- .../indexer-data-source-factory.ts | 16 ---- .../indexer-data-source-repository.ts | 48 ------------ .../data-source/indexer-data-source.test.ts | 8 -- .../common/data-source/pattern-selector.tsx | 48 ------------ .../alerts}/alerts-data-source-repository.ts | 4 +- .../data-source/pattern/alerts/index.ts | 1 + .../common/data-source/pattern/index.ts | 5 ++ .../pattern/pattern-data-source-factory.ts | 16 ++++ .../pattern/pattern-data-source-repository.ts | 75 +++++++++++++++++++ .../pattern/pattern-data-source.test.ts | 8 ++ .../pattern-data-source.ts} | 26 ++++--- .../wz-pattern-selector.test.tsx} | 6 +- .../wz-pattern-selector.tsx | 63 ++++++++++++++++ 19 files changed, 263 insertions(+), 169 deletions(-) rename plugins/main/public/components/common/data-source/{data-source-service.test.ts => data-source-selector.test.ts} (81%) create mode 100644 plugins/main/public/components/common/data-source/data-source-selector.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source-service.ts delete mode 100644 plugins/main/public/components/common/data-source/indexer-data-source-factory.ts delete mode 100644 plugins/main/public/components/common/data-source/indexer-data-source-repository.ts delete mode 100644 plugins/main/public/components/common/data-source/indexer-data-source.test.ts delete mode 100644 plugins/main/public/components/common/data-source/pattern-selector.tsx rename plugins/main/public/components/common/data-source/{ => pattern/alerts}/alerts-data-source-repository.ts (87%) create mode 100644 plugins/main/public/components/common/data-source/pattern/alerts/index.ts create mode 100644 plugins/main/public/components/common/data-source/pattern/index.ts create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.ts create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.ts create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts rename plugins/main/public/components/common/data-source/{indexer-data-source.ts => pattern/pattern-data-source.ts} (92%) rename plugins/main/public/components/{common/data-source/pattern-selector.test.tsx => wz-pattern-selector/wz-pattern-selector.test.tsx} (58%) create mode 100644 plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx 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 index 841d465a1f..d5ff031c0f 100644 --- a/plugins/main/public/components/common/data-source/data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/data-source-repository.ts @@ -3,4 +3,6 @@ import { tDataSource } from "./data-source"; export interface DataSourceRepository { get(id: string): Promise; getAll(): Promise; + setDefault(dataSource: tDataSource): Promise; + getDefault(): Promise; } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-service.test.ts b/plugins/main/public/components/common/data-source/data-source-selector.test.ts similarity index 81% rename from plugins/main/public/components/common/data-source/data-source-service.test.ts rename to plugins/main/public/components/common/data-source/data-source-selector.test.ts index f6ee836767..ae657c66b9 100644 --- a/plugins/main/public/components/common/data-source/data-source-service.test.ts +++ b/plugins/main/public/components/common/data-source/data-source-selector.test.ts @@ -1,11 +1,11 @@ -import { DataSourceService } from './data-source-service'; +import { DataSourceSelector } from './pattern-data-source-selector'; import { DataSourceRepository } from './data-source-repository'; import { tDataSource } from './data-source'; -describe('DataSourceService', () => { +describe('DataSourceSelector', () => { it('should return ERROR when the handler not receive a valid factory', () => { try { - new DataSourceService({} as any); + new DataSourceSelector({} as any); }catch(error){ expect(error.message).toBe('Invalid data source factory'); } @@ -13,7 +13,7 @@ describe('DataSourceService', () => { it('should return ERROR when the handler not receive a factory', () => { try { - new DataSourceService(null as any); + new DataSourceSelector(null as any); }catch(error){ expect(error.message).toBe('Data source factory is required'); } @@ -36,7 +36,7 @@ describe('DataSourceService', () => { } - const dataSourceService = new DataSourceService(new ExampleFactory()); + const dataSourceService = new DataSourceSelector(new ExampleFactory()); expect(dataSourceService).toBeTruthy(); }) }) \ 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..fd307bb5f0 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -0,0 +1,63 @@ +import { DataSourceRepository } from './data-source-repository'; +import { tDataSourceFactory } from './data-source-factory'; +import { tDataSource } from "./data-source"; + +type tDataSourceSelector = { + getAllDataSources: () => Promise; + getDataSource: (id: string) => Promise; + getSelectedDataSource: () => Promise; + selectDataSource: (id: string) => Promise; +} + + +export class DataSourceSelector { + // 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'); + } + } + + async getAllDataSources(): Promise { + // when the map of the data sources is empty, get all the data sources from the repository + 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()); + } + + 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; + } + + + async selectDataSource(id: string): Promise { + const dataSource = await this.getDataSource(id); + if(!dataSource){ + throw new Error('Data source not found'); + } + this.repository.setDefault(dataSource); + } + + async getSelectedDataSource(): Promise { + return await this.repository.getDefault(); + } + + +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-service.ts b/plugins/main/public/components/common/data-source/data-source-service.ts deleted file mode 100644 index a0037084d3..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { tDataSource } from "./data-source"; -import { DataSourceRepository } from './data-source-repository'; - -export class DataSourceService { - constructor(private repository: DataSourceRepository, private factory: DataSourceFactory) { - if(!repository) { - throw new Error('Data source repository is required'); - } - - if(!factory) { - throw new Error('Data source factory is required'); - } - } - - async getAllDataSources(): Promise { - return this.factory.createAll(await this.repository.getAll()); - } - - async getDataSource(id: string): Promise { - return this.factory.create(await this.repository.get(id)); - } - -} \ 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 index 5d1d821838..e9a9f87f48 100644 --- a/plugins/main/public/components/common/data-source/data-source.ts +++ b/plugins/main/public/components/common/data-source/data-source.ts @@ -1,3 +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 index a7cdd2989e..ef95f4ee82 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -1,9 +1,5 @@ export * from './data-source'; -export * from './data-source-service'; export * from './data-source-repository'; export * from './data-source-factory'; -export * from './indexer-data-source-repository'; -export * from './indexer-data-source'; -export * from './indexer-data-source-factory'; -export * from './alerts-data-source-repository'; -export * from './pattern-selector'; +export * from './data-source-selector'; +export * from './pattern'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts b/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts deleted file mode 100644 index 648db9d229..0000000000 --- a/plugins/main/public/components/common/data-source/indexer-data-source-factory.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { tDataSourceFactory, IndexerDataSource } from './index' -export class IndexerDataSourceFactory implements tDataSourceFactory { - - create(item: IndexerDataSource): IndexerDataSource { - if(!item){ - throw new Error('Cannot create data source from null or undefined'); - }; - return new IndexerDataSource(item.id, item.title); - } - createAll(items: IndexerDataSource[]): IndexerDataSource[] { - 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/indexer-data-source-repository.ts b/plugins/main/public/components/common/data-source/indexer-data-source-repository.ts deleted file mode 100644 index d577968c87..0000000000 --- a/plugins/main/public/components/common/data-source/indexer-data-source-repository.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { tDataSource } from "./data-source"; -import { DataSourceRepository } from "./data-source-repository"; -import { IndexerDataSource } from "./indexer-data-source"; -import { GenericRequest } from '../../../react-services/generic-request'; - -export class IndexerDataSourceRepository implements DataSourceRepository { - - async get(id: string): Promise { - try { - const indexPatternData = await GenericRequest.request( - 'GET', - `/api/saved_objects/index-pattern/${id}?fields=title&fields=fields`, - ); - const parsedIndexPatternData = parseIndexPattern(indexPatternData); - if (!parsedIndexPatternData.title || !parsedIndexPatternData.id) { - throw new Error('Index pattern not found'); - } - return parsedIndexPatternData; - } catch (error) { - throw new Error(`Error getting data source: ${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 data source: ${error.message}`); - } - } - - parseIndexPattern(indexPatternData): tDataSource { - const title = ((indexPatternData || {}).attributes || {}).title; - const id = (indexPatternData || {}).id; - return { - ...indexPatternData, - id: id, - title: title, - _fields: indexPatternData?.attributes?.fields - ? JSON.parse(indexPatternData.attributes.fields) - : [], - }; - } -} diff --git a/plugins/main/public/components/common/data-source/indexer-data-source.test.ts b/plugins/main/public/components/common/data-source/indexer-data-source.test.ts deleted file mode 100644 index c9b7923a46..0000000000 --- a/plugins/main/public/components/common/data-source/indexer-data-source.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IndexerDataSource } from './indexer-data-source'; - -describe('IndexerDataSource', () => { - it('should create a new data source handler', () => { - const dataSourceHandler = new IndexerDataSource('id', 'title'); - expect(dataSourceHandler).toEqual({ id: 'id', title: 'title' }); - }) -}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern-selector.tsx b/plugins/main/public/components/common/data-source/pattern-selector.tsx deleted file mode 100644 index eabc8e9931..0000000000 --- a/plugins/main/public/components/common/data-source/pattern-selector.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { useEffect, useState } from 'react'; -import { DataSourceService, tDataSource, AlertsDataSourceRepository, IndexerDataSourceFactory } from '../data-source'; -import { - EuiFormRow, - EuiSelect, -} from '@elastic/eui'; - -const PatternSelector = () => { - const [dataSourceList, setDataSourceList] = useState([]); - - useEffect(() => { - loadAlertsIndexPatterns(); - }, []) - - async function loadAlertsIndexPatterns() { - // pass to the data source handler the data source factory - // the data source handler will create the data source - const dataSourceHandler = new DataSourceService(new AlertsDataSourceRepository(), new IndexerDataSourceFactory()); - const dataSourcesList = await dataSourceHandler.getAllDataSources(); - setDataSourceList(dataSourcesList); - console.log('list', dataSourcesList); - } - - async function selectDataSource() { - try{ - await dataSourceList[0].select(); - }catch(error){ - console.error('Error selecting index pattern', error); - } - } - - return ( - - { - return { value: item.id, text: item.title }; - })} - value={dataSourceList[0]?.id || null} - onChange={selectDataSource} - aria-label='Index pattern selector' - /> - - ); -} - -export default PatternSelector; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/alerts-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source-repository.ts similarity index 87% rename from plugins/main/public/components/common/data-source/alerts-data-source-repository.ts rename to plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source-repository.ts index 9e232544c9..b7048c65c5 100644 --- a/plugins/main/public/components/common/data-source/alerts-data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source-repository.ts @@ -1,8 +1,8 @@ -import { IndexerDataSourceRepository } from "."; +import { PatternDataSourceRepository } from "../pattern-data-source-repository"; const ALERTS_REQUIRED_FIELDS = ['timestamp', 'rule.groups', 'manager.name', 'agent.id']; -export class AlertsDataSourceRepository extends IndexerDataSourceRepository { +export class AlertsDataSourceRepository extends PatternDataSourceRepository { constructor() { super(); } 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.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.ts new file mode 100644 index 0000000000..7bb1ade630 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.ts @@ -0,0 +1,16 @@ +import { tDataSourceFactory, PatternDataSource } from './index' +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.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.ts new file mode 100644 index 0000000000..39d772ba3a --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.ts @@ -0,0 +1,75 @@ +import { tDataSource } from "../data-source"; +import { DataSourceRepository } from "../data-source-repository"; +import { PatternDataSource } from "./pattern-data-source"; +import { GenericRequest } from '../../../../react-services/generic-request'; +import { AppState } from '../../../../react-services'; + +export class PatternDataSourceRepository implements DataSourceRepository { + setDefault(dataSource: tDataSource): Promise { + if(!dataSource){ + throw new Error('Data source is required'); + } + AppState.setCurrentPattern(dataSource.id); + return Promise.resolve(); + } + getDefault(): Promise { + const currentPattern = AppState.getCurrentPattern(); + // the AppState returns the title + if(!currentPattern){ + throw new Error('No default pattern set'); + } + return this.get(currentPattern); + } + + async get(id: string): Promise { + try { + const savedObjectResponse = await GenericRequest.request( + 'GET', + `/api/saved_objects/index-pattern/${id}?fields=title&fields=fields`, + ); + + 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(`Index pattern "${id}" not found`); + } + return parsedIndexPatternData; + } catch (error) { + throw new Error(`Error getting index pattern "${id}": ${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 pattern: ${error.message}`); + } + } + + parseIndexPattern(indexPatternData): tDataSource { + const title = ((indexPatternData || {}).attributes || {}).title; + const id = (indexPatternData || {}).id; + return { + ...indexPatternData, + id: id, + title: title, + _fields: indexPatternData?.attributes?.fields + ? JSON.parse(indexPatternData.attributes.fields) + : [], + }; + } + + + + + + +} 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..f209af0e9f --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts @@ -0,0 +1,8 @@ +import { PatternDataSource } from './pattern-data-source'; + +describe('PatternDataSource', () => { + it('should create a new data source handler', () => { + const dataSourceHandler = new PatternDataSource('id', 'title'); + expect(dataSourceHandler).toEqual({ id: 'id', title: 'title' }); + }) +}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/indexer-data-source.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts similarity index 92% rename from plugins/main/public/components/common/data-source/indexer-data-source.ts rename to plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts index 4d633d7414..e05ff6c99d 100644 --- a/plugins/main/public/components/common/data-source/indexer-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts @@ -51,10 +51,10 @@ static validateIndexPatterns(list) { */ // get the valid list of index patterns patternHandler.getPatternList('api'); the flag is to difference between api or health-check -import { getDataPlugin } from '../../../kibana-services'; +import { getDataPlugin } from '../../../../kibana-services'; -export class IndexerDataSource implements tDataSource { +export class PatternDataSource implements tDataSource { id: string; title: string; constructor(id: string, title: string) { @@ -63,14 +63,20 @@ export class IndexerDataSource implements tDataSource { } async select(){ - // select the current index pattern in the indexPatternService - const pattern = await getDataPlugin().indexPatterns.get(this.id); - console.log('pattern', pattern); - if(pattern){ - await getDataPlugin().indexPatterns.updateSavedObject(pattern); - console.log('pattern default', await getDataPlugin().indexPatterns.getDefault()); - }else{ - throw new Error('Error selecting index pattern'); + 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}`); } } diff --git a/plugins/main/public/components/common/data-source/pattern-selector.test.tsx b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.test.tsx similarity index 58% rename from plugins/main/public/components/common/data-source/pattern-selector.test.tsx rename to plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.test.tsx index 3ba70889ee..b6f363a282 100644 --- a/plugins/main/public/components/common/data-source/pattern-selector.test.tsx +++ b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { render } from '@testing-library/react'; -import PatternSelector from './pattern-selector'; +import WzPatternSelector from './wz-pattern-selector'; import '@testing-library/jest-dom/extend-expect'; -describe('PatternSelector', () => { +describe('WzPatternSelector', () => { it('should render', () => { - const { container } = render(); + const { container } = render(); expect(container).toBeInTheDocument(); }) diff --git a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx new file mode 100644 index 0000000000..dd5f38cd05 --- /dev/null +++ b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { useEffect, useState } from 'react'; +import { + EuiFormRow, + EuiSelect, +} from '@elastic/eui'; +import { + DataSourceSelector, + tDataSource, + PatternDataSource, + AlertsDataSourceRepository, + PatternDataSourceFactory } from '../common/data-source'; + + +const WzPatternSelector = () => { + const [dataSourceList, setDataSourceList] = useState([]); + const [selectedPattern, setSelectedPattern] = useState(); + const [isLoading, setIsLoading] = useState(false); + const dataSourceSelector = new DataSourceSelector(new AlertsDataSourceRepository(), new PatternDataSourceFactory()); + + useEffect(() => { + loadAlertsIndexPatterns(); + }, []) + + + async function loadAlertsIndexPatterns() { + // pass to the data source handler the data source factory + // the data source handler will create the data source + setIsLoading(true); + const dataSourcesList = await dataSourceSelector.getAllDataSources(); + // load default index pattern + const defaultIndexPattern = await dataSourceSelector.getSelectedDataSource(); + setSelectedPattern(defaultIndexPattern); + setDataSourceList(dataSourcesList); + setIsLoading(false); + } + + async function selectDataSource(e) { + const dataSourceId = e.target.value; + try{ + // search in datasource list the selected index pattern + await dataSourceSelector.selectDataSource(dataSourceId); + }catch(error){ + console.error(error); + } + } + + return ( + + { + return { value: item.id, text: item.title }; + })} + value={selectedPattern?.id} + onChange={selectDataSource} + aria-label='Index pattern selector' + /> + + ); +} + +export default WzPatternSelector; \ No newline at end of file From 32368a2c9f1c3e826940e71d050a4081d34e1ce5 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Tue, 5 Mar 2024 22:26:24 -0300 Subject: [PATCH 05/36] Add select feature and unit tests --- .../data-source/data-source-selector.ts | 4 +- .../pattern/pattern-data-source-factory.ts | 2 +- .../pattern-data-source-repository.test.ts | 255 ++++++++++++++++++ .../pattern/pattern-data-source-repository.ts | 79 ++++-- .../pattern/pattern-data-source.ts | 54 +--- .../main/public/components/wz-menu/wz-menu.js | 99 ++----- .../wz-pattern-selector.tsx | 12 +- 7 files changed, 351 insertions(+), 154 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.test.ts 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 index fd307bb5f0..aaba8074fa 100644 --- a/plugins/main/public/components/common/data-source/data-source-selector.ts +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -48,10 +48,12 @@ export class DataSourceSelector { async selectDataSource(id: string): Promise { + const currentSelectedDataSource = await this.getSelectedDataSource(); const dataSource = await this.getDataSource(id); - if(!dataSource){ + if (!dataSource) { throw new Error('Data source not found'); } + dataSource.select(); this.repository.setDefault(dataSource); } 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 index 7bb1ade630..fea41e6065 100644 --- 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 @@ -1,4 +1,4 @@ -import { tDataSourceFactory, PatternDataSource } from './index' +import { tDataSourceFactory, PatternDataSource } from '../'; export class PatternDataSourceFactory implements tDataSourceFactory { create(item: PatternDataSource): PatternDataSource { 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 index 39d772ba3a..d8af2e80fb 100644 --- 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 @@ -4,29 +4,51 @@ import { PatternDataSource } from "./pattern-data-source"; import { GenericRequest } from '../../../../react-services/generic-request'; import { AppState } from '../../../../react-services'; -export class PatternDataSourceRepository implements DataSourceRepository { - setDefault(dataSource: tDataSource): Promise { - if(!dataSource){ - throw new Error('Data source is required'); - } - AppState.setCurrentPattern(dataSource.id); - return Promise.resolve(); - } - getDefault(): Promise { - const currentPattern = AppState.getCurrentPattern(); - // the AppState returns the title - if(!currentPattern){ - throw new Error('No default pattern set'); - } - return this.get(currentPattern); +/* +{ + attributes: { + fields: "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"cluster.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"configSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"configSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"configSum\"}}},{\"name\":\"dateAdd\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"disconnection_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group\"}}},{\"name\":\"group_config_status\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group_config_status.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group_config_status\"}}},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"lastKeepAlive\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manager\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manager.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"manager\"}}},{\"name\":\"mergedSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"mergedSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"mergedSum\"}}},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"node_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"node_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"node_name\"}}},{\"name\":\"os.arch\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.arch.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.arch\"}}},{\"name\":\"os.build\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.build.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.build\"}}},{\"name\":\"os.major\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.major.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.major\"}}},{\"name\":\"os.minor\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.minor.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.minor\"}}},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.name\"}}},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.platform.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.platform\"}}},{\"name\":\"os.uname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.uname.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.uname\"}}},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.version\"}}},{\"name\":\"registerIP\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"registerIP.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"registerIP\"}}},{\"name\":\"status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"version\"}}}]" + title: "wazuh-monitoring-*" + }, + id: 'wazuh-monitoring-*; + migrationVersion: { + index-pattern: '7.6.0' + }, + namespace: ['default'], + references: [], + score: 0, + type: "index-pattern" + updated_at: "2024-01-09T16:37:31.020Z" + version: "WzQsMV0=" +} +*/ + +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 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){ @@ -34,11 +56,11 @@ export class PatternDataSourceRepository implements DataSourceRepository { } const parsedIndexPatternData = this.parseIndexPattern(indexPatternData); if (!parsedIndexPatternData.title || !parsedIndexPatternData.id) { - throw new Error(`Index pattern "${id}" not found`); + throw new Error(`Invalid index pattern data`); } return parsedIndexPatternData; } catch (error) { - throw new Error(`Error getting index pattern "${id}": ${error.message}`); + throw new Error(`Error getting index pattern: ${error.message}`); } } async getAll(): Promise { @@ -50,7 +72,7 @@ export class PatternDataSourceRepository implements DataSourceRepository { const indexPatterns = ((savedObjects || {}).data || {}).saved_objects || []; return indexPatterns.map(this.parseIndexPattern); } catch (error) { - throw new Error(`Error getting index pattern: ${error.message}`); + throw new Error(`Error getting index patterns: ${error.message}`); } } @@ -66,10 +88,21 @@ export class PatternDataSourceRepository implements DataSourceRepository { : [], }; } - - - - + setDefault(dataSource: tDataSource): Promise { + if(!dataSource){ + throw new Error('Index pattern is required'); + } + AppState.setCurrentPattern(dataSource.id); + return Promise.resolve(); + } + getDefault(): Promise { + const currentPattern = AppState.getCurrentPattern(); + // the AppState returns the title + if(!currentPattern){ + throw new Error('No default pattern set'); + } + return this.get(currentPattern); + } } 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 e05ff6c99d..df3d321c17 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 @@ -1,56 +1,4 @@ -import { tDataSource } from "./data-source"; - - -/* Use case load DataSource index patterns - -- load index patterns service - indexPatternService from plugin - -- load index patterns available - * await PatternHandler.getPatternList('api') : IndexPattern[] - type indexPattern = { - id: string; - title: string - } - - * receive the default patterns and validate - SavedObject.getListOfWazuhValidIndexPatterns(defaultPatterns, origin) - SavedObject.getListOfWazuhValidIndexPatterns(IndePattern[]], 'api'); - - * to validate request the saved_objects `/api/saved_objects/_find?type=index-pattern&fields=title&fields=fields&per_page=9999` - find all type=index-pattern - type savedObjects = { - attributes: { - fields: "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"cluster.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"configSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"configSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"configSum\"}}},{\"name\":\"dateAdd\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"disconnection_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group\"}}},{\"name\":\"group_config_status\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group_config_status.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group_config_status\"}}},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"lastKeepAlive\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manager\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manager.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"manager\"}}},{\"name\":\"mergedSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"mergedSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"mergedSum\"}}},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"node_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"node_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"node_name\"}}},{\"name\":\"os.arch\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.arch.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.arch\"}}},{\"name\":\"os.build\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.build.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.build\"}}},{\"name\":\"os.major\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.major.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.major\"}}},{\"name\":\"os.minor\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.minor.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.minor\"}}},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.name\"}}},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.platform.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.platform\"}}},{\"name\":\"os.uname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.uname.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.uname\"}}},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.version\"}}},{\"name\":\"registerIP\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"registerIP.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"registerIP\"}}},{\"name\":\"status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"version\"}}}]" - title: "wazuh-monitoring-*" - }, - id: 'wazuh-monitoring-*; - migrationVersion: { - index-pattern: '7.6.0' - }, - namespace: ['default'], - references: [], - score: 0, - type: "index-pattern" - updated_at: "2024-01-09T16:37:31.020Z" - version: "WzQsMV0=" - } - -- get the fields from the indexPattern /api/saved_objects/index-pattern/${patternID}?fields=title&fields=fields. -(this adds the _fields into index patterns list) -- why validateIndexPattern its only check if the index have the following fields ? -this filter and remove the wazuh-monitoring and wazuh-statistics index pattern - -static validateIndexPatterns(list) { - const requiredFields = ['timestamp', 'rule.groups', 'manager.name', 'agent.id']; - return list.filter(item => item && item._fields && requiredFields.every(reqField => item._fields.some(field => field.name === reqField))); -} -- validate comparing list of patterns and saved_objects requested - -- ONLY RETURNS title and id - - -*/ - -// get the valid list of index patterns patternHandler.getPatternList('api'); the flag is to difference between api or health-check +import { tDataSource } from "../data-source"; import { getDataPlugin } from '../../../../kibana-services'; diff --git a/plugins/main/public/components/wz-menu/wz-menu.js b/plugins/main/public/components/wz-menu/wz-menu.js index dece4702ed..b553572f65 100644 --- a/plugins/main/public/components/wz-menu/wz-menu.js +++ b/plugins/main/public/components/wz-menu/wz-menu.js @@ -43,6 +43,7 @@ import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/typ import { getErrorOrchestrator } from '../../react-services/common-services'; import { MountPointPortal } from '../../../../../src/plugins/opensearch_dashboards_react/public'; import { setBreadcrumbs } from '../common/globalBreadcrumb/platformBreadcrumb'; +import WzPatternSelector from '../wz-pattern-selector/wz-pattern-selector'; const sections = { overview: 'overview', @@ -341,36 +342,6 @@ export const WzMenu = withWindowSize( this.isLoading = false; } - changePattern = async event => { - 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, @@ -434,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( @@ -565,6 +504,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) { @@ -576,19 +539,9 @@ 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' - /> +
diff --git a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx index dd5f38cd05..45ec738f75 100644 --- a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx +++ b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx @@ -11,8 +11,12 @@ import { AlertsDataSourceRepository, PatternDataSourceFactory } from '../common/data-source'; +type tWzPatternSelector = { + onChange?: (dataSource: tDataSource) => void; +} -const WzPatternSelector = () => { +const WzPatternSelector = (props: tWzPatternSelector) => { + const { onChange } = props; const [dataSourceList, setDataSourceList] = useState([]); const [selectedPattern, setSelectedPattern] = useState(); const [isLoading, setIsLoading] = useState(false); @@ -40,6 +44,8 @@ const WzPatternSelector = () => { try{ // search in datasource list the selected index pattern await dataSourceSelector.selectDataSource(dataSourceId); + setSelectedPattern(await dataSourceSelector.getDataSource(dataSourceId)); + onChange && onChange(selectedPattern); }catch(error){ console.error(error); } @@ -48,11 +54,11 @@ const WzPatternSelector = () => { return ( { return { value: item.id, text: item.title }; })} - value={selectedPattern?.id} + value={selectedPattern?.id || dataSourceList[0]?.id } onChange={selectDataSource} aria-label='Index pattern selector' /> From 38cb5896442d7c4b31c0a2f46a2163278c938b81 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 6 Mar 2024 18:26:16 -0300 Subject: [PATCH 06/36] Added unit tests --- .../data-source/data-source-repository.ts | 4 +- .../data-source/data-source-selector.test.ts | 138 +++++++++++++++--- .../data-source/data-source-selector.ts | 30 +++- .../pattern-data-source-factory.test.ts | 47 ++++++ .../pattern/pattern-data-source-repository.ts | 30 +++- .../pattern/pattern-data-source.test.ts | 30 +++- .../pattern/pattern-data-source.ts | 1 - 7 files changed, 238 insertions(+), 42 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.test.ts 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 index d5ff031c0f..7a7dc3bebe 100644 --- a/plugins/main/public/components/common/data-source/data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/data-source-repository.ts @@ -3,6 +3,6 @@ import { tDataSource } from "./data-source"; export interface DataSourceRepository { get(id: string): Promise; getAll(): Promise; - setDefault(dataSource: tDataSource): Promise; - getDefault(): 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 index ae657c66b9..63d8e89ed7 100644 --- 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 @@ -1,42 +1,136 @@ -import { DataSourceSelector } from './pattern-data-source-selector'; +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', () => { - it('should return ERROR when the handler not receive a valid factory', () => { + + beforeEach(() => { + repository = new ExampleRepository(); + factory = new ExampleFactory(); + dataSourceSelector = new DataSourceSelector(repository, factory); + }); + + 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); + new DataSourceSelector({} as any, factory); }catch(error){ expect(error.message).toBe('Invalid data source factory'); } }) - it('should return ERROR when the handler not receive a factory', () => { + + it('should return ERROR when the selector not receive a factory', () => { try { - new DataSourceSelector(null as any); + new DataSourceSelector(repository, null as any); }catch(error){ expect(error.message).toBe('Data source factory is required'); } }) - it('should create a new data source handler when receive a valid factory', () => { - class ExampleFactory implements DataSourceRepository { - get(id: string): Promise { - throw new Error('Method not implemented.'); - } - getAll(): Promise { - throw new Error('Method not implemented.'); - } - validateAll(dataSources: tDataSource[]): boolean { - throw new Error('Method not implemented.'); - } - validate(dataSource: tDataSource): boolean { - throw new Error('Method not implemented.'); - } - + 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'); } + }) + + 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); + }); + + 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); + const result = await dataSourceSelector.getSelectedDataSource(); + expect(result.id).toEqual(dataSourcesMocked[0].id); + expect(repository.getDefault).toHaveBeenCalledTimes(1); + expect(repository.getAll).toHaveBeenCalledTimes(1); + }) + + it.skip('should select a data source by ID when exists', async () => { + const dataSourceId = '1'; + jest.spyOn(repository, 'get').mockResolvedValue({ id: dataSourceId, name: 'Selected DataSource' }); + await dataSourceSelector.selectDataSource(dataSourceId); + const result = await dataSourceSelector.getSelectedDataSource(); + expect(result.id).toEqual(dataSourceId); + expect(repository.setDefault).toHaveBeenCalledWith({ id: dataSourceId, name: 'Selected DataSource' }); + }); + + it.skip('should throw an error when selecting a non-existing data source', async () => { + const dataSourceId = '999'; + jest.spyOn(repository, 'get').mockResolvedValue(null); + await expect(dataSourceSelector.selectDataSource(dataSourceId)).rejects.toThrowError('Data source not found'); + }); + + it.skip('should throw an error when selecting a data source with an invalid ID', async () => { + const dataSourceId = ''; + await expect(dataSourceSelector.selectDataSource(dataSourceId)).rejects.toThrowError('Invalid data source ID'); + }); + + it.skip('should return all data sources from the repository when the map is empty', () => { + + }) + + it('should return all data sources from the map when was loaded previously', () => { - const dataSourceService = new DataSourceSelector(new ExampleFactory()); - expect(dataSourceService).toBeTruthy(); }) + + it('should return all data sources from the repository when the map is empty', () => { + + }) + }) \ 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 index aaba8074fa..26fb3e1def 100644 --- a/plugins/main/public/components/common/data-source/data-source-selector.ts +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -2,7 +2,7 @@ import { DataSourceRepository } from './data-source-repository'; import { tDataSourceFactory } from './data-source-factory'; import { tDataSource } from "./data-source"; -type tDataSourceSelector = { +type tDataSourceSelector = { getAllDataSources: () => Promise; getDataSource: (id: string) => Promise; getSelectedDataSource: () => Promise; @@ -10,7 +10,7 @@ type tDataSourceSelector = { } -export class DataSourceSelector { +export class DataSourceSelector implements tDataSourceSelector { // add a map to store locally the data sources private dataSources: Map = new Map(); @@ -24,7 +24,6 @@ export class DataSourceSelector { } async getAllDataSources(): Promise { - // when the map of the data sources is empty, get all the data sources from the repository if (this.dataSources.size === 0) { const dataSources = await this.factory.createAll(await this.repository.getAll()); dataSources.forEach(dataSource => { @@ -46,20 +45,35 @@ export class DataSourceSelector { 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 { const currentSelectedDataSource = await this.getSelectedDataSource(); const dataSource = await this.getDataSource(id); if (!dataSource) { throw new Error('Data source not found'); } - dataSource.select(); - this.repository.setDefault(dataSource); + await dataSource.select(); + await this.repository.setDefault(dataSource); } + + /** + * Get the selected data source from the repository. + */ async getSelectedDataSource(): Promise { - return await this.repository.getDefault(); + const defaultDataSource = await this.repository.getDefault(); + if(!defaultDataSource){ + // if the data source is not saved on the repository, return the first one + const dataSources = await this.getAllDataSources(); + return dataSources[0]; + }else{ + return defaultDataSource; + } } - } \ 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-repository.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-repository.ts index d8af2e80fb..d342623ea2 100644 --- 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 @@ -42,6 +42,27 @@ export type tSavedObjectResponse = { } } +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 { @@ -76,9 +97,9 @@ export class PatternDataSourceRepository implements DataSourceRepository { } } - parseIndexPattern(indexPatternData): tDataSource { + parseIndexPattern(indexPatternData): tParsedIndexPattern { const title = ((indexPatternData || {}).attributes || {}).title; - const id = (indexPatternData || {}).id; + const id = (indexPatternData || {}).id; return { ...indexPatternData, id: id, @@ -96,11 +117,10 @@ export class PatternDataSourceRepository implements DataSourceRepository { AppState.setCurrentPattern(dataSource.id); return Promise.resolve(); } - getDefault(): Promise { + getDefault(): Promise | tDataSource | null { const currentPattern = AppState.getCurrentPattern(); - // the AppState returns the title if(!currentPattern){ - throw new Error('No default pattern set'); + 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 index f209af0e9f..b36d08e655 100644 --- 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 @@ -1,8 +1,30 @@ import { PatternDataSource } from './pattern-data-source'; +jest.mock('../../../../kibana-services', () => ({ + ...(jest.requireActual('../../../../kibana-services') as object), + getDataPlugin: jest.fn(), +})); + describe('PatternDataSource', () => { it('should create a new data source handler', () => { - const dataSourceHandler = new PatternDataSource('id', 'title'); - expect(dataSourceHandler).toEqual({ id: 'id', title: 'title' }); - }) -}) \ No newline at end of file + 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'); + const spy = jest.spyOn(patternDataSource, 'select'); + // mock getDataPlugin().indexPatterns.get + + }); +}); 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 df3d321c17..a5b2bbdf13 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 @@ -26,6 +26,5 @@ export class PatternDataSource implements tDataSource { }catch(error){ throw new Error(`Error selecting index pattern: ${error}`); } - } } \ No newline at end of file From d1bcbd2cc47626324fb789e930920f24c9a296a1 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Mon, 11 Mar 2024 13:42:28 -0300 Subject: [PATCH 07/36] Change wz-pattern-selector props, data source like dependency --- .../data-source/data-source-selector.test.ts | 2 +- .../data-source/data-source-selector.ts | 63 +++++++++++++++---- .../main/public/components/wz-menu/wz-menu.js | 9 ++- .../wz-pattern-selector.tsx | 42 ++++++------- 4 files changed, 82 insertions(+), 34 deletions(-) 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 index 63d8e89ed7..93d767d833 100644 --- 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 @@ -122,7 +122,7 @@ describe('DataSourceSelector', () => { }); it.skip('should return all data sources from the repository when the map is empty', () => { - + }) it('should return all data sources from the map when was loaded previously', () => { 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 index 26fb3e1def..e7ce1bdaf9 100644 --- a/plugins/main/public/components/common/data-source/data-source-selector.ts +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -2,11 +2,12 @@ import { DataSourceRepository } from './data-source-repository'; import { tDataSourceFactory } from './data-source-factory'; import { tDataSource } from "./data-source"; -type tDataSourceSelector = { - getAllDataSources: () => Promise; - getDataSource: (id: string) => Promise; - getSelectedDataSource: () => Promise; +export type tDataSourceSelector = { + getAllDataSources: () => Promise; + getDataSource: (id: string) => Promise; + getSelectedDataSource: () => Promise; selectDataSource: (id: string) => Promise; + existsDataSource: (id: string) => Promise; } @@ -22,6 +23,17 @@ export class DataSourceSelector implements tDataSourceSelector { throw new Error('Data source factory is required'); } } + 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; + } + } async getAllDataSources(): Promise { if (this.dataSources.size === 0) { @@ -52,6 +64,9 @@ export class DataSourceSelector implements tDataSourceSelector { * @param id */ async selectDataSource(id: string): Promise { + if (!id) { + throw new Error('Error selecting data source. ID is required'); + } const currentSelectedDataSource = await this.getSelectedDataSource(); const dataSource = await this.getDataSource(id); if (!dataSource) { @@ -61,18 +76,44 @@ export class DataSourceSelector implements tDataSourceSelector { await this.repository.setDefault(dataSource); } + async getDataSourceByIndex(id: string | null): Promise { + if (!id) { + const dataSources = await this.getAllDataSources(); + return dataSources[0]; + } + return await this.getDataSource(id); + } /** * Get the selected data source from the repository. */ async getSelectedDataSource(): Promise { - const defaultDataSource = await this.repository.getDefault(); - if(!defaultDataSource){ - // if the data source is not saved on the repository, return the first one - const dataSources = await this.getAllDataSources(); - return dataSources[0]; - }else{ - return defaultDataSource; + try { + const defaultDataSource = await this.repository.getDefault(); + // when the default data source is not found, return the first one of the map + if (!defaultDataSource) { + // if the data source is not saved on the repository, return the first one + const firstDataSource = await this.getDataSourceByIndex(null); + await firstDataSource.select(); + await this.repository.setDefault(firstDataSource); + return firstDataSource; + } else { + // if exists check if the data source is in the map + const dataSource = this.dataSources.get(defaultDataSource.id); + if (!dataSource) { + const firstDataSource = await this.getDataSourceByIndex(null); + await firstDataSource.select(); + await this.repository.setDefault(firstDataSource); + return firstDataSource; + } else { + return dataSource; + } + } + } catch (error) { + const firstDataSource = await this.getDataSourceByIndex(null); + await firstDataSource.select(); + await this.repository.setDefault(firstDataSource); + return firstDataSource; } } diff --git a/plugins/main/public/components/wz-menu/wz-menu.js b/plugins/main/public/components/wz-menu/wz-menu.js index b553572f65..bfa4ef0118 100644 --- a/plugins/main/public/components/wz-menu/wz-menu.js +++ b/plugins/main/public/components/wz-menu/wz-menu.js @@ -44,6 +44,12 @@ import { getErrorOrchestrator } from '../../react-services/common-services'; import { MountPointPortal } from '../../../../../src/plugins/opensearch_dashboards_react/public'; import { setBreadcrumbs } from '../common/globalBreadcrumb/platformBreadcrumb'; import WzPatternSelector from '../wz-pattern-selector/wz-pattern-selector'; +import { + DataSourceSelector, + PatternDataSource, + AlertsDataSourceRepository, + PatternDataSourceFactory +} from '../common/data-source'; const sections = { overview: 'overview', @@ -73,6 +79,7 @@ export const WzMenu = withWindowSize( currentSelectedPattern: '', isManagementPopoverOpen: false, isOverviewPopoverOpen: false, + dataSourceSelector: new DataSourceSelector(new AlertsDataSourceRepository(), new PatternDataSourceFactory()) }; this.store = store; this.genericReq = GenericRequest; @@ -541,7 +548,7 @@ export const WzMenu = withWindowSize(
- +
diff --git a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx index 45ec738f75..408bd3f4df 100644 --- a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx +++ b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx @@ -4,35 +4,33 @@ import { EuiFormRow, EuiSelect, } from '@elastic/eui'; -import { - DataSourceSelector, - tDataSource, - PatternDataSource, - AlertsDataSourceRepository, - PatternDataSourceFactory } from '../common/data-source'; - +import { + tDataSourceSelector +} from '../common/data-source'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../react-services/error-management'; + type tWzPatternSelector = { onChange?: (dataSource: tDataSource) => void; + dataSourceSelector: tDataSourceSelector; } const WzPatternSelector = (props: tWzPatternSelector) => { - const { onChange } = props; - const [dataSourceList, setDataSourceList] = useState([]); + const { onChange, dataSourceSelector } = props; + const [dataSourceList, setDataSourceList] = useState([]); const [selectedPattern, setSelectedPattern] = useState(); const [isLoading, setIsLoading] = useState(false); - const dataSourceSelector = new DataSourceSelector(new AlertsDataSourceRepository(), new PatternDataSourceFactory()); - + useEffect(() => { loadAlertsIndexPatterns(); }, []) - async function loadAlertsIndexPatterns() { - // pass to the data source handler the data source factory - // the data source handler will create the data source setIsLoading(true); const dataSourcesList = await dataSourceSelector.getAllDataSources(); - // load default index pattern const defaultIndexPattern = await dataSourceSelector.getSelectedDataSource(); setSelectedPattern(defaultIndexPattern); setDataSourceList(dataSourcesList); @@ -41,24 +39,26 @@ const WzPatternSelector = (props: tWzPatternSelector) => { async function selectDataSource(e) { const dataSourceId = e.target.value; - try{ - // search in datasource list the selected index pattern + try { await dataSourceSelector.selectDataSource(dataSourceId); setSelectedPattern(await dataSourceSelector.getDataSource(dataSourceId)); onChange && onChange(selectedPattern); - }catch(error){ - console.error(error); + } catch (error) { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error selecting index pattern', + }); + ErrorHandler.handleError(searchError); } } return ( { return { value: item.id, text: item.title }; })} - value={selectedPattern?.id || dataSourceList[0]?.id } + value={selectedPattern?.id || dataSourceList[0]?.id} onChange={selectDataSource} aria-label='Index pattern selector' /> From e67478948e9f07de5d139a5cef4eeb894bee72e1 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Mon, 11 Mar 2024 18:11:59 -0300 Subject: [PATCH 08/36] Update unit tests --- .../data-source/data-source-selector.test.ts | 217 +++++++++++------- .../data-source/data-source-selector.ts | 80 ++++--- .../components/common/data-source/index.ts | 1 + .../pattern/pattern-data-source.test.ts | 25 +- .../wz-data-source-selector.tsx} | 19 +- .../main/public/components/wz-menu/wz-menu.js | 7 +- .../wz-pattern-selector.test.tsx | 13 -- 7 files changed, 217 insertions(+), 145 deletions(-) rename plugins/main/public/components/{wz-pattern-selector/wz-pattern-selector.tsx => common/data-source/wz-data-source-selector/wz-data-source-selector.tsx} (78%) delete mode 100644 plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.test.tsx 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 index 93d767d833..3360119aeb 100644 --- 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 @@ -33,104 +33,149 @@ describe('DataSourceSelector', () => { dataSourceSelector = new DataSourceSelector(repository, factory); }); - 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'); - } - }) + 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'); + } + }) - 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); + }); }) - 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); - }); - - 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); - const result = await dataSourceSelector.getSelectedDataSource(); - expect(result.id).toEqual(dataSourcesMocked[0].id); - expect(repository.getDefault).toHaveBeenCalledTimes(1); - expect(repository.getAll).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'); + } + }); }) - it.skip('should select a data source by ID when exists', async () => { - const dataSourceId = '1'; - jest.spyOn(repository, 'get').mockResolvedValue({ id: dataSourceId, name: 'Selected DataSource' }); - await dataSourceSelector.selectDataSource(dataSourceId); - const result = await dataSourceSelector.getSelectedDataSource(); - expect(result.id).toEqual(dataSourceId); - expect(repository.setDefault).toHaveBeenCalledWith({ id: dataSourceId, name: 'Selected DataSource' }); - }); - - it.skip('should throw an error when selecting a non-existing data source', async () => { - const dataSourceId = '999'; - jest.spyOn(repository, 'get').mockResolvedValue(null); - await expect(dataSourceSelector.selectDataSource(dataSourceId)).rejects.toThrowError('Data source not found'); - }); - - it.skip('should throw an error when selecting a data source with an invalid ID', async () => { - const dataSourceId = ''; - await expect(dataSourceSelector.selectDataSource(dataSourceId)).rejects.toThrowError('Invalid data source ID'); - }); - - it.skip('should return all data sources from the repository when the map is empty', () => { - + 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); + }); }) - it('should return all data sources from the map when was loaded previously', () => { + 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); + }) }) - it('should return all data sources from the repository when the map is empty', () => { - + 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 index e7ce1bdaf9..d35c0c87d0 100644 --- a/plugins/main/public/components/common/data-source/data-source-selector.ts +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -3,11 +3,12 @@ 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; - existsDataSource: (id: string) => Promise; } @@ -23,6 +24,11 @@ export class DataSourceSelector implements tDataSourceSelector { 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) { @@ -35,6 +41,32 @@ export class DataSourceSelector implements tDataSourceSelector { } } + /** + * 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()); @@ -45,6 +77,12 @@ export class DataSourceSelector implements tDataSourceSelector { 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) { @@ -67,7 +105,6 @@ export class DataSourceSelector implements tDataSourceSelector { if (!id) { throw new Error('Error selecting data source. ID is required'); } - const currentSelectedDataSource = await this.getSelectedDataSource(); const dataSource = await this.getDataSource(id); if (!dataSource) { throw new Error('Data source not found'); @@ -76,44 +113,25 @@ export class DataSourceSelector implements tDataSourceSelector { await this.repository.setDefault(dataSource); } - async getDataSourceByIndex(id: string | null): Promise { - if (!id) { - const dataSources = await this.getAllDataSources(); - return dataSources[0]; - } - return await this.getDataSource(id); - } - /** * 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(); - // when the default data source is not found, return the first one of the map if (!defaultDataSource) { - // if the data source is not saved on the repository, return the first one - const firstDataSource = await this.getDataSourceByIndex(null); - await firstDataSource.select(); - await this.repository.setDefault(firstDataSource); - return firstDataSource; - } else { - // if exists check if the data source is in the map - const dataSource = this.dataSources.get(defaultDataSource.id); - if (!dataSource) { - const firstDataSource = await this.getDataSourceByIndex(null); - await firstDataSource.select(); - await this.repository.setDefault(firstDataSource); - return firstDataSource; - } else { - return dataSource; - } + const validDataSource = await this.getFirstValidDataSource(); + await this.selectDataSource(validDataSource.id); + return validDataSource; } + return defaultDataSource; } catch (error) { - const firstDataSource = await this.getDataSourceByIndex(null); - await firstDataSource.select(); - await this.repository.setDefault(firstDataSource); - return firstDataSource; + const validateDataSource = await this.getFirstValidDataSource(); + await this.selectDataSource(validateDataSource.id); + return validateDataSource; } } diff --git a/plugins/main/public/components/common/data-source/index.ts b/plugins/main/public/components/common/data-source/index.ts index ef95f4ee82..5902361d2f 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -2,4 +2,5 @@ export * from './data-source'; export * from './data-source-repository'; export * from './data-source-factory'; export * from './data-source-selector'; +export * from './wz-data-source-selector/wz-data-source-selector'; export * from './pattern'; \ No newline at end of file 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 index b36d08e655..36eee0e8ec 100644 --- 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 @@ -1,8 +1,22 @@ import { PatternDataSource } from './pattern-data-source'; +import { getDataPlugin } from '../../../../kibana-services'; jest.mock('../../../../kibana-services', () => ({ ...(jest.requireActual('../../../../kibana-services') as object), - getDataPlugin: jest.fn(), + 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', () => { @@ -23,8 +37,11 @@ describe('PatternDataSource', () => { it.skip('should select the data source', async () => { const patternDataSource = new PatternDataSource('id', 'title'); - const spy = jest.spyOn(patternDataSource, 'select'); - // mock getDataPlugin().indexPatterns.get - + (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/wz-pattern-selector/wz-pattern-selector.tsx b/plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx similarity index 78% rename from plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx rename to plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx index 408bd3f4df..2374c999da 100644 --- a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.tsx +++ b/plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx @@ -11,24 +11,25 @@ import { ErrorHandler, ErrorFactory, HttpError, -} from '../../react-services/error-management'; +} from '../../../../react-services/error-management'; -type tWzPatternSelector = { +type tWzDataSourceSelector = { + name: 'string' onChange?: (dataSource: tDataSource) => void; dataSourceSelector: tDataSourceSelector; } -const WzPatternSelector = (props: tWzPatternSelector) => { - const { onChange, dataSourceSelector } = props; +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(() => { - loadAlertsIndexPatterns(); + loadDataSources(); }, []) - async function loadAlertsIndexPatterns() { + async function loadDataSources() { setIsLoading(true); const dataSourcesList = await dataSourceSelector.getAllDataSources(); const defaultIndexPattern = await dataSourceSelector.getSelectedDataSource(); @@ -46,7 +47,7 @@ const WzPatternSelector = (props: tWzPatternSelector) => { } catch (error) { const searchError = ErrorFactory.create(HttpError, { error, - message: 'Error selecting index pattern', + message: `Error selecting the ${name.toLowerCase()} '${dataSourceId}` }); ErrorHandler.handleError(searchError); } @@ -60,10 +61,10 @@ const WzPatternSelector = (props: tWzPatternSelector) => { })} value={selectedPattern?.id || dataSourceList[0]?.id} onChange={selectDataSource} - aria-label='Index pattern selector' + aria-label={`${name} selector`} /> ); } -export default WzPatternSelector; \ No newline at end of file +export default WzDataSourceSelector; \ No newline at end of file diff --git a/plugins/main/public/components/wz-menu/wz-menu.js b/plugins/main/public/components/wz-menu/wz-menu.js index 243c910c35..2d56dd8df3 100644 --- a/plugins/main/public/components/wz-menu/wz-menu.js +++ b/plugins/main/public/components/wz-menu/wz-menu.js @@ -43,9 +43,9 @@ import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/typ import { getErrorOrchestrator } from '../../react-services/common-services'; import { MountPointPortal } from '../../../../../src/plugins/opensearch_dashboards_react/public'; import { setBreadcrumbs } from '../common/globalBreadcrumb/platformBreadcrumb'; -import WzPatternSelector from '../wz-pattern-selector/wz-pattern-selector'; import { DataSourceSelector, + WzDataSourceSelector, PatternDataSource, AlertsDataSourceRepository, PatternDataSourceFactory @@ -543,7 +543,10 @@ export const WzMenu = withWindowSize(
- +
diff --git a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.test.tsx b/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.test.tsx deleted file mode 100644 index b6f363a282..0000000000 --- a/plugins/main/public/components/wz-pattern-selector/wz-pattern-selector.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import WzPatternSelector from './wz-pattern-selector'; -import '@testing-library/jest-dom/extend-expect'; - -describe('WzPatternSelector', () => { - - it('should render', () => { - const { container } = render(); - expect(container).toBeInTheDocument(); - }) - -}) \ No newline at end of file From 9393d8a93c70d4c0e441fb482c0769a0e1e3b9d1 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 13 Mar 2024 10:34:23 -0300 Subject: [PATCH 09/36] Fix data source selector import --- plugins/main/public/components/common/data-source/index.ts | 1 - .../wz-data-source-selector/wz-data-source-selector.tsx | 6 +++--- plugins/main/public/components/wz-menu/wz-menu.js | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/main/public/components/common/data-source/index.ts b/plugins/main/public/components/common/data-source/index.ts index 5902361d2f..ef95f4ee82 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -2,5 +2,4 @@ export * from './data-source'; export * from './data-source-repository'; export * from './data-source-factory'; export * from './data-source-selector'; -export * from './wz-data-source-selector/wz-data-source-selector'; export * from './pattern'; \ 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 index 2374c999da..b4271e4fca 100644 --- 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 @@ -14,7 +14,7 @@ import { } from '../../../../react-services/error-management'; type tWzDataSourceSelector = { - name: 'string' + name: 'string'; onChange?: (dataSource: tDataSource) => void; dataSourceSelector: tDataSourceSelector; } @@ -59,9 +59,9 @@ const WzDataSourceSelector = (props: tWzDataSourceSelector) => { options={dataSourceList.map((item) => { return { value: item.id, text: item.title }; })} - value={selectedPattern?.id || dataSourceList[0]?.id} + value={selectedPattern?.id} onChange={selectDataSource} - aria-label={`${name} selector`} + aria-label={name} />
); diff --git a/plugins/main/public/components/wz-menu/wz-menu.js b/plugins/main/public/components/wz-menu/wz-menu.js index 2d56dd8df3..1ae5a0208b 100644 --- a/plugins/main/public/components/wz-menu/wz-menu.js +++ b/plugins/main/public/components/wz-menu/wz-menu.js @@ -45,12 +45,13 @@ import { MountPointPortal } from '../../../../../src/plugins/opensearch_dashboar import { setBreadcrumbs } from '../common/globalBreadcrumb/platformBreadcrumb'; import { DataSourceSelector, - WzDataSourceSelector, PatternDataSource, AlertsDataSourceRepository, PatternDataSourceFactory } from '../common/data-source'; +import WzDataSourceSelector from '../common/data-source/wz-data-source-selector/wz-data-source-selector'; + const sections = { overview: 'overview', manager: 'manager', From 636600184afa5eda3c4d84458f2b5b8d1ffd4f7f Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 13 Mar 2024 10:42:29 -0300 Subject: [PATCH 10/36] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 167f6ce2d8..db0528cfa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Added upgrade agent action to Endpoints Summary [#6476](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6476) - Added global actions add agents to groups and remove agents from groups to Endpoints Summary [#6274](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6274) - 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 From 4906af5a585d92ffd6b3a9e3ecb76be2b08564c9 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Tue, 19 Mar 2024 16:22:00 -0300 Subject: [PATCH 11/36] Adapting types in factory, repository and data source --- .../common/data-source/data-source-factory.ts | 7 +- .../data-source/data-source-repository.ts | 12 ++- .../common/data-source/pattern/index.ts | 3 +- .../pattern/pattern-data-source-factory.ts | 8 +- .../pattern/pattern-data-source-repository.ts | 12 ++- .../pattern/pattern-data-source.ts | 89 ++++++++++++++++++- .../pattern/vulnerabilities/index.ts | 3 + 7 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts 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 index 993a78141a..8039d61b8c 100644 --- a/plugins/main/public/components/common/data-source/data-source-factory.ts +++ b/plugins/main/public/components/common/data-source/data-source-factory.ts @@ -1,5 +1,4 @@ -import { tDataSource } from './index' -export type tDataSourceFactory = { - create(item: tDataSource): tDataSource; - createAll(items: tDataSource[]): tDataSource[]; +export type tDataSourceFactory = { + create(item: T): Promise | K; + createAll(items: T[]): Promise | K[]; } \ 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 index 7a7dc3bebe..143dd86fec 100644 --- a/plugins/main/public/components/common/data-source/data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/data-source-repository.ts @@ -1,8 +1,6 @@ -import { tDataSource } from "./data-source"; - -export interface DataSourceRepository { - get(id: string): Promise; - getAll(): Promise; - setDefault(dataSource: tDataSource): Promise | void; - getDefault(): Promise | tDataSource | null; +export interface DataSourceRepository { + get(id: string): Promise; + getAll(): Promise; + setDefault(dataSource: K): Promise | void; + getDefault(): Promise | T | null; } \ 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 index cf3b71dd1d..1005379f31 100644 --- a/plugins/main/public/components/common/data-source/pattern/index.ts +++ b/plugins/main/public/components/common/data-source/pattern/index.ts @@ -2,4 +2,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 +export * from './alerts'; +export * from './vulnerabilities'; \ No newline at end of file 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 index fea41e6065..f8ad86c766 100644 --- 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 @@ -1,13 +1,13 @@ -import { tDataSourceFactory, PatternDataSource } from '../'; -export class PatternDataSourceFactory implements tDataSourceFactory { +import { tDataSourceFactory, PatternDataSource, tDataSource, tParsedIndexPattern } from '../'; +export class PatternDataSourceFactory implements tDataSourceFactory{ - create(item: PatternDataSource): PatternDataSource { + create(item: tParsedIndexPattern): 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[] { + createAll(items: tParsedIndexPattern[]): PatternDataSource[] { if(!items){ throw new Error('Cannot create data source from null or undefined'); }; 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 index d342623ea2..099ba9d009 100644 --- 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 @@ -59,12 +59,10 @@ export type tParsedIndexPattern = { 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 { +export class PatternDataSourceRepository implements DataSourceRepository{ + async get(id: string): Promise { try { const savedObjectResponse = await GenericRequest.request( 'GET', @@ -84,7 +82,7 @@ export class PatternDataSourceRepository implements DataSourceRepository { throw new Error(`Error getting index pattern: ${error.message}`); } } - async getAll(): Promise { + async getAll(): Promise { try { const savedObjects = await GenericRequest.request( 'GET', @@ -110,14 +108,14 @@ export class PatternDataSourceRepository implements DataSourceRepository { }; } - setDefault(dataSource: tDataSource): Promise { + setDefault(dataSource: PatternDataSource): Promise { if(!dataSource){ throw new Error('Index pattern is required'); } AppState.setCurrentPattern(dataSource.id); return Promise.resolve(); } - getDefault(): Promise | tDataSource | null { + getDefault(): Promise | tParsedIndexPattern | null { const currentPattern = AppState.getCurrentPattern(); if(!currentPattern){ return null; 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 a5b2bbdf13..cb61703776 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 @@ -1,25 +1,50 @@ -import { tDataSource } from "../data-source"; +import { tDataSource, tSearchParams, tFilter } from "../index"; import { getDataPlugin } from '../../../../kibana-services'; +import { Filter, IndexPatternsContract, OpenSearchQuerySortValue } from "../../../../../../../src/plugins/data/public"; export class PatternDataSource implements tDataSource { id: string; title: string; + fields: any[]; + patternService: IndexPatternsContract; + defaultFixedFilters: tFilter[]; + constructor(id: string, title: string) { this.id = id; this.title = title; } + /** + * Initialize the data source + */ + async init(){ + this.patternService = await getDataPlugin().indexPatterns; + } + + getFilters(){ + return []; + } + + getFields(){ + return this.fields; + } + + getFixedFilters():Filter[]{ + // return all filters + return []; + } + async select(){ try { - const pattern = await getDataPlugin().indexPatterns.get(this.id); + const pattern = await this.patternService.get(this.id); if(pattern){ - const fields = await getDataPlugin().indexPatterns.getFieldsForIndexPattern( + const fields = await this.patternService.getFieldsForIndexPattern( pattern, ); const scripted = pattern.getScriptedFields().map(field => field.spec); pattern.fields.replaceAll([...fields, ...scripted]); - await getDataPlugin().indexPatterns.updateSavedObject(pattern); + await this.patternService.updateSavedObject(pattern); }else{ throw new Error('Error selecting index pattern: pattern not found'); } @@ -27,4 +52,60 @@ export class PatternDataSource implements tDataSource { throw new Error(`Error selecting index pattern: ${error}`); } } + + async fetch(params: tSearchParams){ + const indexPattern = await this.patternService.get(this.id); + const { filters: defaultFilters = [], query, pagination, sorting, fields } = params; + if(!indexPattern){ + return; + } + const data = getDataPlugin(); + const searchSource = await data.search.searchSource.create(); + const fromField = (pagination?.pageIndex || 0) * (pagination?.pageSize || 100); + const sortOrder: OpenSearchQuerySortValue[] = sorting?.columns.map((column) => { + const sortDirection = column.direction === 'asc' ? 'asc' : 'desc'; + return { [column?.id || '']: sortDirection } as OpenSearchQuerySortValue; + }) || []; + let filters = defaultFilters; + + // check if dateRange is defined + if(params.dateRange && params.dateRange?.from && params.dateRange?.to){ + const { from, to } = params.dateRange; + filters = [ + ...filters, + { + range: { + [indexPattern.timeFieldName || 'timestamp']: { + gte: from, + lte: to, + format: 'strict_date_optional_time' + } + } + } + ] + } + + const searchParams = searchSource + .setParent(undefined) + .setField('filter', filters) + .setField('query', query) + .setField('sort', sortOrder) + .setField('size', pagination?.pageSize) + .setField('from', fromField) + .setField('index', indexPattern) + + // add fields + if (fields && Array.isArray(fields) && fields.length > 0){ + searchParams.setField('fields', fields); + } + try{ + return await searchParams.fetch(); + }catch(error){ + if(error.body){ + throw error.body; + } + throw error; + } + } + } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts new file mode 100644 index 0000000000..88de51fc8d --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts @@ -0,0 +1,3 @@ +export * from './vulnerabilities-data-source-repository'; +export * from './vulnerabilities-data-source'; +export * from './vulnerabilities-data-source-factory'; \ No newline at end of file From 2ccbccce39eb859ba2f5af136d5aa228fde2a76a Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Tue, 19 Mar 2024 16:22:54 -0300 Subject: [PATCH 12/36] Creating solution using data source in vulnerabilities --- .../common/data-source/data-source.ts | 7 ++ .../components/common/data-source/index.ts | 4 +- .../vulnerabilities-data-source-factory.ts | 27 +++++++ .../vulnerabilities-data-source-repository.ts | 44 ++++++++++++ .../vulnerabilities-data-source.ts | 70 +++++++++++++++++++ .../data-sources/vulnerabilities-states.ts | 4 +- 6 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts create mode 100644 plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts create mode 100644 plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts diff --git a/plugins/main/public/components/common/data-source/data-source.ts b/plugins/main/public/components/common/data-source/data-source.ts index e9a9f87f48..b9a2486722 100644 --- a/plugins/main/public/components/common/data-source/data-source.ts +++ b/plugins/main/public/components/common/data-source/data-source.ts @@ -1,5 +1,12 @@ +import { tFilter, tSearchParams } from './search-params-builder'; + export type tDataSource = { id: string; title: string; select(): Promise; + getFilters: () => Promise | tFilter[]; + setFilters: (filters: tFilter[]) => Promise | void; + getFields: () => Promise | any[]; + getFixedFilters: () => tFilter[]; + fetch: (params: tSearchParams) => 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 index ef95f4ee82..b00514a6fc 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -2,4 +2,6 @@ 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 +export * from './data-source-filter-manager'; +export * from './pattern'; +export * from './search-params-builder'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts new file mode 100644 index 0000000000..cd189b88f4 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts @@ -0,0 +1,27 @@ +import { tDataSource, tParsedIndexPattern, tDataSourceFactory } from "../../index"; +import { VulnerabilitiesDataSource } from "./vulnerabilities-data-source"; + +export class VulnerabilitiesDataSourceFactory implements tDataSourceFactory { + + async create(item: tParsedIndexPattern): Promise { + if(!item){ + throw new Error('Cannot create data source from null or undefined'); + }; + const dataSource = new VulnerabilitiesDataSource(item.id, item.attributes.title); + await dataSource.init(); + return dataSource; + } + async createAll(items: tParsedIndexPattern[]): Promise { + if(!items){ + throw new Error('Cannot create data source from null or undefined'); + }; + + const dataSources: VulnerabilitiesDataSource[] = []; + for (const item of items) { + const dataSource = new VulnerabilitiesDataSource(item.id, item.attributes.title); + await dataSource.init(); + dataSources.push(dataSource); + } + return dataSources; + } +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts new file mode 100644 index 0000000000..711135b46d --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts @@ -0,0 +1,44 @@ +import { PatternDataSourceRepository } from '../pattern-data-source-repository'; +import { tDataSource, tParsedIndexPattern } from '../../index' + + +export class VulnerabilitiesDataSourceRepository extends PatternDataSourceRepository { + constructor() { + super(); + } + + async get(id: string) { + const dataSource = await super.get(id); + if (this.validate(dataSource)) { + return dataSource; + } else { + throw new Error('Vulnerabilities index pattern not found'); + } + } + + async getAll() { + const indexs = await super.getAll(); + return indexs.filter(this.validate); + } + + validate(dataSource): boolean { + // check if the dataSource has the id or the title have the vulnerabilities word + const fieldsToCheck = ['id', 'attributes.title']; + // must check in the object and the attributes + for (const field of fieldsToCheck) { + if (dataSource[field] && dataSource[field].toLowerCase().includes('vulnerabilities')) { + return true; + } + } + return false; + } + + async getDefault(): Promise | tParsedIndexPattern | null { + throw new Error('Method not implemented') + } + + async setDefault(dataSource: tDataSource): Promise { + throw new Error('Method not implemented') + } + +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts new file mode 100644 index 0000000000..3ab6424d79 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts @@ -0,0 +1,70 @@ +import { tFilter } from '../../search-params-builder'; +import { PatternDataSource } from '../pattern-data-source'; + +import { AppState } from '../../../../../react-services/app-state'; +import { FilterHandler } from '../../../../../utils/filter-handler'; +import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../../../../../common/constants'; + +const VULNERABILITIES_GROUP_KEY = 'rules.group'; +const VULNERABILITIES_GROUP_VALUE = 'vulnerability-detector'; + +export class VulnerabilitiesDataSource extends PatternDataSource { + + constructor(id: string, title: string) { + super(id, title); + } + getClusterManagerFilters() { + const filterHandler = new FilterHandler(); + const isCluster = AppState.getClusterInfo().status == 'enabled'; + const managerFilter = filterHandler.managerQuery( + isCluster + ? AppState.getClusterInfo().cluster + : AppState.getClusterInfo().manager, + true, + VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ + AppState.getClusterInfo().status + ], + ); + return managerFilter; + } + + getRuleGroupsFilter() { + return { + meta: { + //removable: false, // not exists in the original type - removed to preserve the original type + index: this.id, + negate: false, + disabled: false, + alias: null, + type: 'phrase', + key: VULNERABILITIES_GROUP_KEY, + value: VULNERABILITIES_GROUP_VALUE, + params: { + query: VULNERABILITIES_GROUP_VALUE, + type: 'phrase', + }, + controlledBy: 'wazuh' // or concatenate with the index name + }, + query: { + match: { + 'rules.group': { + query: VULNERABILITIES_GROUP_VALUE, + type: 'phrase', + } + }, + }, + $state: { + store: 'appState', + }, + }; + } + + getFixedFilters(): tFilter[] { + return [ + ...[this.getClusterManagerFilters()], + this.getRuleGroupsFilter() + ] + } + + +} \ No newline at end of file diff --git a/plugins/main/public/react-services/data-sources/vulnerabilities-states.ts b/plugins/main/public/react-services/data-sources/vulnerabilities-states.ts index 24b6585473..77f841fa8a 100644 --- a/plugins/main/public/react-services/data-sources/vulnerabilities-states.ts +++ b/plugins/main/public/react-services/data-sources/vulnerabilities-states.ts @@ -10,7 +10,7 @@ import { IDataSourcesFilterManager } from './types'; * @param indexPatternTitle Index pattern title * @returns */ -function getFilterExcludeManager(indexPatternTitle: string) { +export function getFilterExcludeManager(indexPatternTitle: string) { return { meta: { alias: null, @@ -33,7 +33,7 @@ function getFilterExcludeManager(indexPatternTitle: string) { * @param indexPatternTitle * @returns */ -function getFilterAllowedAgents( +export function getFilterAllowedAgents( agentsIds: string[], indexPatternTitle: string, ) { From d11cd35e59b528ae21eeb239494fff15c7186111 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Tue, 19 Mar 2024 16:23:23 -0300 Subject: [PATCH 13/36] Adding data source filter manager --- .../data-source/data-source-filter-manager.ts | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 plugins/main/public/components/common/data-source/data-source-filter-manager.ts diff --git a/plugins/main/public/components/common/data-source/data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/data-source-filter-manager.ts new file mode 100644 index 0000000000..1fcaa43a53 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-filter-manager.ts @@ -0,0 +1,184 @@ +import { tDataSource } from "./data-source"; +import { tFilter } from "./search-params-builder"; +import { AppState } from '../../../react-services/app-state'; +import { FilterHandler } from '../../../utils/filter-handler'; +import store from '../../../redux/store'; +import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../react-services/data-sources/vulnerabilities-states'; + +const baseFixedFilter = { + meta: { + index: null, + negate: false, + disabled: false, + alias: null, + type: 'phrase', + key: null, + value: null, + params: { + query: null, + type: 'phrase', + }, + }, + query: { + match: null, + }, + $state: { + store: 'appState', + isImplicit: true, + }, +}; + +type FiltersStateRepository = { + getAllowAgents: () => string[]; + getExcludeManager: () => boolean; + getPinnedAgent: () => object; +} + + +/** + * Use case + * + * - The data source filter manager receives the filters from the filter manager + * - When the filters received only store the filters that was added by the user. + * Not have the property isImplicit ($state.isImplicit) defined or not have the meta.controlledBy property defined + * Also, when the meta.index is not the same as the dataSource.id + * + * - Then add the fixedFilters and concatenate with the user filters + */ + + +/** + * Get the filters from the filter manager + * + * - filter the filters that was added by the user + * - check if agent is pinned (check in session storage or redux store if is possible wz-shared-selected-agent) + * + * Add filters only necessary to fetch the data from the data source + * - + */ + +export class DataSourceFilterManager { + + constructor(private dataSource: tDataSource, private filters: tFilter[] = []) { + if(!dataSource) { + throw new Error('Data source is required'); + } + this.dataSource = dataSource; + this.filters = this.filterUserFilters(filters); + } + + /** + * Get the filters necessary to fetch the data from the data source + * @returns + */ + fetch() { + return this.dataSource.fetch({ + filters: this.getFetchFilters() + }); + } + + setFilters(filters: tFilter[]) { + this.filters = this.filterUserFilters(filters); + } + + /** + * Filter the filters that was added by the user + * The filters must not have: + * - the property isImplicit ($state.isImplicit) defined + * - the meta.controlledBy property defined + * - the meta.index is not the same as the dataSource.id + * + */ + private filterUserFilters(filters: tFilter[]) { + return filters.filter( + filter => !(filter.$state['isImplicit'] || !filter.meta?.controlledBy || filter.meta?.index !== this.dataSource.id) + ); + } + + getFixedFilters(): tFilter[] { + const fixedFilters = this.dataSource.getFixedFilters(); + const pinnedAgent = this.getPinnedAgentFilter(); + + return [ + ...fixedFilters, + ...pinnedAgent + ].map(filter => this.addMetaDataInFilter(filter)); + } + + getFilters() { + return [ + ...this.filters, + ...this.getFixedFilters() + ] + } + + /** + * Concatenate the filters to fetch the data from the data source + * @returns + */ + getFetchFilters(): tFilter[] { + const filters = this.getFilters(); + const excludeManager = this.getExcludeManagerFilter(); + const allowedAgents = this.getAllowAgentsFilter(); + + return [ + ...filters, + ...allowedAgents, + ...excludeManager + ].map(filter => this.addMetaDataInFilter(filter)); + + } + + addMetaDataInFilter(filter: tFilter) { + filter.meta.controlledBy = 'data-source-filter-manager'; + //filter.meta.index = this.dataSource.id; + return filter; + } + + getPinnedAgentFilter(): tFilter[] { + const agentId = store.getState().appStateReducers?.currentAgentData?.id; + if(!agentId) return []; + return [{ + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: false, + params: { query: agentId }, + type: 'phrase', + index: this.dataSource.id, + controlledBy: 'wazuh' + }, + query: { + match: { + 'agent.id': { + query: agentId, + type: 'phrase', + }, + }, + }, + $state: { + store: 'appState' // check appStore is not assignable, why is stored here? + }, + } as tFilter] + } + + /** + * Add the filter to exclude the data related to servers (managers) due to the setting hideManagerAlerts is enabled + */ + getExcludeManagerFilter(): tFilter[] { + return store.getState().appConfig?.data?.hideManagerAlerts ? + [getFilterExcludeManager(this.dataSource.title) as tFilter] : []; + } + + /** + * Add the allowed agents related to the user permissions to read data from agents in the + API server + */ + getAllowAgentsFilter(): tFilter[] { + const allowedAgents = store.getState().appStateReducers?.allowedAgents; + return allowedAgents.lenght > 0 ? + [getFilterAllowedAgents(allowedAgents,this.dataSource.title) as tFilter] : [] + } + +} \ No newline at end of file From a4e1b652f6fbf72cb03ad85072e9bf16739139c0 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Tue, 19 Mar 2024 16:23:58 -0300 Subject: [PATCH 14/36] Applying data source filter manager in dashboard and inventory --- .../data-source-filter-manager.test.ts | 5 ++ .../data-source/search-params-builder.ts | 65 +++++++++++++++++++ .../dashboards/inventory/inventory.tsx | 38 ++++++++++- .../dashboards/overview/dashboard.tsx | 34 +++++++++- 4 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts create mode 100644 plugins/main/public/components/common/data-source/search-params-builder.ts diff --git a/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts b/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts new file mode 100644 index 0000000000..8b0a3a27a4 --- /dev/null +++ b/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts @@ -0,0 +1,5 @@ +describe('DataSourceFilterManager', () => { + it('should initialize the data source filter manager', () => { + + }) +}) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/search-params-builder.ts b/plugins/main/public/components/common/data-source/search-params-builder.ts new file mode 100644 index 0000000000..de06ac341c --- /dev/null +++ b/plugins/main/public/components/common/data-source/search-params-builder.ts @@ -0,0 +1,65 @@ +import { Filter } from "../../../../../../src/plugins/data/common"; + +export type tFilter = Filter; + +export type tSearchParams = { + filters?: tFilter[]; + query?: any; + pagination?: { + pageIndex?: number; + pageSize?: number; + }; + fields?: string[], + sorting?: { + columns: { + id: string; + direction: 'asc' | 'desc'; + }[]; + }; + dateRange?: { + from: string; + to: string; + }; +} + +export class SearchParamsBuilder { + private searchParams: tSearchParams; + + constructor() { + this.searchParams = {}; + } + + addFilters(filters: tFilter[]): SearchParamsBuilder { + this.searchParams.filters = filters; + return this; + } + + addQuery(query: any): SearchParamsBuilder { + this.searchParams.query = query; + return this; + } + + addPagination(pageIndex: number, pageSize: number): SearchParamsBuilder { + this.searchParams.pagination = { pageIndex, pageSize }; + return this; + } + + addFields(fields: string[]): SearchParamsBuilder { + this.searchParams.fields = fields; + return this; + } + + addSorting(columns: { id: string, direction: 'asc' | 'desc' }[]): SearchParamsBuilder { + this.searchParams.sorting = { columns }; + return this; + } + + addDateRange(from: string, to: string): SearchParamsBuilder { + this.searchParams.dateRange = { from, to }; + return this; + } + + build(): tSearchParams { + return this.searchParams; + } +} \ No newline at end of file diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index 9174ced121..ac233254ed 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -47,17 +47,48 @@ import { withVulnerabilitiesStateDataSource } from '../../common/hocs/validate-v import { ModuleEnabledCheck } from '../../common/components/check-module-enabled'; import { DataSourceFilterManagerVulnerabilitiesStates } from '../../../../../react-services/data-sources'; +import { + DataSourceSelector, + VulnerabilitiesDataSourceRepository, + PatternDataSourceFactory, + DataSourceFilterManager, + AlertsDataSourceRepository} from '../../../../common/data-source'; +import { + VulnerabilitiesDataSourceFactory, +} from '../../../../common/data-source/pattern/vulnerabilities'; + + const InventoryVulsComponent = () => { const appConfig = useAppConfig(); const VULNERABILITIES_INDEX_PATTERN_ID = appConfig.data['vulnerabilities.pattern']; const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, - onMount: vulnerabilityIndexFiltersAdapter, - onUpdate: onUpdateAdapter, - onUnMount: restorePrevIndexFiltersAdapter, + //onMount: vulnerabilityIndexFiltersAdapter, + //onUpdate: onUpdateAdapter, + //onUnMount: restorePrevIndexFiltersAdapter, }); + useEffect(() => { + console.log('on mount'); + }, []) + + const initDataSource = async () => { + if(!searchBarProps.dateRangeFrom || !searchBarProps.dateRangeTo){ + return; + } + const factory = new VulnerabilitiesDataSourceFactory(); + const repository = new VulnerabilitiesDataSourceRepository(); + + const dataSources = await factory.createAll(await repository.getAll()); + const dataSource = dataSources[0]; + + const filterManager = new DataSourceFilterManager(dataSource, searchBarProps.filters); + console.log('INVENTORY filters', filterManager.getFilters()); + console.log('INVENTORY fixed filters', filterManager.getFixedFilters()); + console.log('INVENTORY fetch filters', filterManager.getFetchFilters()); + } + const fetchFilters = DataSourceFilterManagerVulnerabilitiesStates.getFilters( searchBarProps.filters, VULNERABILITIES_INDEX_PATTERN_ID, @@ -114,6 +145,7 @@ const InventoryVulsComponent = () => { useEffect(() => { if (!isLoading) { + initDataSource(); setIndexPattern(indexPatterns?.[0] as IndexPattern); search({ indexPattern: indexPatterns?.[0] as IndexPattern, diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index 6508987edb..d88a4b3f77 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -31,6 +31,16 @@ import { ModuleEnabledCheck } from '../../common/components/check-module-enabled import { DataSourceFilterManagerVulnerabilitiesStates } from '../../../../../react-services/data-sources'; import { DashboardContainerInput } from '../../../../../../../../src/plugins/dashboard/public'; +import { + DataSourceSelector, + VulnerabilitiesDataSourceRepository, + PatternDataSourceFactory, + DataSourceFilterManager, + AlertsDataSourceRepository} from '../../../../common/data-source'; +import { + VulnerabilitiesDataSourceFactory, +} from '../../../../common/data-source/pattern/vulnerabilities'; + const plugins = getPlugins(); const SearchBar = getPlugins().data.ui.SearchBar; @@ -47,11 +57,28 @@ const DashboardVulsComponent: React.FC = () => { appConfig.data['vulnerabilities.pattern']; const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, - onMount: vulnerabilityIndexFiltersAdapter, + /*onMount: vulnerabilityIndexFiltersAdapter, onUpdate: onUpdateAdapter, - onUnMount: restorePrevIndexFiltersAdapter, + onUnMount: restorePrevIndexFiltersAdapter,*/ }); + const initDataSource = async () => { + if(!searchBarProps.dateRangeFrom || !searchBarProps.dateRangeTo){ + return; + } + const factory = new VulnerabilitiesDataSourceFactory(); + const repository = new VulnerabilitiesDataSourceRepository(); + + const dataSources = await factory.createAll(await repository.getAll()); + const dataSource = dataSources[0]; + + const filterManager = new DataSourceFilterManager(dataSource, searchBarProps.filters); + console.log('DASHBOARD filters', filterManager.getFilters()); + console.log('DASHBOARD fixed filters', filterManager.getFixedFilters()); + console.log('DASHBOARD fetch filters', filterManager.getFetchFilters()); + } + + /* This function is responsible for updating the storage filters so that the filters between dashboard and inventory added using visualizations call to actions. Without this feature, filters added using visualizations call to actions are @@ -70,8 +97,11 @@ const DashboardVulsComponent: React.FC = () => { const [isSearching, setIsSearching] = useState(false); const [results, setResults] = useState({} as SearchResponse); + + useEffect(() => { if (!isLoading) { + initDataSource(); search({ indexPattern: indexPatterns?.[0] as IndexPattern, filters: fetchFilters, From 4352d8141d1c3456c96c5ecba77c3b02aee10775 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Thu, 21 Mar 2024 10:07:27 -0300 Subject: [PATCH 15/36] Created data source hook --- plugins/main/common/constants.ts | 2 + .../data-source-filter-manager.test.ts | 287 +++++++++++++++++- .../data-source/data-source-filter-manager.ts | 177 ++++++----- .../common/data-source/hooks/index.ts | 1 + .../data-source/hooks/use-data-source.ts | 110 +++++++ .../components/common/data-source/index.ts | 3 +- .../common/data-source/pattern/index.ts | 2 +- .../pattern/pattern-data-source-repository.ts | 19 -- .../pattern/pattern-data-source.ts | 64 ++-- .../vulnerabilities-data-source-repository.ts | 8 - .../vulnerabilities-data-source.ts | 18 +- .../data-source/search-params-builder.ts | 2 +- 12 files changed, 537 insertions(+), 156 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/hooks/index.ts create mode 100644 plugins/main/public/components/common/data-source/hooks/use-data-source.ts diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index d0a7c6539a..4a2a75f5e9 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -225,6 +225,8 @@ export enum WAZUH_MENU_SETTINGS_SECTIONS_ID { export const AUTHORIZED_AGENTS = 'authorized-agents'; export const DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER = 'exclude-server'; +export const DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT = 'pinned-agent'; +export const DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER = 'cluster-manager'; // Wazuh links export const WAZUH_LINK_GITHUB = 'https://github.com/wazuh'; diff --git a/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts b/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts index 8b0a3a27a4..7935aba31d 100644 --- a/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts +++ b/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts @@ -1,5 +1,288 @@ +import { DataSourceFilterManager, tDataSource, tSearchParams, tFilter } from './index'; + +// mock the store +import store from '../../../redux/store'; +import { DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT } from '../../../../common/constants'; + +// mock the AppState +jest.mock('../../../redux/store', () => ({ + getState: jest.fn().mockReturnValue({ + appStateReducers: { + } + }) +})); + +class DataSourceMocked implements tDataSource { + constructor(id: string, title: string) { + this.id = id; + this.title = title; + } + id: string; + title: string; + select = jest.fn(); + getFilters = jest.fn(); + setFilters = jest.fn(); + getFields = jest.fn(); + getFixedFilters = jest.fn(); + fetch = jest.fn(); +} + + +const createFilter = (id: string, value: string, index: string) => { + return { + meta: { + index: index, + negate: false, + disabled: false, + alias: null, + type: 'phrase', + key: id, + value: value, + params: { + query: value, + type: 'phrase', + }, + }, + query: { + match: { + [id]: value, + }, + }, + $state: { + store: 'appState', + }, + } as tFilter; +} + + describe('DataSourceFilterManager', () => { - it('should initialize the data source filter manager', () => { - + + beforeEach(() => { + jest.clearAllMocks(); + }) + + describe('constructor', () => { + it('should initialize the data source filter manager', () => { + const filterManager = new DataSourceFilterManager(new DataSourceMocked('my-id', 'my-title'), []); + expect(filterManager).toBeDefined(); + }) + + it('should return ERROR when the data source is not defined', () => { + try { + new DataSourceFilterManager(null as any, []); + } catch (error) { + expect(error.message).toBe('Data source is required'); + } + }) + + it('should filter the filters received in the constructor then no keep the filters with different index and merge with the fixed filters', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const filterDifIndex = createFilter('agent.id', '1', 'different filter'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, [filterDifIndex]); + expect(filterManager.getFilters()).not.toContainEqual(filterDifIndex); + expect(filterManager.getFilters()).toContainEqual(fixedFilter); + }) + + it('should filter the filters received in the constructor then no keep the filters with meta.controlledBy property (with same index)', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const filterSameIndexControlled = createFilter('agent.id', '1', 'my-index'); + filterSameIndexControlled.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT; + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, [filterSameIndexControlled]); + expect(filterManager.getFilters()).not.toContainEqual(filterSameIndexControlled); + expect(filterManager.getFilters()).toEqual([fixedFilter]); + }) + + it('should filter the filters received in the constructor then no keep the filters with $state.isImplicit property (with same index)', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const filterSameIndexControlled = createFilter('agent.id', '1', 'my-index'); + filterSameIndexControlled.$state = { + ...filterSameIndexControlled.$state, + // isImplicit' does not exist in type 'FilterState' + // @ts-ignore + isImplicit: true + } + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, [filterSameIndexControlled]); + expect(filterManager.getFilters()).not.toContainEqual(filterSameIndexControlled); + expect(filterManager.getFilters()).toContainEqual(fixedFilter); + }) + + + it('should filter the filters received in the constructor and keep the filters with the same index and merge with the fixed ', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const sameIndexFilter = createFilter('agent.id', '1', 'my-index'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, [sameIndexFilter]); + expect(filterManager.getFilters()).toEqual([sameIndexFilter, fixedFilter]); + }); + + + }) + + describe('getFilters', () => { + it('should return the filters with the fixed filters included defined in data source', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, []); + const filters = filterManager.getFilters(); + expect(filters).toEqual([fixedFilter]); + }) + + it('should return the filters merged the fixed filters included defined in data source', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const sameIndexFilter = createFilter('agent.id', '1', 'my-index'); + const filterManager = new DataSourceFilterManager(dataSource, [sameIndexFilter]); + const filters = filterManager.getFilters(); + expect(filters).toEqual([fixedFilter, sameIndexFilter]); + }) + + it('should return only the fixed filters when receives invalid filters', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const anotherIndexFilter = createFilter('agent.id', '1', 'another-index'); + const filterManager = new DataSourceFilterManager(dataSource, [anotherIndexFilter]); + const filters = filterManager.getFilters(); + expect(filters).toEqual([fixedFilter]); + expect(filters).not.toContainEqual(anotherIndexFilter); + }) + + }) + + describe('getFixedFilters', () => { + it('should return the fixed filters defined in the data source', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + (store.getState as jest.Mock).mockReturnValue( + { + appStateReducers: { + } + }); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, []); + const filters = filterManager.getFixedFilters(); + expect(filters).toContainEqual(fixedFilter); + expect(dataSource.getFixedFilters).toHaveBeenCalledTimes(1); + }); + + it('should return the fixed filters merged with the pinned agent filter when correspond', () => { + // mock store.getState + (store.getState as jest.Mock).mockReturnValue( + { + appStateReducers: { + currentAgentData: { + id: '001' + } + } + }); + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, []); + const filters = filterManager.getFixedFilters(); + const agentPinnedFilter = filterManager.getPinnedAgentFilter(); + expect(filters).toContainEqual(fixedFilter); + expect(filters).toEqual([fixedFilter, ...agentPinnedFilter]); + }); + + it('should return only the fixed filters from the data source when the pinned agent filter is not defined', () => { + // mock store.getState + (store.getState as jest.Mock).mockReturnValue( + { + appStateReducers: { + } + }); + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const fixedFilter = createFilter('agent.id', '1', 'my-index'); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); + const filterManager = new DataSourceFilterManager(dataSource, []); + const filters = filterManager.getFixedFilters(); + expect(filters).toContainEqual(fixedFilter); + expect(filters).not.toContainEqual(filterManager.getPinnedAgentFilter()); + }) + + }) + + describe('getFetchFilters', () => { + it('should return the filters to fetch the data from the data source merging filters', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const storedFilter = createFilter('agent.id', '1', 'my-index'); + + const filterManager = new DataSourceFilterManager(dataSource, []); + jest.spyOn(filterManager, 'getFilters').mockReturnValue([storedFilter]); + }) + + it('should return the filters to fetch the data merging the filters stored and the excluded manager filter', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const storedFilter = createFilter('agent.id', '1', 'my-index'); + (store.getState as jest.Mock).mockReturnValue( + { + appConfig: { + data: { + hideManagerAlerts: true + } + } + }); + const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + const excludeManager = filterManager.getExcludeManagerFilter(); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); + const filters = filterManager.getFetchFilters(); + expect(filters).toEqual([storedFilter, ...excludeManager]); + }) + + it('should return the filters to fetch the data merging the filters stored without the excluded manager filter when is not defined', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const storedFilter = createFilter('agent.id', '1', 'my-index'); + (store.getState as jest.Mock).mockReturnValue( + { + appConfig: { + data: { + } + } + }); + const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); + const filters = filterManager.getFetchFilters(); + expect(filters).toEqual([storedFilter]); + }) + + it('should return the filters to fetch the data merging the filters stored and the allowed agents filter', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const storedFilter = createFilter('agent.id', '1', 'my-index'); + (store.getState as jest.Mock).mockReturnValue( + { + appStateReducers: { + allowedAgents: ['001'] + } + }); + const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); + const allowedAgents = filterManager.getAllowAgentsFilter(); + const filters = filterManager.getFetchFilters(); + expect(filters).toEqual([storedFilter, ...allowedAgents]); + }) + + it('should return the filters to fetch the data merging the filters stored without the allowed agents filter when is not defined', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const storedFilter = createFilter('agent.id', '1', 'my-index'); + (store.getState as jest.Mock).mockReturnValue( + { + appStateReducers: { + } + }); + const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); + const filters = filterManager.getFetchFilters(); + expect(filters).toEqual([storedFilter]); + }) }) }) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/data-source-filter-manager.ts index 1fcaa43a53..3d2188412b 100644 --- a/plugins/main/public/components/common/data-source/data-source-filter-manager.ts +++ b/plugins/main/public/components/common/data-source/data-source-filter-manager.ts @@ -1,32 +1,9 @@ import { tDataSource } from "./data-source"; -import { tFilter } from "./search-params-builder"; -import { AppState } from '../../../react-services/app-state'; -import { FilterHandler } from '../../../utils/filter-handler'; +import { tFilter, tSearchParams } from "./search-params-builder"; import store from '../../../redux/store'; import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../react-services/data-sources/vulnerabilities-states'; +import { DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT } from "../../../../common/constants"; -const baseFixedFilter = { - meta: { - index: null, - negate: false, - disabled: false, - alias: null, - type: 'phrase', - key: null, - value: null, - params: { - query: null, - type: 'phrase', - }, - }, - query: { - match: null, - }, - $state: { - store: 'appState', - isImplicit: true, - }, -}; type FiltersStateRepository = { getAllowAgents: () => string[]; @@ -57,23 +34,37 @@ type FiltersStateRepository = { * - */ -export class DataSourceFilterManager { +export type tDataSourceFilterManager = { + fetch: () => Promise; + setFilters: (filters: tFilter[]) => void; + getFixedFilters: () => tFilter[]; + getFilters: () => tFilter[]; + getFetchFilters: () => tFilter[]; + addMetaDataInFilter: (filter: tFilter) => tFilter; + // global filters + getPinnedAgentFilter: () => tFilter[]; + getExcludeManagerFilter: () => tFilter[]; + getAllowAgentsFilter: () => tFilter[]; +} + +export class DataSourceFilterManager implements tDataSourceFilterManager { constructor(private dataSource: tDataSource, private filters: tFilter[] = []) { - if(!dataSource) { + if (!dataSource) { throw new Error('Data source is required'); } this.dataSource = dataSource; - this.filters = this.filterUserFilters(filters); + this.filters = filters.length ? this.filterUserFilters(filters) : []; } /** * Get the filters necessary to fetch the data from the data source * @returns */ - fetch() { + fetch(params: Omit = {}): Promise { return this.dataSource.fetch({ - filters: this.getFetchFilters() + ...params, + filters: this.getFetchFilters(), }); } @@ -90,11 +81,18 @@ export class DataSourceFilterManager { * */ private filterUserFilters(filters: tFilter[]) { - return filters.filter( - filter => !(filter.$state['isImplicit'] || !filter.meta?.controlledBy || filter.meta?.index !== this.dataSource.id) - ); + if (!filters) return []; + return this.removeRepeatedFilters(filters.filter( + filter => !(filter?.$state?.['isImplicit'] || filter.meta?.controlledBy || filter.meta?.index !== this.dataSource.id) + )); } + /** + * Return the fixed filters. The fixed filters are filters that cannot be removed by the user. + * The filters for the specific data source are defined in the data source. + * Also, exists fixed filters that are defined in the data source filter manager (globally). + * @returns + */ getFixedFilters(): tFilter[] { const fixedFilters = this.dataSource.getFixedFilters(); const pinnedAgent = this.getPinnedAgentFilter(); @@ -105,10 +103,15 @@ export class DataSourceFilterManager { ].map(filter => this.addMetaDataInFilter(filter)); } + /** + * Return the filters that was added by the user and the fixed filters. + * This can be use to show the filters in the UI (For instance: SearchBar) + * @returns + */ getFilters() { return [ + ...this.getFixedFilters(), ...this.filters, - ...this.getFixedFilters() ] } @@ -117,68 +120,92 @@ export class DataSourceFilterManager { * @returns */ getFetchFilters(): tFilter[] { - const filters = this.getFilters(); - const excludeManager = this.getExcludeManagerFilter(); - const allowedAgents = this.getAllowAgentsFilter(); - - return [ - ...filters, - ...allowedAgents, - ...excludeManager - ].map(filter => this.addMetaDataInFilter(filter)); - + const filters = this.getFilters(); + const excludeManager = this.getExcludeManagerFilter(); + const allowedAgents = this.getAllowAgentsFilter(); + + return [ + ...filters, + ...allowedAgents, + ...excludeManager + ].map(filter => this.addMetaDataInFilter(filter)); + + } + + /** + * Remove filter repeated filters in query property + * @param filter + * @returns + */ + + removeRepeatedFilters(filters: tFilter) { + if (!filters) return filters; + const query = filters.query; + if (!query) return filters; + const keys = Object.keys(query); + if (keys.length === 1) { + const key = keys[0]; + if (query[key].query) { + query[key].query = Array.isArray(query[key].query) ? Array.from(new Set(query[key].query)) : query[key].query; + } + } + return filters; } addMetaDataInFilter(filter: tFilter) { - filter.meta.controlledBy = 'data-source-filter-manager'; + //check is necessary add the controlled by here or add in the data source + //filter.meta.controlledBy = 'data-source-filter-manager'; //filter.meta.index = this.dataSource.id; return filter; } + /** + * Returns the filter when the an agent is pinned (saved in the session storage or redux store) + */ getPinnedAgentFilter(): tFilter[] { - const agentId = store.getState().appStateReducers?.currentAgentData?.id; - if(!agentId) return []; - return [{ - meta: { - alias: null, - disabled: false, - key: 'agent.id', - negate: false, - params: { query: agentId }, - type: 'phrase', - index: this.dataSource.id, - controlledBy: 'wazuh' - }, - query: { - match: { - 'agent.id': { - query: agentId, - type: 'phrase', - }, + const agentId = store.getState().appStateReducers?.currentAgentData?.id; + if (!agentId) return []; + return [{ + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: false, + params: { query: agentId }, + type: 'phrase', + index: this.dataSource.id, + controlledBy: DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT + }, + query: { + match: { + 'agent.id': { + query: agentId, + type: 'phrase', }, }, - $state: { - store: 'appState' // check appStore is not assignable, why is stored here? - }, - } as tFilter] + }, + $state: { + store: 'appState' // check appStore is not assignable, why is stored here? + }, + } as tFilter] } /** - * Add the filter to exclude the data related to servers (managers) due to the setting hideManagerAlerts is enabled + * Return the filter to exclude the data related to servers (managers) due to the setting hideManagerAlerts is enabled */ - getExcludeManagerFilter(): tFilter[] { - return store.getState().appConfig?.data?.hideManagerAlerts ? - [getFilterExcludeManager(this.dataSource.title) as tFilter] : []; + getExcludeManagerFilter(): tFilter[] { + return store.getState().appConfig?.data?.hideManagerAlerts ? + [getFilterExcludeManager(this.dataSource.title) as tFilter] : []; } /** - * Add the allowed agents related to the user permissions to read data from agents in the + * Return the allowed agents related to the user permissions to read data from agents in the API server */ getAllowAgentsFilter(): tFilter[] { - const allowedAgents = store.getState().appStateReducers?.allowedAgents; + const allowedAgents = store.getState().appStateReducers?.allowedAgents || []; return allowedAgents.lenght > 0 ? - [getFilterAllowedAgents(allowedAgents,this.dataSource.title) as tFilter] : [] + [getFilterAllowedAgents(allowedAgents, this.dataSource.title) as tFilter] : [] } } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/hooks/index.ts b/plugins/main/public/components/common/data-source/hooks/index.ts new file mode 100644 index 0000000000..7898960b91 --- /dev/null +++ b/plugins/main/public/components/common/data-source/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-data-source'; \ No newline at end of file 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 new file mode 100644 index 0000000000..26260de648 --- /dev/null +++ b/plugins/main/public/components/common/data-source/hooks/use-data-source.ts @@ -0,0 +1,110 @@ +import React, { useEffect, useState } from "react"; +import { tFilter, tSearchParams } from "../search-params-builder"; +import { + tDataSource, + tDataSourceFactory, + DataSourceRepository, + DataSourceSelector, + DataSourceFilterManager +} from '../index'; + +type tUseDataSourceProps = { + filters: tFilter[]; + factory: tDataSourceFactory; + repository: DataSourceRepository; +} + +type tUseDataSourceLoadedReturns = { + isLoading: boolean; + dataSource: tDataSource; + filters: tFilter[]; + fetchFilters: tFilter[]; + fixedFilters: tFilter[]; + fetchData: () => Promise; + setFilters: (filters: tFilter[]) => void; +} + +type tUseDataSourceNotLoadedReturns = { + isLoading: boolean; + dataSource: undefined; + filters: []; + fetchFilters: []; + fixedFilters: []; + fetchData: (params: Omit) => Promise; + setFilters: (filters: tFilter[]) => void; +} + +export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { + const { filters: defaultFilters = [], factory, repository } = props; + + if (!factory || !repository) { + throw new Error('Factory and repository are required'); + } + + const [dataSource, setDataSource] = useState(); + const [dataSourceFilterManager, setDataSourceFilterManager] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [fetchFilters, setFetchFilters] = useState([]); + const [allFilters, setAllFilters] = useState([]); + + const setFilters = (filters: tFilter[]) => { + if (!dataSourceFilterManager) { + return; + } + dataSourceFilterManager?.setFilters(filters); + setAllFilters(dataSourceFilterManager?.getFilters() || []); + setFetchFilters(dataSourceFilterManager?.getFetchFilters() || []); + } + + const fetchData = async (params: Omit) => { + if (!dataSourceFilterManager) { + return + } + return await dataSourceFilterManager?.fetch(params); + } + + useEffect(() => { + init(); + }, []) + + const init = async () => { + setIsLoading(true); + const selector = new DataSourceSelector(repository, factory); + const dataSource = await selector.getFirstValidDataSource(); + if (!dataSource) { + throw new Error('No valid data source found'); + } + const dataSourceFilterManager = new DataSourceFilterManager(dataSource, defaultFilters); + if (!dataSourceFilterManager) { + throw new Error('Error creating filter manager'); + } + setAllFilters(dataSourceFilterManager.getFilters()); + setFetchFilters(dataSourceFilterManager.getFetchFilters()); + setDataSourceFilterManager(dataSourceFilterManager); + setDataSource(dataSource); + setIsLoading(false); + } + + if (isLoading) { + return { + isLoading: true, + dataSource, + filters: [], + fetchFilters: [], + fixedFilters: [], + fetchData, + setFilters + } + } + + return { + isLoading: false, + dataSource, + filters: allFilters, + fetchFilters, + fixedFilters: dataSourceFilterManager?.getFixedFilters() || [], + fetchData, + setFilters + } + +} \ 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 index b00514a6fc..89baeb115b 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -4,4 +4,5 @@ export * from './data-source-factory'; export * from './data-source-selector'; export * from './data-source-filter-manager'; export * from './pattern'; -export * from './search-params-builder'; \ No newline at end of file +export * from './search-params-builder'; +export * from './hooks'; \ 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 index 1005379f31..a92cb6861e 100644 --- a/plugins/main/public/components/common/data-source/pattern/index.ts +++ b/plugins/main/public/components/common/data-source/pattern/index.ts @@ -3,4 +3,4 @@ export * from './pattern-data-source'; export * from './pattern-data-source-factory'; export * from './pattern-data-source'; export * from './alerts'; -export * from './vulnerabilities'; \ No newline at end of file +export * from './vulnerabilities'; 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 index 099ba9d009..97c10cafdf 100644 --- 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 @@ -4,25 +4,6 @@ import { PatternDataSource } from "./pattern-data-source"; import { GenericRequest } from '../../../../react-services/generic-request'; import { AppState } from '../../../../react-services'; -/* -{ - attributes: { - fields: "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"cluster.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"configSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"configSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"configSum\"}}},{\"name\":\"dateAdd\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"disconnection_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group\"}}},{\"name\":\"group_config_status\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"group_config_status.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"group_config_status\"}}},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"lastKeepAlive\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manager\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manager.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"manager\"}}},{\"name\":\"mergedSum\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"mergedSum.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"mergedSum\"}}},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"node_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"node_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"node_name\"}}},{\"name\":\"os.arch\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.arch.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.arch\"}}},{\"name\":\"os.build\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.build.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.build\"}}},{\"name\":\"os.major\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.major.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.major\"}}},{\"name\":\"os.minor\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.minor.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.minor\"}}},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.name\"}}},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.platform.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.platform\"}}},{\"name\":\"os.uname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.uname.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.uname\"}}},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"os.version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"os.version\"}}},{\"name\":\"registerIP\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"registerIP.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"registerIP\"}}},{\"name\":\"status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"version\"}}}]" - title: "wazuh-monitoring-*" - }, - id: 'wazuh-monitoring-*; - migrationVersion: { - index-pattern: '7.6.0' - }, - namespace: ['default'], - references: [], - score: 0, - type: "index-pattern" - updated_at: "2024-01-09T16:37:31.020Z" - version: "WzQsMV0=" -} -*/ - export type tSavedObjectResponse = { data: { attributes: { 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 cb61703776..647e546605 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 @@ -1,7 +1,7 @@ import { tDataSource, tSearchParams, tFilter } from "../index"; import { getDataPlugin } from '../../../../kibana-services'; import { Filter, IndexPatternsContract, OpenSearchQuerySortValue } from "../../../../../../../src/plugins/data/public"; - +import { search } from '../../search-bar/search-bar-service'; export class PatternDataSource implements tDataSource { id: string; @@ -59,53 +59,31 @@ export class PatternDataSource implements tDataSource { if(!indexPattern){ return; } - const data = getDataPlugin(); - const searchSource = await data.search.searchSource.create(); - const fromField = (pagination?.pageIndex || 0) * (pagination?.pageSize || 100); - const sortOrder: OpenSearchQuerySortValue[] = sorting?.columns.map((column) => { - const sortDirection = column.direction === 'asc' ? 'asc' : 'desc'; - return { [column?.id || '']: sortDirection } as OpenSearchQuerySortValue; - }) || []; - let filters = defaultFilters; - // check if dateRange is defined - if(params.dateRange && params.dateRange?.from && params.dateRange?.to){ - const { from, to } = params.dateRange; - filters = [ - ...filters, + try { + const results = await search( { - range: { - [indexPattern.timeFieldName || 'timestamp']: { - gte: from, - lte: to, - format: 'strict_date_optional_time' - } - } + indexPattern, + filters: defaultFilters, + query, + pagination, + sorting, + fields: fields } - ] - } - - const searchParams = searchSource - .setParent(undefined) - .setField('filter', filters) - .setField('query', query) - .setField('sort', sortOrder) - .setField('size', pagination?.pageSize) - .setField('from', fromField) - .setField('index', indexPattern) - - // add fields - if (fields && Array.isArray(fields) && fields.length > 0){ - searchParams.setField('fields', fields); - } - try{ - return await searchParams.fetch(); + ); + + return results; }catch(error){ - if(error.body){ - throw error.body; - } - throw error; + throw new Error(`Error fetching data: ${error}`); } + } + + getFetchFilters(){ + return []; + } + + + } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts index 711135b46d..6fc9e9e4a8 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts @@ -33,12 +33,4 @@ export class VulnerabilitiesDataSourceRepository extends PatternDataSourceReposi return false; } - async getDefault(): Promise | tParsedIndexPattern | null { - throw new Error('Method not implemented') - } - - async setDefault(dataSource: tDataSource): Promise { - throw new Error('Method not implemented') - } - } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts index 3ab6424d79..d27b49da62 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts @@ -3,7 +3,7 @@ import { PatternDataSource } from '../pattern-data-source'; import { AppState } from '../../../../../react-services/app-state'; import { FilterHandler } from '../../../../../utils/filter-handler'; -import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../../../../../common/constants'; +import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER } from '../../../../../../common/constants'; const VULNERABILITIES_GROUP_KEY = 'rules.group'; const VULNERABILITIES_GROUP_VALUE = 'vulnerability-detector'; @@ -13,6 +13,7 @@ export class VulnerabilitiesDataSource extends PatternDataSource { constructor(id: string, title: string) { super(id, title); } + getClusterManagerFilters() { const filterHandler = new FilterHandler(); const isCluster = AppState.getClusterInfo().status == 'enabled'; @@ -25,11 +26,16 @@ export class VulnerabilitiesDataSource extends PatternDataSource { AppState.getClusterInfo().status ], ); - return managerFilter; + managerFilter.meta.index = this.id; + managerFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER; + managerFilter.$state = { + store: 'appState' + } + return [managerFilter] as tFilter[]; } getRuleGroupsFilter() { - return { + return [{ meta: { //removable: false, // not exists in the original type - removed to preserve the original type index: this.id, @@ -56,15 +62,15 @@ export class VulnerabilitiesDataSource extends PatternDataSource { $state: { store: 'appState', }, - }; + } as tFilter]; } getFixedFilters(): tFilter[] { return [ - ...[this.getClusterManagerFilters()], - this.getRuleGroupsFilter() + ...this.getClusterManagerFilters(), ] } + } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/search-params-builder.ts b/plugins/main/public/components/common/data-source/search-params-builder.ts index de06ac341c..040bd270c0 100644 --- a/plugins/main/public/components/common/data-source/search-params-builder.ts +++ b/plugins/main/public/components/common/data-source/search-params-builder.ts @@ -1,6 +1,6 @@ import { Filter } from "../../../../../../src/plugins/data/common"; -export type tFilter = Filter; +export type tFilter = {} & Filter; export type tSearchParams = { filters?: tFilter[]; From fc9dbcffe01d32939a0140f0ab1764c3d9fa07fe Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Thu, 21 Mar 2024 10:08:32 -0300 Subject: [PATCH 16/36] Using use data source hook on vuls inventory and dashboard tabs --- .../dashboards/inventory/inventory.tsx | 129 ++++++++---------- .../dashboards/overview/dashboard.tsx | 84 ++++-------- 2 files changed, 89 insertions(+), 124 deletions(-) diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index ac233254ed..defe27f085 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -31,7 +31,7 @@ import { LoadingSpinner } from '../../common/components/loading_spinner'; // common components/hooks import DocViewer from '../../../../common/doc-viewer/doc-viewer'; import useSearchBar from '../../../../common/search-bar/use-search-bar'; -import { useAppConfig } from '../../../../common/hooks'; +import { useAppConfig, useFilterManager } from '../../../../common/hooks'; import { useDataGrid } from '../../../../common/data-grid/use-data-grid'; import { useDocViewer } from '../../../../common/doc-viewer/use-doc-viewer'; import { withErrorBoundary } from '../../../../common/hocs'; @@ -45,55 +45,42 @@ import { import { compose } from 'redux'; import { withVulnerabilitiesStateDataSource } from '../../common/hocs/validate-vulnerabilities-states-index-pattern'; import { ModuleEnabledCheck } from '../../common/components/check-module-enabled'; -import { DataSourceFilterManagerVulnerabilitiesStates } from '../../../../../react-services/data-sources'; - -import { - DataSourceSelector, - VulnerabilitiesDataSourceRepository, - PatternDataSourceFactory, - DataSourceFilterManager, - AlertsDataSourceRepository} from '../../../../common/data-source'; -import { - VulnerabilitiesDataSourceFactory, -} from '../../../../common/data-source/pattern/vulnerabilities'; +import { VulnerabilitiesDataSourceRepository, VulnerabilitiesDataSourceFactory } from '../../../../common/data-source'; +import { useDataSource } from '../../../../common/data-source/hooks'; +import { FilterManager } from '../../../../../../../../src/plugins/data/public'; const InventoryVulsComponent = () => { const appConfig = useAppConfig(); const VULNERABILITIES_INDEX_PATTERN_ID = appConfig.data['vulnerabilities.pattern']; - const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, - //onMount: vulnerabilityIndexFiltersAdapter, - //onUpdate: onUpdateAdapter, - //onUnMount: restorePrevIndexFiltersAdapter, + const filterManager = useFilterManager().filterManager as FilterManager; + const { + dataSource, + filters: defaultFilters, + fetchFilters, + setFilters, + isLoading: isDataSourceLoading, + fetchData, + } = useDataSource({ + filters: filterManager.getFilters(), + factory: new VulnerabilitiesDataSourceFactory(), + repository: new VulnerabilitiesDataSourceRepository() }); useEffect(() => { - console.log('on mount'); - }, []) - - const initDataSource = async () => { - if(!searchBarProps.dateRangeFrom || !searchBarProps.dateRangeTo){ - return; + if(!isDataSourceLoading) { + console.log('default filters', defaultFilters); + console.log('filters manager filters', filterManager.getFilters()); + filterManager.setFilters(defaultFilters); } - const factory = new VulnerabilitiesDataSourceFactory(); - const repository = new VulnerabilitiesDataSourceRepository(); - - const dataSources = await factory.createAll(await repository.getAll()); - const dataSource = dataSources[0]; - - const filterManager = new DataSourceFilterManager(dataSource, searchBarProps.filters); - console.log('INVENTORY filters', filterManager.getFilters()); - console.log('INVENTORY fixed filters', filterManager.getFixedFilters()); - console.log('INVENTORY fetch filters', filterManager.getFetchFilters()); - } + }, [isDataSourceLoading]) + + const { searchBarProps } = useSearchBar({ + defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, + }); + const { isLoading, filters: searchBarFilters , query, indexPatterns } = searchBarProps; - const fetchFilters = DataSourceFilterManagerVulnerabilitiesStates.getFilters( - searchBarProps.filters, - VULNERABILITIES_INDEX_PATTERN_ID, - ); - const { isLoading, filters, query, indexPatterns } = searchBarProps; const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); const [inspectedHit, setInspectedHit] = useState(undefined); @@ -143,36 +130,6 @@ const InventoryVulsComponent = () => { indexPattern: indexPattern as IndexPattern, }); - useEffect(() => { - if (!isLoading) { - initDataSource(); - setIndexPattern(indexPatterns?.[0] as IndexPattern); - search({ - indexPattern: indexPatterns?.[0] as IndexPattern, - filters: fetchFilters, - query, - pagination, - sorting, - }) - .then(results => { - setResults(results); - setIsSearching(false); - }) - .catch(error => { - const searchError = ErrorFactory.create(HttpError, { - error, - message: 'Error fetching vulnerabilities', - }); - ErrorHandler.handleError(searchError); - setIsSearching(false); - }); - } - }, [ - JSON.stringify(searchBarProps), - JSON.stringify(pagination), - JSON.stringify(sorting), - JSON.stringify(fetchFilters), - ]); const onClickExportResults = async () => { const params = { @@ -200,6 +157,38 @@ const InventoryVulsComponent = () => { } }; + + useEffect(() => { + if (!isLoading) { + setIndexPattern(indexPatterns?.[0]); + setIsSearching(true); + fetchData( + { + query, + pagination, + sorting, + } + ) + .then(results => { + console.log('results', results); + setResults(results); + setIsSearching(false); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching vulnerabilities', + }); + ErrorHandler.handleError(searchError); + setIsSearching(false); + }); + } + }, [ + JSON.stringify(searchBarProps), + JSON.stringify(pagination), + JSON.stringify(sorting), + ]); + return ( <> @@ -210,7 +199,7 @@ const InventoryVulsComponent = () => { grow > <> - {isLoading ? ( + {isLoading || isDataSourceLoading ? ( ) : ( { const appConfig = useAppConfig(); const VULNERABILITIES_INDEX_PATTERN_ID = appConfig.data['vulnerabilities.pattern']; - const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, - /*onMount: vulnerabilityIndexFiltersAdapter, - onUpdate: onUpdateAdapter, - onUnMount: restorePrevIndexFiltersAdapter,*/ + const filterManager = useFilterManager().filterManager as FilterManager; + const { + dataSource, + filters: defaultFilters, + fetchFilters, + setFilters, + isLoading: isDataSourceLoading, + fetchData, + } = useDataSource({ + filters: filterManager.getFilters(), + factory: new VulnerabilitiesDataSourceFactory(), + repository: new VulnerabilitiesDataSourceRepository() }); - const initDataSource = async () => { - if(!searchBarProps.dateRangeFrom || !searchBarProps.dateRangeTo){ - return; + useEffect(() => { + if(!isDataSourceLoading) { + filterManager.setFilters(defaultFilters); } - const factory = new VulnerabilitiesDataSourceFactory(); - const repository = new VulnerabilitiesDataSourceRepository(); - - const dataSources = await factory.createAll(await repository.getAll()); - const dataSource = dataSources[0]; - - const filterManager = new DataSourceFilterManager(dataSource, searchBarProps.filters); - console.log('DASHBOARD filters', filterManager.getFilters()); - console.log('DASHBOARD fixed filters', filterManager.getFixedFilters()); - console.log('DASHBOARD fetch filters', filterManager.getFetchFilters()); - } - - - /* This function is responsible for updating the storage filters so that the - filters between dashboard and inventory added using visualizations call to actions. - Without this feature, filters added using visualizations call to actions are - not maintained between dashboard and inventory tabs */ - const handleFilterByVisualization = (newInput: DashboardContainerInput) => { - updateFiltersStorage(newInput.filters); - }; - - const fetchFilters = DataSourceFilterManagerVulnerabilitiesStates.getFilters( - searchBarProps.filters, - VULNERABILITIES_INDEX_PATTERN_ID, - ); + }, [isDataSourceLoading]) + const { searchBarProps } = useSearchBar({ + defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, + }); const { isLoading, query, indexPatterns } = searchBarProps; const [isSearching, setIsSearching] = useState(false); const [results, setResults] = useState({} as SearchResponse); - - useEffect(() => { if (!isLoading) { - initDataSource(); - search({ - indexPattern: indexPatterns?.[0] as IndexPattern, - filters: fetchFilters, - query, - }) + setIsSearching(true); + fetchData( + { + query, + } + ) .then(results => { + console.log('results', results); setResults(results); setIsSearching(false); }) @@ -168,7 +147,6 @@ const DashboardVulsComponent: React.FC = () => { }, hidePanelTitles: true, }} - onInputUpdated={handleFilterByVisualization} /> { }, hidePanelTitles: true, }} - onInputUpdated={handleFilterByVisualization} /> { }, hidePanelTitles: false, }} - onInputUpdated={handleFilterByVisualization} /> ) : null} From 3cba1d1e195fb539633be0d348e66e178c8f85db Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Thu, 21 Mar 2024 16:23:12 -0300 Subject: [PATCH 17/36] Applied use data source --- .../data-source/data-source-filter-manager.ts | 37 +------- .../data-source/data-source-selector.ts | 3 +- .../common/data-source/data-source.ts | 1 + .../data-source/hooks/use-data-source.ts | 8 +- .../pattern/pattern-data-source-factory.ts | 17 +++- .../pattern/pattern-data-source.ts | 19 ++-- .../vulnerabilities-data-source-repository.ts | 8 ++ .../vulnerabilities-data-source.ts | 4 +- .../common/modules/modules-defaults.tsx | 24 ++--- .../dashboards/inventory/inventory.tsx | 89 +++++++++---------- .../dashboards/overview/dashboard.tsx | 80 ++++++++--------- 11 files changed, 136 insertions(+), 154 deletions(-) diff --git a/plugins/main/public/components/common/data-source/data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/data-source-filter-manager.ts index 3d2188412b..29bd8b733c 100644 --- a/plugins/main/public/components/common/data-source/data-source-filter-manager.ts +++ b/plugins/main/public/components/common/data-source/data-source-filter-manager.ts @@ -2,37 +2,7 @@ import { tDataSource } from "./data-source"; import { tFilter, tSearchParams } from "./search-params-builder"; import store from '../../../redux/store'; import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../react-services/data-sources/vulnerabilities-states'; -import { DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT } from "../../../../common/constants"; - - -type FiltersStateRepository = { - getAllowAgents: () => string[]; - getExcludeManager: () => boolean; - getPinnedAgent: () => object; -} - - -/** - * Use case - * - * - The data source filter manager receives the filters from the filter manager - * - When the filters received only store the filters that was added by the user. - * Not have the property isImplicit ($state.isImplicit) defined or not have the meta.controlledBy property defined - * Also, when the meta.index is not the same as the dataSource.id - * - * - Then add the fixedFilters and concatenate with the user filters - */ - - -/** - * Get the filters from the filter manager - * - * - filter the filters that was added by the user - * - check if agent is pinned (check in session storage or redux store if is possible wz-shared-selected-agent) - * - * Add filters only necessary to fetch the data from the data source - * - - */ +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT } from "../../../../common/constants"; export type tDataSourceFilterManager = { fetch: () => Promise; @@ -69,7 +39,7 @@ export class DataSourceFilterManager implements tDataSourceFilterManager { } setFilters(filters: tFilter[]) { - this.filters = this.filterUserFilters(filters); + this.filters = this.filterUserFilters(filters) || []; } /** @@ -167,6 +137,7 @@ export class DataSourceFilterManager implements tDataSourceFilterManager { if (!agentId) return []; return [{ meta: { + removable: false, // used to hide the close icon in the filter alias: null, disabled: false, key: 'agent.id', @@ -174,7 +145,7 @@ export class DataSourceFilterManager implements tDataSourceFilterManager { params: { query: agentId }, type: 'phrase', index: this.dataSource.id, - controlledBy: DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT }, query: { match: { 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 index d35c0c87d0..26f5596007 100644 --- a/plugins/main/public/components/common/data-source/data-source-selector.ts +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -127,7 +127,8 @@ export class DataSourceSelector implements tDataSourceSelector { await this.selectDataSource(validDataSource.id); return validDataSource; } - return defaultDataSource; + + return await this.getDataSource(defaultDataSource.id); } catch (error) { const validateDataSource = await this.getFirstValidDataSource(); await this.selectDataSource(validateDataSource.id); diff --git a/plugins/main/public/components/common/data-source/data-source.ts b/plugins/main/public/components/common/data-source/data-source.ts index b9a2486722..4b1a02cbf9 100644 --- a/plugins/main/public/components/common/data-source/data-source.ts +++ b/plugins/main/public/components/common/data-source/data-source.ts @@ -8,5 +8,6 @@ export type tDataSource = { setFilters: (filters: tFilter[]) => Promise | void; getFields: () => Promise | any[]; getFixedFilters: () => tFilter[]; + getFetchFilters: () => tFilter[]; fetch: (params: tSearchParams) => Promise; } \ No newline at end of file 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 26260de648..99a62e2681 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 @@ -14,9 +14,9 @@ type tUseDataSourceProps = { repository: DataSourceRepository; } -type tUseDataSourceLoadedReturns = { +type tUseDataSourceLoadedReturns = { isLoading: boolean; - dataSource: tDataSource; + dataSource: K; filters: tFilter[]; fetchFilters: tFilter[]; fixedFilters: tFilter[]; @@ -34,7 +34,7 @@ type tUseDataSourceNotLoadedReturns = { setFilters: (filters: tFilter[]) => void; } -export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { +export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { const { filters: defaultFilters = [], factory, repository } = props; if (!factory || !repository) { @@ -70,7 +70,7 @@ export function useDataSource(props: tUseDataSourceProps): tUseDataS const init = async () => { setIsLoading(true); const selector = new DataSourceSelector(repository, factory); - const dataSource = await selector.getFirstValidDataSource(); + const dataSource = await selector.getSelectedDataSource(); if (!dataSource) { throw new Error('No valid data source found'); } 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 index f8ad86c766..e4a55483a0 100644 --- 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 @@ -1,16 +1,25 @@ import { tDataSourceFactory, PatternDataSource, tDataSource, tParsedIndexPattern } from '../'; export class PatternDataSourceFactory implements tDataSourceFactory{ - create(item: tParsedIndexPattern): PatternDataSource { + async create(item: tParsedIndexPattern): Promise { if(!item){ throw new Error('Cannot create data source from null or undefined'); }; - return new PatternDataSource(item.id, item.title); + const dataSource = new PatternDataSource(item.id, item.attributes.title); + await dataSource.init(); + return dataSource; } - createAll(items: tParsedIndexPattern[]): PatternDataSource[] { + async createAll(items: tParsedIndexPattern[]): Promise { if(!items){ throw new Error('Cannot create data source from null or undefined'); }; - return items.map(item => this.create(item)); + + const dataSources: PatternDataSource[] = []; + for (const item of items) { + const dataSource = new PatternDataSource(item.id, item.attributes.title); + await dataSource.init(); + dataSources.push(dataSource); + } + return dataSources; } } \ No newline at end of file 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 42a550905a..28f6f1c672 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 @@ -1,6 +1,6 @@ import { tDataSource, tSearchParams, tFilter } from "../index"; import { getDataPlugin } from '../../../../kibana-services'; -import { Filter, IndexPatternsContract, OpenSearchQuerySortValue } from "../../../../../../../src/plugins/data/public"; +import { Filter, IndexPatternsContract, IndexPattern } from "../../../../../../../src/plugins/data/public"; import { search } from '../../search-bar/search-bar-service'; export class PatternDataSource implements tDataSource { @@ -8,18 +8,21 @@ export class PatternDataSource implements tDataSource { title: string; fields: any[]; patternService: IndexPatternsContract; + indexPattern: IndexPattern; defaultFixedFilters: tFilter[]; constructor(id: string, title: string) { this.id = id; this.title = title; } + setFilters: (filters: Filter[]) => void | Promise; /** * Initialize the data source */ async init(){ this.patternService = await getDataPlugin().indexPatterns; + this.indexPattern = await this.patternService.get(this.id); } getFilters(){ @@ -30,11 +33,15 @@ export class PatternDataSource implements tDataSource { return this.fields; } - getFixedFilters():Filter[]{ + getFixedFilters(): tFilter[]{ // return all filters return []; } + getFetchFilters(): tFilter[]{ + return []; + } + async select(){ try { const pattern = await this.patternService.get(this.id); @@ -61,8 +68,7 @@ export class PatternDataSource implements tDataSource { } try { - const results = await search( - { + const results = await search({ indexPattern, filters: defaultFilters, query, @@ -79,9 +85,4 @@ export class PatternDataSource implements tDataSource { } - - getFetchFilters(){ - return []; - } - } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts index 6fc9e9e4a8..387311e001 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts @@ -33,4 +33,12 @@ export class VulnerabilitiesDataSourceRepository extends PatternDataSourceReposi return false; } + getDefault(){ + return null; + } + + setDefault(dataSource: tDataSource){ + return null; + } + } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts index d27b49da62..ea64cabdec 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts @@ -37,7 +37,7 @@ export class VulnerabilitiesDataSource extends PatternDataSource { getRuleGroupsFilter() { return [{ meta: { - //removable: false, // not exists in the original type - removed to preserve the original type + removable: false, // used to hide the close icon in the filter index: this.id, negate: false, disabled: false, @@ -70,7 +70,5 @@ export class VulnerabilitiesDataSource extends PatternDataSource { ...this.getClusterManagerFilters(), ] } - - } \ No newline at end of file diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index 80de04594c..a81a67fea1 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.tsx +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -54,13 +54,13 @@ const DashboardTab = { component: Dashboard, }; -const renderDiscoverTab = (columns, indexPattern) => { +const renderDiscoverTab = (indexName = DEFAULT_INDEX_PATTERN, columns) => { return { id: 'events', name: 'Events', buttons: [ButtonModuleExploreAgent], component: () => ( - + ), }; }; @@ -73,7 +73,7 @@ const RegulatoryComplianceTabs = columns => [ buttons: [ButtonModuleExploreAgent], component: ComplianceTable, }, - renderDiscoverTab(columns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN,columns), ]; export const ModulesDefaults = { @@ -81,7 +81,7 @@ export const ModulesDefaults = { init: 'events', tabs: [ DashboardTab, - renderDiscoverTab(threatHuntingColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN,threatHuntingColumns), ], availableFor: ['manager', 'agent'], }, @@ -100,7 +100,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: InventoryFim, }, - renderDiscoverTab(fileIntegrityMonitoringColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN,fileIntegrityMonitoringColumns), ], availableFor: ['manager', 'agent'], }, @@ -108,7 +108,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(amazonWebServicesColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN,amazonWebServicesColumns), ], availableFor: ['manager', 'agent'], }, @@ -116,7 +116,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(googleCloudColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN,googleCloudColumns), ], availableFor: ['manager', 'agent'], }, @@ -144,7 +144,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: MainSca, }, - renderDiscoverTab(configurationAssessmentColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN,configurationAssessmentColumns), ], buttons: ['settings'], availableFor: ['manager', 'agent'], @@ -165,7 +165,7 @@ export const ModulesDefaults = { component: withModuleNotForAgent(OfficePanel), }, { - ...renderDiscoverTab(office365Columns), + ...renderDiscoverTab(DEFAULT_INDEX_PATTERN,office365Columns), component: withModuleNotForAgent(() => ( ( { const appConfig = useAppConfig(); const VULNERABILITIES_INDEX_PATTERN_ID = appConfig.data['vulnerabilities.pattern']; - const filterManager = useFilterManager().filterManager as FilterManager; + const filterManager = useFilterManager().filterManager as FilterManager; const { dataSource, filters: defaultFilters, @@ -62,24 +62,22 @@ const InventoryVulsComponent = () => { setFilters, isLoading: isDataSourceLoading, fetchData, - } = useDataSource({ + } = useDataSource({ filters: filterManager.getFilters(), factory: new VulnerabilitiesDataSourceFactory(), repository: new VulnerabilitiesDataSourceRepository() }); useEffect(() => { - if(!isDataSourceLoading) { - console.log('default filters', defaultFilters); - console.log('filters manager filters', filterManager.getFilters()); + if (!isDataSourceLoading) { filterManager.setFilters(defaultFilters); } }, [isDataSourceLoading]) - + const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, }); - const { isLoading, filters: searchBarFilters , query, indexPatterns } = searchBarProps; + const { isLoading, filters: searchBarFilters, query } = searchBarProps; const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); @@ -133,7 +131,7 @@ const InventoryVulsComponent = () => { const onClickExportResults = async () => { const params = { - indexPattern: indexPatterns?.[0] as IndexPattern, + indexPattern: indexPattern as IndexPattern, filters: fetchFilters, query, fields: columnVisibility.visibleColumns, @@ -157,37 +155,37 @@ const InventoryVulsComponent = () => { } }; - useEffect(() => { if (!isLoading) { - setIndexPattern(indexPatterns?.[0]); - setIsSearching(true); - fetchData( - { - query, - pagination, - sorting, - } - ) - .then(results => { - console.log('results', results); - setResults(results); - setIsSearching(false); - }) - .catch(error => { - const searchError = ErrorFactory.create(HttpError, { - error, - message: 'Error fetching vulnerabilities', - }); - ErrorHandler.handleError(searchError); - setIsSearching(false); - }); + setFilters(searchBarFilters as tFilter[]); } }, [ - JSON.stringify(searchBarProps), + JSON.stringify(searchBarFilters) + ]); + + useEffect(() => { + if (isLoading || isDataSourceLoading) { + return; + } + setIndexPattern(dataSource?.indexPattern); + fetchData({ query, pagination, sorting }) + .then(results => { + console.log('results', results); + setResults(results); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching vulnerabilities', + }); + ErrorHandler.handleError(searchError); + }); + }, [ + JSON.stringify(fetchFilters), + JSON.stringify(query), JSON.stringify(pagination), JSON.stringify(sorting), - ]); + ]) return ( @@ -210,11 +208,10 @@ const InventoryVulsComponent = () => { showQueryBar={true} /> )} - {isSearching ? : null} - {!isLoading && !isSearching && results?.hits?.total === 0 ? ( + {!isLoading && results?.hits?.total === 0 ? ( ) : null} - {!isLoading && !isSearching && results?.hits?.total > 0 ? ( + {!isLoading && results?.hits?.total > 0 ? ( { {}} + onResetQuery={() => { }} tooltip={ results?.hits?.total && - results?.hits?.total > MAX_ENTRIES_PER_QUERY + results?.hits?.total > MAX_ENTRIES_PER_QUERY ? { - ariaLabel: 'Warning', - content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas( - MAX_ENTRIES_PER_QUERY, - )} hits.`, - iconType: 'alert', - position: 'top', - } + ariaLabel: 'Warning', + content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas( + MAX_ENTRIES_PER_QUERY, + )} hits.`, + iconType: 'alert', + position: 'top', + } : undefined } /> diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index a26afd192a..bcfac7e0c3 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { SearchResponse } from '../../../../../../../../src/core/server'; import { getPlugins } from '../../../../../kibana-services'; import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; @@ -12,14 +12,6 @@ import { useAppConfig, useFilterManager } from '../../../../common/hooks'; import { withErrorBoundary } from '../../../../common/hocs'; import { DiscoverNoResults } from '../../common/components/no_results'; import { LoadingSpinner } from '../../common/components/loading_spinner'; -import { - vulnerabilityIndexFiltersAdapter, - restorePrevIndexFiltersAdapter, - onUpdateAdapter, - updateFiltersStorage, -} from '../../common/vulnerability_detector_adapters'; -import { search } from '../../../../common/search-bar/search-bar-service'; -import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { ErrorFactory, ErrorHandler, @@ -28,17 +20,19 @@ import { import { compose } from 'redux'; import { withVulnerabilitiesStateDataSource } from '../../common/hocs/validate-vulnerabilities-states-index-pattern'; import { ModuleEnabledCheck } from '../../common/components/check-module-enabled'; -import { DataSourceFilterManagerVulnerabilitiesStates } from '../../../../../react-services/data-sources'; -import { DashboardContainerInput } from '../../../../../../../../src/plugins/dashboard/public'; -import { VulnerabilitiesDataSourceRepository, VulnerabilitiesDataSourceFactory } from '../../../../common/data-source'; +import { + VulnerabilitiesDataSourceRepository, + VulnerabilitiesDataSourceFactory, + PatternDataSource, + tFilter, + tParsedIndexPattern +} from '../../../../common/data-source'; import { useDataSource } from '../../../../common/data-source/hooks'; import { FilterManager } from '../../../../../../../../src/plugins/data/public'; const plugins = getPlugins(); - const SearchBar = getPlugins().data.ui.SearchBar; - const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; /* The vulnerabilities dashboard is made up of 3 dashboards because the filters need @@ -49,7 +43,7 @@ const DashboardVulsComponent: React.FC = () => { const appConfig = useAppConfig(); const VULNERABILITIES_INDEX_PATTERN_ID = appConfig.data['vulnerabilities.pattern']; - const filterManager = useFilterManager().filterManager as FilterManager; + const filterManager = useFilterManager().filterManager as FilterManager; const { dataSource, filters: defaultFilters, @@ -57,14 +51,14 @@ const DashboardVulsComponent: React.FC = () => { setFilters, isLoading: isDataSourceLoading, fetchData, - } = useDataSource({ + } = useDataSource({ filters: filterManager.getFilters(), factory: new VulnerabilitiesDataSourceFactory(), repository: new VulnerabilitiesDataSourceRepository() }); useEffect(() => { - if(!isDataSourceLoading) { + if (!isDataSourceLoading) { filterManager.setFilters(defaultFilters); } }, [isDataSourceLoading]) @@ -72,34 +66,37 @@ const DashboardVulsComponent: React.FC = () => { const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, }); - const { isLoading, query, indexPatterns } = searchBarProps; - const [isSearching, setIsSearching] = useState(false); + const { isLoading, query, filters: searchBarFilters } = searchBarProps; const [results, setResults] = useState({} as SearchResponse); useEffect(() => { if (!isLoading) { - setIsSearching(true); - fetchData( - { - query, - } - ) - .then(results => { - console.log('results', results); - setResults(results); - setIsSearching(false); - }) - .catch(error => { - const searchError = ErrorFactory.create(HttpError, { - error, - message: 'Error fetching vulnerabilities', - }); - ErrorHandler.handleError(searchError); - setIsSearching(false); - }); + setFilters(searchBarFilters as tFilter[]); } - }, [JSON.stringify(searchBarProps), JSON.stringify(fetchFilters)]); + }, [ + JSON.stringify(searchBarFilters) + ]); + + useEffect(() => { + if (isLoading || isDataSourceLoading) { + return; + } + fetchData({ query }).then(results => { + console.log('results', results); + setResults(results); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching vulnerabilities', + }); + ErrorHandler.handleError(searchError); + }); + }, [ + JSON.stringify(fetchFilters), + JSON.stringify(query) + ]) return ( <> @@ -116,11 +113,10 @@ const DashboardVulsComponent: React.FC = () => { showQueryBar={true} /> ) : null} - {isSearching ? : null} - {!isLoading && !isSearching && results?.hits?.total === 0 ? ( + {!isLoading && results?.hits?.total === 0 ? ( ) : null} - {!isLoading && !isSearching && results?.hits?.total > 0 ? ( + {!isLoading && results?.hits?.total > 0 ? (
Date: Thu, 21 Mar 2024 16:23:32 -0300 Subject: [PATCH 18/36] Applying in discover --- .../common/wazuh-discover/wz-discover.tsx | 101 +++++++++++------- 1 file changed, 65 insertions(+), 36 deletions(-) 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 3f8934d464..eb99a4188d 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -43,6 +43,15 @@ const DashboardByRenderer = getPlugins().dashboard.DashboardContainerByValueRenderer; import './discover.scss'; import { withErrorBoundary } from '../hocs'; +import { useFilterManager } from '../hooks'; +import { FilterManager } from '../../../../../../src/plugins/data/public'; +import { + useDataSource, + tParsedIndexPattern, + PatternDataSource, + AlertsDataSourceRepository, + PatternDataSourceFactory +} from '../data-source'; export const MAX_ENTRIES_PER_QUERY = 10000; @@ -64,6 +73,26 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav(); const [indexPatternTitle, setIndexPatternTitle] = useState(''); + const filterManager = useFilterManager().filterManager as FilterManager; + const { + dataSource, + filters: defaultFilters, + fetchFilters, + setFilters, + isLoading: isDataSourceLoading, + fetchData, + } = useDataSource({ + filters: filterManager.getFilters(), + factory: new PatternDataSourceFactory(), + repository: new AlertsDataSourceRepository() + }); + + useEffect(() => { + if (!isDataSourceLoading) { + filterManager.setFilters(defaultFilters); + } + }, [isDataSourceLoading]) + const onClickInspectDoc = useMemo( () => (index: number) => { const rowClicked = results.hits.hits[index]; @@ -92,9 +121,8 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { }); const { isLoading, - filters, + filters: searchBarFilters, query, - indexPatterns, dateRangeFrom, dateRangeTo, } = searchBarProps; @@ -119,47 +147,48 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { indexPattern: indexPattern as IndexPattern, }); - const currentIndexPattern = useIndexPattern(); useEffect(() => { - if (currentIndexPattern) { - setIndexPattern(currentIndexPattern); - setIndexPatternTitle(currentIndexPattern.title); + if (!isLoading) { + setFilters(searchBarFilters as tFilter[]); } - }, [currentIndexPattern]) + }, [ + JSON.stringify(searchBarFilters) + ]); useEffect(() => { - if (!isLoading && indexPattern) { - setIsSearching(true); - search({ - indexPattern: indexPattern as IndexPattern, - filters, - query, - pagination, - sorting, - dateRange: { - from: dateRangeFrom, - to: dateRangeTo, - }, + if (isLoading || isDataSourceLoading) { + return; + } + setIndexPattern(dataSource?.indexPattern); + fetchData({ query, pagination, sorting }) + .then(results => { + console.log('results', results); + setResults(results); }) - .then(results => { - setResults(results); - setIsSearching(false); - }) - .catch(error => { - const searchError = ErrorFactory.create(HttpError, { - error, - message: 'Error fetching data', - }); - ErrorHandler.handleError(searchError); - setIsSearching(false); + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching vulnerabilities', }); - } + ErrorHandler.handleError(searchError); + }); }, [ - JSON.stringify(searchBarProps), + JSON.stringify(fetchFilters), + JSON.stringify(query), JSON.stringify(pagination), JSON.stringify(sorting), - ]); + ]) + + + const currentIndexPattern = useIndexPattern(); + + useEffect(() => { + if (currentIndexPattern) { + setIndexPattern(currentIndexPattern); + setIndexPatternTitle(currentIndexPattern.title); + } + }, [currentIndexPattern]) const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName @@ -168,7 +197,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const onClickExportResults = async () => { const params = { indexPattern: indexPattern as IndexPattern, - filters, + fetchFilters, query, fields: columnVisibility.visibleColumns, pagination: { @@ -200,7 +229,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { grow > <> - {isLoading ? ( + {isLoading || isDataSourceLoading ? ( ) : ( { Date: Mon, 25 Mar 2024 10:18:57 -0300 Subject: [PATCH 19/36] Update data source types to simplify --- .../common/data-source/data-source-factory.ts | 12 +- .../data-source/data-source-repository.ts | 4 +- .../data-source/data-source-selector.ts | 141 +----------------- .../common/data-source/data-source.ts | 1 + .../components/common/data-source/index.ts | 2 +- .../pattern/pattern-data-source-repository.ts | 8 +- .../pattern/pattern-data-source-selector.ts | 122 +++++++++++++++ .../pattern/pattern-data-source.ts | 38 ++++- 8 files changed, 174 insertions(+), 154 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts 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 index 8039d61b8c..7c55a6e444 100644 --- a/plugins/main/public/components/common/data-source/data-source-factory.ts +++ b/plugins/main/public/components/common/data-source/data-source-factory.ts @@ -1,4 +1,10 @@ -export type tDataSourceFactory = { - create(item: T): Promise | K; - createAll(items: T[]): Promise | K[]; +import { tDataSource, tParsedIndexPattern } from './index'; + +export interface IDataSourceFactoryConstructor { + new (id: T['id'], title: T['title']): T; +} + +export type tDataSourceFactory = { + create: (DataSourceType: IDataSourceFactoryConstructor, data: T) => Promise; + createAll: (DataSourceType: IDataSourceFactoryConstructor, data: T[]) => Promise; } \ 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 index 143dd86fec..c20e1a465e 100644 --- a/plugins/main/public/components/common/data-source/data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/data-source-repository.ts @@ -1,6 +1,6 @@ -export interface DataSourceRepository { +export type tDataSourceRepository = { get(id: string): Promise; getAll(): Promise; - setDefault(dataSource: K): Promise | void; + setDefault(dataSourceData: T): Promise | void; getDefault(): Promise | T | null; } \ 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 index 26f5596007..fa50b21438 100644 --- a/plugins/main/public/components/common/data-source/data-source-selector.ts +++ b/plugins/main/public/components/common/data-source/data-source-selector.ts @@ -1,139 +1,10 @@ -import { DataSourceRepository } from './data-source-repository'; -import { tDataSourceFactory } from './data-source-factory'; -import { tDataSource } from "./data-source"; +import { tDataSource } from "./index"; -export type tDataSourceSelector = { +export type tDataSourceSelector = { existsDataSource: (id: string) => Promise; - getFirstValidDataSource: () => Promise; - getAllDataSources: () => Promise; - getDataSource: (id: string) => Promise; - getSelectedDataSource: () => 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 await this.getDataSource(defaultDataSource.id); - } 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 index 4b1a02cbf9..b7d7e32c5e 100644 --- a/plugins/main/public/components/common/data-source/data-source.ts +++ b/plugins/main/public/components/common/data-source/data-source.ts @@ -10,4 +10,5 @@ export type tDataSource = { getFixedFilters: () => tFilter[]; getFetchFilters: () => tFilter[]; fetch: (params: tSearchParams) => Promise; + toJSON(): object; } \ 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 index 7014d2332b..c4c7def5ff 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -3,7 +3,7 @@ export * from './data-source-repository'; export * from './data-source-factory'; export * from './data-source-selector'; export * from './data-source-filter-manager'; -export * from './pattern'; export * from './search-params-builder'; +export * from './pattern'; export * from './hooks'; export * from './pattern'; \ No newline at end of file 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 index 97c10cafdf..7c9e323ba7 100644 --- 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 @@ -1,6 +1,4 @@ -import { tDataSource } from "../data-source"; -import { DataSourceRepository } from "../data-source-repository"; -import { PatternDataSource } from "./pattern-data-source"; +import { tDataSourceRepository } from "../data-source-repository"; import { GenericRequest } from '../../../../react-services/generic-request'; import { AppState } from '../../../../react-services'; @@ -42,7 +40,7 @@ export type tParsedIndexPattern = { _fields: any[]; } -export class PatternDataSourceRepository implements DataSourceRepository{ +export class PatternDataSourceRepository implements tDataSourceRepository{ async get(id: string): Promise { try { const savedObjectResponse = await GenericRequest.request( @@ -89,7 +87,7 @@ export class PatternDataSourceRepository implements DataSourceRepository { + setDefault(dataSource: tParsedIndexPattern): Promise { if(!dataSource){ throw new Error('Index pattern is required'); } diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts new file mode 100644 index 0000000000..1426141f1e --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts @@ -0,0 +1,122 @@ +import { tDataSourceSelector, tDataSourceRepository, tParsedIndexPattern } from '../index'; +import { PatternDataSource } from './index'; + +export class PatternDataSourceSelector implements tDataSourceSelector { + // add a map to store locally the data sources + private dataSources: Map = new Map(); + + constructor(dataSourcesList: PatternDataSource[], private repository: tDataSourceRepository) { + if (!repository) { + throw new Error('Data source repository is required'); + } + if(!dataSourcesList || dataSourcesList?.length === 0) { + throw new Error('Data sources list is required'); + } + + this.dataSources = new Map(dataSourcesList.map(dataSource => [dataSource.id, dataSource])); + } + + /** + * 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 { + if (this.dataSources.size === 0) { + throw new Error('No data sources found'); + } + let index = 0; + do { + const dataSource = Array.from(this.dataSources.values())[index]; + if (await this.existsDataSource(dataSource.id)) { + return dataSource; + } + index++; + } while (index < this.dataSources.size); + 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) { + throw new Error('No data sources found'); + } + 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 { + 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 await this.getDataSource(defaultDataSource.id); + } 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/pattern/pattern-data-source.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts index 28f6f1c672..878901c5f3 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 @@ -1,9 +1,9 @@ -import { tDataSource, tSearchParams, tFilter } from "../index"; +import { tDataSource, tSearchParams, tFilter, tParsedIndexPattern } from "../index"; import { getDataPlugin } from '../../../../kibana-services'; import { Filter, IndexPatternsContract, IndexPattern } from "../../../../../../../src/plugins/data/public"; import { search } from '../../search-bar/search-bar-service'; -export class PatternDataSource implements tDataSource { +export class PatternDataSource implements tDataSource{ id: string; title: string; fields: any[]; @@ -15,8 +15,6 @@ export class PatternDataSource implements tDataSource { this.id = id; this.title = title; } - setFilters: (filters: Filter[]) => void | Promise; - /** * Initialize the data source */ @@ -25,14 +23,17 @@ export class PatternDataSource implements tDataSource { this.indexPattern = await this.patternService.get(this.id); } - getFilters(){ - return []; - } - getFields(){ return this.fields; } + setFilters: (filters: Filter[]) => void | Promise; + + + getFilters(){ + return []; + } + getFixedFilters(): tFilter[]{ // return all filters return []; @@ -85,4 +86,25 @@ export class PatternDataSource implements tDataSource { } + toJSON(): tParsedIndexPattern { + return { + attributes: { + fields: JSON.stringify(this.fields), + title: this.title + }, + title: this.title, + id: this.id, + migrationVersion: { + 'index-pattern': '7.10.0' + }, + namespace: [], + references: [], + score: 0, + type: 'index-pattern', + updated_at: new Date().toISOString(), + version: 'WzPatternDataSource', + _fields: this.fields + } + } + } \ No newline at end of file From 89a42740be46970af68a19e663b3d118db250243 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Mon, 25 Mar 2024 10:20:20 -0300 Subject: [PATCH 20/36] Update data source selector component --- .../common/data-source/components/index.ts | 1 + .../wz-data-source-selector.tsx | 96 +++++++++++++++++++ .../pattern/pattern-data-source-factory.ts | 27 ++---- .../wz-data-source-selector.tsx | 70 -------------- .../main/public/components/wz-menu/wz-menu.js | 5 +- 5 files changed, 106 insertions(+), 93 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/components/index.ts create mode 100644 plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx delete mode 100644 plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx diff --git a/plugins/main/public/components/common/data-source/components/index.ts b/plugins/main/public/components/common/data-source/components/index.ts new file mode 100644 index 0000000000..2557ca05a1 --- /dev/null +++ b/plugins/main/public/components/common/data-source/components/index.ts @@ -0,0 +1 @@ +export WzDataSourceSelector from './wz-data-source-selector/wz-data-source-selector'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx b/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx new file mode 100644 index 0000000000..fe044faa27 --- /dev/null +++ b/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { useEffect, useState } from 'react'; +import { + EuiFormRow, + EuiSelect, +} from '@elastic/eui'; +import { + tDataSource, + tDataSourceFactory, + tDataSourceSelector, + PatternDataSourceFactory, + PatternDataSource, + AlertsDataSourceRepository, + tDataSourceRepository, + tParsedIndexPattern +} from '../../index'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../../../react-services/error-management'; +import { + PatternDataSourceSelector, +} from '../../pattern/pattern-data-source-selector'; + + + +type tWzDataSourceSelector = { + name: 'string'; + onChange?: (dataSource: T) => void; + dataSourceSelector?: tDataSourceSelector; +} + +const WzDataSourceSelector = (props: tWzDataSourceSelector) => { + const { onChange, name = 'data source', dataSourceSelector: defaultDataSourceSelector } = props; + const [dataSourceList, setDataSourceList] = useState([]); + const [selectedPattern, setSelectedPattern] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [dataSourceSelector, setDataSourceSelector] = useState | undefined>(defaultDataSourceSelector); + + useEffect(() => { + init(); + }, []) + + async function init() { + let selector; + let dataSources; + setIsLoading(true); + if(!dataSourceSelector){ + const factory = new PatternDataSourceFactory(); + const repository = new AlertsDataSourceRepository(); + dataSources = await factory.createAll(PatternDataSource,await repository.getAll()); + selector = new PatternDataSourceSelector(dataSources, repository); + setDataSourceSelector(selector); + } + const defaultIndexPattern = await selector.getSelectedDataSource(); + dataSources = await selector.getAllDataSources(); + setSelectedPattern(defaultIndexPattern); + setDataSourceList(dataSources); + setIsLoading(false); + } + + async function selectDataSource(e) { + const dataSourceId = e.target.value; + if(!dataSourceSelector){ + throw new Error('Data source selector not initialized'); + }; + try { + await dataSourceSelector.selectDataSource(dataSourceId); + const selected = await dataSourceSelector.getSelectedDataSource(); + setSelectedPattern(await dataSourceSelector.getDataSource(dataSourceId)); + onChange && onChange(selected.toJSON()); + } 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/data-source/pattern/pattern-data-source-factory.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-factory.ts index e4a55483a0..1b64ecfc57 100644 --- 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 @@ -1,25 +1,14 @@ import { tDataSourceFactory, PatternDataSource, tDataSource, tParsedIndexPattern } from '../'; export class PatternDataSourceFactory implements tDataSourceFactory{ - - async create(item: tParsedIndexPattern): Promise { - if(!item){ - throw new Error('Cannot create data source from null or undefined'); - }; - const dataSource = new PatternDataSource(item.id, item.attributes.title); + async create(DataSourceType: new (id: string, title: string) => PatternDataSource, data: tParsedIndexPattern): Promise { + const dataSource = new DataSourceType(data.id, data.title); await dataSource.init(); return dataSource; } - async createAll(items: tParsedIndexPattern[]): Promise { - if(!items){ - throw new Error('Cannot create data source from null or undefined'); - }; - - const dataSources: PatternDataSource[] = []; - for (const item of items) { - const dataSource = new PatternDataSource(item.id, item.attributes.title); - await dataSource.init(); - dataSources.push(dataSource); - } - return dataSources; - } + async createAll(DataSourceType: new (id: string, title: string) => PatternDataSource, data: tParsedIndexPattern[]): Promise { + return Promise.all(data.map(async (d) => { + return this.create(DataSourceType, d); + })); + } + } \ 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 deleted file mode 100644 index b4271e4fca..0000000000 --- a/plugins/main/public/components/common/data-source/wz-data-source-selector/wz-data-source-selector.tsx +++ /dev/null @@ -1,70 +0,0 @@ -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/wz-menu/wz-menu.js b/plugins/main/public/components/wz-menu/wz-menu.js index 27ca83669b..4932c49916 100644 --- a/plugins/main/public/components/wz-menu/wz-menu.js +++ b/plugins/main/public/components/wz-menu/wz-menu.js @@ -49,8 +49,7 @@ import { AlertsDataSourceRepository, PatternDataSourceFactory } from '../common/data-source'; - -import WzDataSourceSelector from '../common/data-source/wz-data-source-selector/wz-data-source-selector'; +import WzDataSourceSelector from '../common/data-source/components/wz-data-source-selector/wz-data-source-selector'; const sections = { overview: 'overview', @@ -80,7 +79,6 @@ export const WzMenu = withWindowSize( currentSelectedPattern: '', isManagementPopoverOpen: false, isOverviewPopoverOpen: false, - dataSourceSelector: new DataSourceSelector(new AlertsDataSourceRepository(), new PatternDataSourceFactory()) }; this.store = store; this.genericReq = GenericRequest; @@ -529,7 +527,6 @@ export const WzMenu = withWindowSize(
From 22d04fe98f9eb10d3b351396c516b345be9bc203 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Mon, 25 Mar 2024 15:20:53 -0300 Subject: [PATCH 21/36] Update and clean types --- .../common/data-source/data-source-factory.ts | 10 - .../data-source/data-source-filter-manager.ts | 182 ------------------ .../data-source/data-source-repository.ts | 6 - .../data-source/data-source-selector.ts | 10 - .../common/data-source/data-source.ts | 14 -- .../components/common/data-source/index.ts | 9 +- .../common/data-source/pattern/index.ts | 2 + ...attern-data-source-filter-manager.test.ts} | 0 .../pattern-data-source-filter-manager.ts | 100 ++++++++++ .../pattern-data-source-selector.test.ts} | 0 .../pattern/pattern-data-source.ts | 77 +++++++- .../vulnerabilities-data-source.ts | 1 + .../data-source/search-params-builder.ts | 65 ------- .../components/common/data-source/types.ts | 75 ++++++++ 14 files changed, 252 insertions(+), 299 deletions(-) delete mode 100644 plugins/main/public/components/common/data-source/data-source-factory.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source-filter-manager.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source-repository.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source-selector.ts delete mode 100644 plugins/main/public/components/common/data-source/data-source.ts rename plugins/main/public/components/common/data-source/{data-source-filter-manager.test.ts => pattern/pattern-data-source-filter-manager.test.ts} (100%) create mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts rename plugins/main/public/components/common/data-source/{data-source-selector.test.ts => pattern/pattern-data-source-selector.test.ts} (100%) delete mode 100644 plugins/main/public/components/common/data-source/search-params-builder.ts create mode 100644 plugins/main/public/components/common/data-source/types.ts 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 deleted file mode 100644 index 7c55a6e444..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-factory.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { tDataSource, tParsedIndexPattern } from './index'; - -export interface IDataSourceFactoryConstructor { - new (id: T['id'], title: T['title']): T; -} - -export type tDataSourceFactory = { - create: (DataSourceType: IDataSourceFactoryConstructor, data: T) => Promise; - createAll: (DataSourceType: IDataSourceFactoryConstructor, data: T[]) => Promise; -} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/data-source-filter-manager.ts deleted file mode 100644 index 29bd8b733c..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-filter-manager.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { tDataSource } from "./data-source"; -import { tFilter, tSearchParams } from "./search-params-builder"; -import store from '../../../redux/store'; -import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../react-services/data-sources/vulnerabilities-states'; -import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT } from "../../../../common/constants"; - -export type tDataSourceFilterManager = { - fetch: () => Promise; - setFilters: (filters: tFilter[]) => void; - getFixedFilters: () => tFilter[]; - getFilters: () => tFilter[]; - getFetchFilters: () => tFilter[]; - addMetaDataInFilter: (filter: tFilter) => tFilter; - // global filters - getPinnedAgentFilter: () => tFilter[]; - getExcludeManagerFilter: () => tFilter[]; - getAllowAgentsFilter: () => tFilter[]; -} - -export class DataSourceFilterManager implements tDataSourceFilterManager { - - constructor(private dataSource: tDataSource, private filters: tFilter[] = []) { - if (!dataSource) { - throw new Error('Data source is required'); - } - this.dataSource = dataSource; - this.filters = filters.length ? this.filterUserFilters(filters) : []; - } - - /** - * Get the filters necessary to fetch the data from the data source - * @returns - */ - fetch(params: Omit = {}): Promise { - return this.dataSource.fetch({ - ...params, - filters: this.getFetchFilters(), - }); - } - - setFilters(filters: tFilter[]) { - this.filters = this.filterUserFilters(filters) || []; - } - - /** - * Filter the filters that was added by the user - * The filters must not have: - * - the property isImplicit ($state.isImplicit) defined - * - the meta.controlledBy property defined - * - the meta.index is not the same as the dataSource.id - * - */ - private filterUserFilters(filters: tFilter[]) { - if (!filters) return []; - return this.removeRepeatedFilters(filters.filter( - filter => !(filter?.$state?.['isImplicit'] || filter.meta?.controlledBy || filter.meta?.index !== this.dataSource.id) - )); - } - - /** - * Return the fixed filters. The fixed filters are filters that cannot be removed by the user. - * The filters for the specific data source are defined in the data source. - * Also, exists fixed filters that are defined in the data source filter manager (globally). - * @returns - */ - getFixedFilters(): tFilter[] { - const fixedFilters = this.dataSource.getFixedFilters(); - const pinnedAgent = this.getPinnedAgentFilter(); - - return [ - ...fixedFilters, - ...pinnedAgent - ].map(filter => this.addMetaDataInFilter(filter)); - } - - /** - * Return the filters that was added by the user and the fixed filters. - * This can be use to show the filters in the UI (For instance: SearchBar) - * @returns - */ - getFilters() { - return [ - ...this.getFixedFilters(), - ...this.filters, - ] - } - - /** - * Concatenate the filters to fetch the data from the data source - * @returns - */ - getFetchFilters(): tFilter[] { - const filters = this.getFilters(); - const excludeManager = this.getExcludeManagerFilter(); - const allowedAgents = this.getAllowAgentsFilter(); - - return [ - ...filters, - ...allowedAgents, - ...excludeManager - ].map(filter => this.addMetaDataInFilter(filter)); - - } - - /** - * Remove filter repeated filters in query property - * @param filter - * @returns - */ - - removeRepeatedFilters(filters: tFilter) { - if (!filters) return filters; - const query = filters.query; - if (!query) return filters; - const keys = Object.keys(query); - if (keys.length === 1) { - const key = keys[0]; - if (query[key].query) { - query[key].query = Array.isArray(query[key].query) ? Array.from(new Set(query[key].query)) : query[key].query; - } - } - return filters; - } - - addMetaDataInFilter(filter: tFilter) { - //check is necessary add the controlled by here or add in the data source - //filter.meta.controlledBy = 'data-source-filter-manager'; - //filter.meta.index = this.dataSource.id; - return filter; - } - - /** - * Returns the filter when the an agent is pinned (saved in the session storage or redux store) - */ - getPinnedAgentFilter(): tFilter[] { - const agentId = store.getState().appStateReducers?.currentAgentData?.id; - if (!agentId) return []; - return [{ - meta: { - removable: false, // used to hide the close icon in the filter - alias: null, - disabled: false, - key: 'agent.id', - negate: false, - params: { query: agentId }, - type: 'phrase', - index: this.dataSource.id, - controlledBy: DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT - }, - query: { - match: { - 'agent.id': { - query: agentId, - type: 'phrase', - }, - }, - }, - $state: { - store: 'appState' // check appStore is not assignable, why is stored here? - }, - } as tFilter] - } - - /** - * Return the filter to exclude the data related to servers (managers) due to the setting hideManagerAlerts is enabled - */ - getExcludeManagerFilter(): tFilter[] { - return store.getState().appConfig?.data?.hideManagerAlerts ? - [getFilterExcludeManager(this.dataSource.title) as tFilter] : []; - } - - /** - * Return the allowed agents related to the user permissions to read data from agents in the - API server - */ - getAllowAgentsFilter(): tFilter[] { - const allowedAgents = store.getState().appStateReducers?.allowedAgents || []; - return allowedAgents.lenght > 0 ? - [getFilterAllowedAgents(allowedAgents, this.dataSource.title) as tFilter] : [] - } - -} \ 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 deleted file mode 100644 index c20e1a465e..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-repository.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type tDataSourceRepository = { - get(id: string): Promise; - getAll(): Promise; - setDefault(dataSourceData: T): Promise | void; - getDefault(): Promise | T | null; -} \ 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 deleted file mode 100644 index fa50b21438..0000000000 --- a/plugins/main/public/components/common/data-source/data-source-selector.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { tDataSource } from "./index"; - -export type tDataSourceSelector = { - existsDataSource: (id: string) => Promise; - getFirstValidDataSource: () => Promise; - getAllDataSources: () => Promise; - getDataSource: (id: string) => Promise; - getSelectedDataSource: () => Promise; - selectDataSource: (id: string) => Promise; -} \ 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 deleted file mode 100644 index b7d7e32c5e..0000000000 --- a/plugins/main/public/components/common/data-source/data-source.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { tFilter, tSearchParams } from './search-params-builder'; - -export type tDataSource = { - id: string; - title: string; - select(): Promise; - getFilters: () => Promise | tFilter[]; - setFilters: (filters: tFilter[]) => Promise | void; - getFields: () => Promise | any[]; - getFixedFilters: () => tFilter[]; - getFetchFilters: () => tFilter[]; - fetch: (params: tSearchParams) => Promise; - toJSON(): object; -} \ 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 index c4c7def5ff..e697801d4c 100644 --- a/plugins/main/public/components/common/data-source/index.ts +++ b/plugins/main/public/components/common/data-source/index.ts @@ -1,9 +1,4 @@ -export * from './data-source'; -export * from './data-source-repository'; -export * from './data-source-factory'; -export * from './data-source-selector'; -export * from './data-source-filter-manager'; -export * from './search-params-builder'; export * from './pattern'; export * from './hooks'; -export * from './pattern'; \ No newline at end of file +export * from './pattern'; +export * from './types' \ 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 index 40ab026096..dc629386ce 100644 --- a/plugins/main/public/components/common/data-source/pattern/index.ts +++ b/plugins/main/public/components/common/data-source/pattern/index.ts @@ -2,6 +2,8 @@ export * from './pattern-data-source-repository'; export * from './pattern-data-source'; export * from './pattern-data-source-factory'; export * from './pattern-data-source'; +export * from './pattern-data-source-selector'; +export * from './pattern-data-source-filter-manager'; export * from './alerts'; export * from './vulnerabilities'; export * from './alerts'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts similarity index 100% rename from plugins/main/public/components/common/data-source/data-source-filter-manager.test.ts rename to plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts new file mode 100644 index 0000000000..9ca1a7685e --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts @@ -0,0 +1,100 @@ +import { tDataSourceFilterManager, tFilter } from '../index'; + +export class PatternDataSourceFilterManager implements tDataSourceFilterManager { + + constructor(private dataSource: tDataSource, private filters: tFilter[] = []) { + if (!dataSource) { + throw new Error('Data source is required'); + } + this.dataSource = dataSource; + this.filters = filters.length ? this.filterUserFilters(filters) : []; + } + + /** + * Get the filters necessary to fetch the data from the data source + * @returns + */ + fetch(params: Omit = {}): Promise { + return this.dataSource.fetch({ + ...params, + filters: this.getFetchFilters(), + }); + } + + setFilters(filters: tFilter[]) { + this.filters = this.filterUserFilters(filters) || []; + } + + /** + * Filter the filters that was added by the user + * The filters must not have: + * - the property isImplicit ($state.isImplicit) defined + * - the meta.controlledBy property defined + * - the meta.index is not the same as the dataSource.id + * + */ + private filterUserFilters(filters: tFilter[]) { + if (!filters) return []; + return this.removeRepeatedFilters(filters.filter( + filter => !(filter?.$state?.['isImplicit'] || filter.meta?.controlledBy || filter.meta?.index !== this.dataSource.id) + )); + } + + /** + * Return the fixed filters. The fixed filters are filters that cannot be removed by the user. + * The filters for the specific data source are defined in the data source. + * Also, exists fixed filters that are defined in the data source filter manager (globally). + * @returns + */ + getFixedFilters(): tFilter[] { + const fixedFilters = this.dataSource.getFixedFilters(); + + return [ + ...fixedFilters, + ] + } + + /** + * Return the filters that was added by the user and the fixed filters. + * This can be use to show the filters in the UI (For instance: SearchBar) + * @returns + */ + getFilters() { + return [ + ...this.getFixedFilters(), + ...this.filters, + ] + } + + /** + * Concatenate the filters to fetch the data from the data source + * @returns + */ + getFetchFilters(): tFilter[] { + return [ + ...this.dataSource.getFetchFilters() + ] + + } + + /** + * Remove filter repeated filters in query property + * @param filter + * @returns + */ + + removeRepeatedFilters(filters: tFilter) { + if (!filters) return filters; + const query = filters.query; + if (!query) return filters; + const keys = Object.keys(query); + if (keys.length === 1) { + const key = keys[0]; + if (query[key].query) { + query[key].query = Array.isArray(query[key].query) ? Array.from(new Set(query[key].query)) : query[key].query; + } + } + return filters; + } + + } \ 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/pattern/pattern-data-source-selector.test.ts similarity index 100% rename from plugins/main/public/components/common/data-source/data-source-selector.test.ts rename to plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.test.ts 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 878901c5f3..4a965b32b0 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 @@ -2,14 +2,19 @@ import { tDataSource, tSearchParams, tFilter, tParsedIndexPattern } from "../ind import { getDataPlugin } from '../../../../kibana-services'; import { Filter, IndexPatternsContract, IndexPattern } from "../../../../../../../src/plugins/data/public"; import { search } from '../../search-bar/search-bar-service'; +import store from '../../../../redux/store'; +import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../../react-services/data-sources/vulnerabilities-states'; +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT } from "../../../../../common/constants"; -export class PatternDataSource implements tDataSource{ + +export class PatternDataSource implements tDataSource { id: string; title: string; fields: any[]; patternService: IndexPatternsContract; indexPattern: IndexPattern; defaultFixedFilters: tFilter[]; + filters: tFilter[]; constructor(id: string, title: string) { this.id = id; @@ -27,20 +32,31 @@ export class PatternDataSource implements tDataSource{ return this.fields; } - setFilters: (filters: Filter[]) => void | Promise; + setFilters(filters: Filter[]) { + this.filters = filters; + } getFilters(){ - return []; + return [ + ...this.getFixedFilters(), + ...this.filters + ]; } getFixedFilters(): tFilter[]{ // return all filters - return []; + return [ + ...this.getPinnedAgentFilter(), + ]; } getFetchFilters(): tFilter[]{ - return []; + return [ + ...this.filters, + ...this.getAllowAgentsFilter(), + ...this.getExcludeManagerFilter() + ]; } async select(){ @@ -107,4 +123,55 @@ export class PatternDataSource implements tDataSource{ } } + /** + * Returns the filter when the an agent is pinned (saved in the session storage or redux store) + */ + getPinnedAgentFilter(): tFilter[] { + const agentId = store.getState().appStateReducers?.currentAgentData?.id; + if (!agentId) return []; + return [{ + meta: { + removable: false, // used to hide the close icon in the filter + alias: null, + disabled: false, + key: 'agent.id', + negate: false, + params: { query: agentId }, + type: 'phrase', + index: this.dataSource.id, + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT + }, + query: { + match: { + 'agent.id': { + query: agentId, + type: 'phrase', + }, + }, + }, + $state: { + store: 'appState' // check appStore is not assignable, why is stored here? + }, + } as tFilter] + } + + /** + * Return the filter to exclude the data related to servers (managers) due to the setting hideManagerAlerts is enabled + */ + getExcludeManagerFilter(): tFilter[] { + return store.getState().appConfig?.data?.hideManagerAlerts ? + [getFilterExcludeManager(this.dataSource.title) as tFilter] : []; + } + + /** + * Return the allowed agents related to the user permissions to read data from agents in the + API server + */ + getAllowAgentsFilter(): tFilter[] { + const allowedAgents = store.getState().appStateReducers?.allowedAgents || []; + return allowedAgents.lenght > 0 ? + [getFilterAllowedAgents(allowedAgents, this.dataSource.title) as tFilter] : [] + } + + } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts index ea64cabdec..1f587e0579 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts @@ -68,6 +68,7 @@ export class VulnerabilitiesDataSource extends PatternDataSource { getFixedFilters(): tFilter[] { return [ ...this.getClusterManagerFilters(), + ...super.getFixedFilters(), ] } diff --git a/plugins/main/public/components/common/data-source/search-params-builder.ts b/plugins/main/public/components/common/data-source/search-params-builder.ts deleted file mode 100644 index 040bd270c0..0000000000 --- a/plugins/main/public/components/common/data-source/search-params-builder.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Filter } from "../../../../../../src/plugins/data/common"; - -export type tFilter = {} & Filter; - -export type tSearchParams = { - filters?: tFilter[]; - query?: any; - pagination?: { - pageIndex?: number; - pageSize?: number; - }; - fields?: string[], - sorting?: { - columns: { - id: string; - direction: 'asc' | 'desc'; - }[]; - }; - dateRange?: { - from: string; - to: string; - }; -} - -export class SearchParamsBuilder { - private searchParams: tSearchParams; - - constructor() { - this.searchParams = {}; - } - - addFilters(filters: tFilter[]): SearchParamsBuilder { - this.searchParams.filters = filters; - return this; - } - - addQuery(query: any): SearchParamsBuilder { - this.searchParams.query = query; - return this; - } - - addPagination(pageIndex: number, pageSize: number): SearchParamsBuilder { - this.searchParams.pagination = { pageIndex, pageSize }; - return this; - } - - addFields(fields: string[]): SearchParamsBuilder { - this.searchParams.fields = fields; - return this; - } - - addSorting(columns: { id: string, direction: 'asc' | 'desc' }[]): SearchParamsBuilder { - this.searchParams.sorting = { columns }; - return this; - } - - addDateRange(from: string, to: string): SearchParamsBuilder { - this.searchParams.dateRange = { from, to }; - return this; - } - - build(): tSearchParams { - return this.searchParams; - } -} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/types.ts b/plugins/main/public/components/common/data-source/types.ts new file mode 100644 index 0000000000..e5f53606c5 --- /dev/null +++ b/plugins/main/public/components/common/data-source/types.ts @@ -0,0 +1,75 @@ +import { Filter } from "../../../../../../src/plugins/data/common"; + +export type tFilter = Filter & { + meta?: { + removable?: boolean; + } +}; + +export type tSearchParams = { + filters?: tFilter[]; + query?: any; + pagination?: { + pageIndex?: number; + pageSize?: number; + }; + fields?: string[], + sorting?: { + columns: { + id: string; + direction: 'asc' | 'desc'; + }[]; + }; + dateRange?: { + from: string; + to: string; + }; +} + +export type tDataSource = { + id: string; + title: string; + select(): Promise; + getFilters: () => Promise | tFilter[]; + setFilters: (filters: tFilter[]) => Promise | void; + getFields: () => Promise | any[]; + getFixedFilters: () => tFilter[]; + getFetchFilters: () => tFilter[]; + fetch: (params: tSearchParams) => Promise; + toJSON(): object; +} + +export interface IDataSourceFactoryConstructor { + new(id: T['id'], title: T['title']): T; +} + +export type tDataSourceFactory = { + create: (DataSourceType: IDataSourceFactoryConstructor, data: T) => Promise; + createAll: (DataSourceType: IDataSourceFactoryConstructor, data: T[]) => Promise; +} + +export type tDataSourceFilterManager = { + fetch: () => Promise; + setFilters: (filters: tFilter[]) => void; + getFixedFilters: () => tFilter[]; + getFilters: () => tFilter[]; + getFetchFilters: () => tFilter[]; + addMetaDataInFilter: (filter: tFilter) => tFilter; +} + +export type tDataSourceRepository = { + get(id: string): Promise; + getAll(): Promise; + setDefault(dataSourceData: T): Promise | void; + getDefault(): Promise | T | null; +} + + +export type tDataSourceSelector = { + existsDataSource: (id: string) => Promise; + getFirstValidDataSource: () => Promise; + getAllDataSources: () => Promise; + getDataSource: (id: string) => Promise; + getSelectedDataSource: () => Promise; + selectDataSource: (id: string) => Promise; +} From 42cd72f351b2c881d972689323e6d9744618b766 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Tue, 26 Mar 2024 09:20:19 -0300 Subject: [PATCH 22/36] Fixed all vuls tabs with new typo --- plugins/main/common/constants.ts | 1 + .../data-source/hooks/use-data-source.ts | 29 ++++++---- .../alerts-vulnerabilities-data-source.ts | 49 ++++++++++++++++ .../pattern-data-source-filter-manager.ts | 10 ++-- .../pattern/pattern-data-source.ts | 38 ++++++++++--- .../pattern/vulnerabilities/index.ts | 3 +- .../vulnerabilities-data-source-factory.ts | 27 --------- .../vulnerabilities-data-source.ts | 57 +------------------ .../components/common/data-source/types.ts | 3 +- .../dashboards/inventory/inventory.tsx | 9 ++- .../dashboards/overview/dashboard.tsx | 8 +-- 11 files changed, 116 insertions(+), 118 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts delete mode 100644 plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index 4a2a75f5e9..ab80158608 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -227,6 +227,7 @@ export const AUTHORIZED_AGENTS = 'authorized-agents'; export const DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER = 'exclude-server'; export const DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT = 'pinned-agent'; export const DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER = 'cluster-manager'; +export const DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP = 'vulnerabilities-rule-group'; // Wazuh links export const WAZUH_LINK_GITHUB = 'https://github.com/wazuh'; 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 99a62e2681..578a2f76fd 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 @@ -1,17 +1,21 @@ import React, { useEffect, useState } from "react"; -import { tFilter, tSearchParams } from "../search-params-builder"; import { tDataSource, - tDataSourceFactory, - DataSourceRepository, - DataSourceSelector, - DataSourceFilterManager + tDataSourceRepository, + tFilter, + tSearchParams, + PatternDataSourceSelector, + PatternDataSourceFactory, + VulnerabilitiesDataSource, + PatternDataSource, + tParsedIndexPattern, + PatternDataSourceFilterManager } from '../index'; -type tUseDataSourceProps = { +type tUseDataSourceProps = { filters: tFilter[]; - factory: tDataSourceFactory; - repository: DataSourceRepository; + factory: PatternDataSourceFactory; + repository: tDataSourceRepository; } type tUseDataSourceLoadedReturns = { @@ -34,7 +38,7 @@ type tUseDataSourceNotLoadedReturns = { setFilters: (filters: tFilter[]) => void; } -export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { +export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { const { filters: defaultFilters = [], factory, repository } = props; if (!factory || !repository) { @@ -42,7 +46,7 @@ export function useDataSource(props: tUseDataSourceProps): tUseDataS } const [dataSource, setDataSource] = useState(); - const [dataSourceFilterManager, setDataSourceFilterManager] = useState(null); + const [dataSourceFilterManager, setDataSourceFilterManager] = useState(null); const [isLoading, setIsLoading] = useState(true); const [fetchFilters, setFetchFilters] = useState([]); const [allFilters, setAllFilters] = useState([]); @@ -69,12 +73,13 @@ export function useDataSource(props: tUseDataSourceProps): tUseDataS const init = async () => { setIsLoading(true); - const selector = new DataSourceSelector(repository, factory); + const dataSources = await factory.createAll(VulnerabilitiesDataSource, await repository.getAll()); + const selector = new PatternDataSourceSelector(dataSources, repository); const dataSource = await selector.getSelectedDataSource(); if (!dataSource) { throw new Error('No valid data source found'); } - const dataSourceFilterManager = new DataSourceFilterManager(dataSource, defaultFilters); + const dataSourceFilterManager = new PatternDataSourceFilterManager(dataSource, defaultFilters); if (!dataSourceFilterManager) { throw new Error('Error creating filter manager'); } diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts new file mode 100644 index 0000000000..c8c6a28559 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts @@ -0,0 +1,49 @@ +import { PatternDataSource, tFilter } from "../../../index"; +import { DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP } from '../../../../../../../common/constants'; + + +const VULNERABILITIES_GROUP_KEY = 'rules.group'; +const VULNERABILITIES_GROUP_VALUE = 'vulnerability-detector'; + +export class AlertsVulnerabilitiesDataSource extends PatternDataSource { + constructor(id: string, title: string) { + super(id, title); + } + + getRuleGroupsFilter() { + return [{ + meta: { + removable: false, // used to hide the close icon in the filter + index: this.id, + negate: false, + disabled: false, + alias: null, + type: 'phrase', + key: VULNERABILITIES_GROUP_KEY, + value: VULNERABILITIES_GROUP_VALUE, + params: { + query: VULNERABILITIES_GROUP_VALUE, + type: 'phrase', + }, + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP + }, + query: { + match: { + [VULNERABILITIES_GROUP_KEY]: { + query: VULNERABILITIES_GROUP_VALUE, + type: 'phrase', + } + }, + }, + $state: { + store: 'appState', + }, + } as tFilter]; + } + + getFixedFilters(): tFilter[] { + return [ + ...super.getFixedFilters(), + ] + } +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts index 9ca1a7685e..42618d0443 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts @@ -1,4 +1,4 @@ -import { tDataSourceFilterManager, tFilter } from '../index'; +import { tDataSourceFilterManager, tFilter, tSearchParams, tDataSource } from '../index'; export class PatternDataSourceFilterManager implements tDataSourceFilterManager { @@ -7,7 +7,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager throw new Error('Data source is required'); } this.dataSource = dataSource; - this.filters = filters.length ? this.filterUserFilters(filters) : []; + this.filters = filters.length ? this.filterUserFilters(filters) as tFilter[] : []; } /** @@ -17,12 +17,12 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager fetch(params: Omit = {}): Promise { return this.dataSource.fetch({ ...params, - filters: this.getFetchFilters(), + filters: [], }); } setFilters(filters: tFilter[]) { - this.filters = this.filterUserFilters(filters) || []; + this.filters = this.filterUserFilters(filters) as tFilter[] || []; } /** @@ -37,7 +37,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager if (!filters) return []; return this.removeRepeatedFilters(filters.filter( filter => !(filter?.$state?.['isImplicit'] || filter.meta?.controlledBy || filter.meta?.index !== this.dataSource.id) - )); + ) as tFilter[] || []); } /** 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 4a965b32b0..43dc3f746a 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 @@ -4,8 +4,9 @@ import { Filter, IndexPatternsContract, IndexPattern } from "../../../../../../. import { search } from '../../search-bar/search-bar-service'; import store from '../../../../redux/store'; import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../../react-services/data-sources/vulnerabilities-states'; -import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT } from "../../../../../common/constants"; - +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER, VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from "../../../../../common/constants"; +import { AppState } from '../../../../react-services/app-state'; +import { FilterHandler } from '../../../../utils/filter-handler'; export class PatternDataSource implements tDataSource { id: string; @@ -14,7 +15,7 @@ export class PatternDataSource implements tDataSource { patternService: IndexPatternsContract; indexPattern: IndexPattern; defaultFixedFilters: tFilter[]; - filters: tFilter[]; + filters: tFilter[] = []; constructor(id: string, title: string) { this.id = id; @@ -47,15 +48,16 @@ export class PatternDataSource implements tDataSource { getFixedFilters(): tFilter[]{ // return all filters return [ + ...this.getClusterManagerFilters(), ...this.getPinnedAgentFilter(), ]; } getFetchFilters(): tFilter[]{ return [ - ...this.filters, ...this.getAllowAgentsFilter(), - ...this.getExcludeManagerFilter() + ...this.getExcludeManagerFilter(), + ...this.getFilters(), ]; } @@ -123,6 +125,26 @@ export class PatternDataSource implements tDataSource { } } + getClusterManagerFilters() { + const filterHandler = new FilterHandler(); + const isCluster = AppState.getClusterInfo().status == 'enabled'; + const managerFilter = filterHandler.managerQuery( + isCluster + ? AppState.getClusterInfo().cluster + : AppState.getClusterInfo().manager, + true, + VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ + AppState.getClusterInfo().status + ], + ); + managerFilter.meta.index = this.id; + managerFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER; + managerFilter.$state = { + store: 'appState' + } + return [managerFilter] as tFilter[]; + } + /** * Returns the filter when the an agent is pinned (saved in the session storage or redux store) */ @@ -138,7 +160,7 @@ export class PatternDataSource implements tDataSource { negate: false, params: { query: agentId }, type: 'phrase', - index: this.dataSource.id, + index: this.id, controlledBy: DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT }, query: { @@ -160,7 +182,7 @@ export class PatternDataSource implements tDataSource { */ getExcludeManagerFilter(): tFilter[] { return store.getState().appConfig?.data?.hideManagerAlerts ? - [getFilterExcludeManager(this.dataSource.title) as tFilter] : []; + [getFilterExcludeManager(this.title) as tFilter] : []; } /** @@ -170,7 +192,7 @@ export class PatternDataSource implements tDataSource { getAllowAgentsFilter(): tFilter[] { const allowedAgents = store.getState().appStateReducers?.allowedAgents || []; return allowedAgents.lenght > 0 ? - [getFilterAllowedAgents(allowedAgents, this.dataSource.title) as tFilter] : [] + [getFilterAllowedAgents(allowedAgents, this.title) as tFilter] : [] } diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts index 88de51fc8d..07ae20a8f0 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/index.ts @@ -1,3 +1,2 @@ export * from './vulnerabilities-data-source-repository'; -export * from './vulnerabilities-data-source'; -export * from './vulnerabilities-data-source-factory'; \ No newline at end of file +export * from './vulnerabilities-data-source'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts deleted file mode 100644 index cd189b88f4..0000000000 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-factory.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { tDataSource, tParsedIndexPattern, tDataSourceFactory } from "../../index"; -import { VulnerabilitiesDataSource } from "./vulnerabilities-data-source"; - -export class VulnerabilitiesDataSourceFactory implements tDataSourceFactory { - - async create(item: tParsedIndexPattern): Promise { - if(!item){ - throw new Error('Cannot create data source from null or undefined'); - }; - const dataSource = new VulnerabilitiesDataSource(item.id, item.attributes.title); - await dataSource.init(); - return dataSource; - } - async createAll(items: tParsedIndexPattern[]): Promise { - if(!items){ - throw new Error('Cannot create data source from null or undefined'); - }; - - const dataSources: VulnerabilitiesDataSource[] = []; - for (const item of items) { - const dataSource = new VulnerabilitiesDataSource(item.id, item.attributes.title); - await dataSource.init(); - dataSources.push(dataSource); - } - return dataSources; - } -} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts index 1f587e0579..9cfd29d210 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts @@ -1,73 +1,18 @@ -import { tFilter } from '../../search-params-builder'; +import { tFilter } from '../../index'; import { PatternDataSource } from '../pattern-data-source'; import { AppState } from '../../../../../react-services/app-state'; import { FilterHandler } from '../../../../../utils/filter-handler'; import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER } from '../../../../../../common/constants'; -const VULNERABILITIES_GROUP_KEY = 'rules.group'; -const VULNERABILITIES_GROUP_VALUE = 'vulnerability-detector'; - export class VulnerabilitiesDataSource extends PatternDataSource { constructor(id: string, title: string) { super(id, title); } - getClusterManagerFilters() { - const filterHandler = new FilterHandler(); - const isCluster = AppState.getClusterInfo().status == 'enabled'; - const managerFilter = filterHandler.managerQuery( - isCluster - ? AppState.getClusterInfo().cluster - : AppState.getClusterInfo().manager, - true, - VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ - AppState.getClusterInfo().status - ], - ); - managerFilter.meta.index = this.id; - managerFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER; - managerFilter.$state = { - store: 'appState' - } - return [managerFilter] as tFilter[]; - } - - getRuleGroupsFilter() { - return [{ - meta: { - removable: false, // used to hide the close icon in the filter - index: this.id, - negate: false, - disabled: false, - alias: null, - type: 'phrase', - key: VULNERABILITIES_GROUP_KEY, - value: VULNERABILITIES_GROUP_VALUE, - params: { - query: VULNERABILITIES_GROUP_VALUE, - type: 'phrase', - }, - controlledBy: 'wazuh' // or concatenate with the index name - }, - query: { - match: { - 'rules.group': { - query: VULNERABILITIES_GROUP_VALUE, - type: 'phrase', - } - }, - }, - $state: { - store: 'appState', - }, - } as tFilter]; - } - getFixedFilters(): tFilter[] { return [ - ...this.getClusterManagerFilters(), ...super.getFixedFilters(), ] } diff --git a/plugins/main/public/components/common/data-source/types.ts b/plugins/main/public/components/common/data-source/types.ts index e5f53606c5..8ecd4c326f 100644 --- a/plugins/main/public/components/common/data-source/types.ts +++ b/plugins/main/public/components/common/data-source/types.ts @@ -51,10 +51,9 @@ export type tDataSourceFactory = { export type tDataSourceFilterManager = { fetch: () => Promise; setFilters: (filters: tFilter[]) => void; - getFixedFilters: () => tFilter[]; getFilters: () => tFilter[]; + getFixedFilters: () => tFilter[]; getFetchFilters: () => tFilter[]; - addMetaDataInFilter: (filter: tFilter) => tFilter; } export type tDataSourceRepository = { diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index abf594e97d..04a57c03a7 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -46,7 +46,12 @@ import { compose } from 'redux'; import { withVulnerabilitiesStateDataSource } from '../../common/hocs/validate-vulnerabilities-states-index-pattern'; import { ModuleEnabledCheck } from '../../common/components/check-module-enabled'; -import { VulnerabilitiesDataSourceRepository, VulnerabilitiesDataSourceFactory, tFilter, tParsedIndexPattern, PatternDataSource } from '../../../../common/data-source'; +import { + VulnerabilitiesDataSourceRepository, + PatternDataSourceFactory, + tFilter, + tParsedIndexPattern, + PatternDataSource } from '../../../../common/data-source'; import { useDataSource } from '../../../../common/data-source/hooks'; import { FilterManager } from '../../../../../../../../src/plugins/data/public'; @@ -64,7 +69,7 @@ const InventoryVulsComponent = () => { fetchData, } = useDataSource({ filters: filterManager.getFilters(), - factory: new VulnerabilitiesDataSourceFactory(), + factory: new PatternDataSourceFactory(), repository: new VulnerabilitiesDataSourceRepository() }); diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index bcfac7e0c3..acb4ceb822 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -23,7 +23,7 @@ import { ModuleEnabledCheck } from '../../common/components/check-module-enabled import { VulnerabilitiesDataSourceRepository, - VulnerabilitiesDataSourceFactory, + PatternDataSourceFactory, PatternDataSource, tFilter, tParsedIndexPattern @@ -53,7 +53,7 @@ const DashboardVulsComponent: React.FC = () => { fetchData, } = useDataSource({ filters: filterManager.getFilters(), - factory: new VulnerabilitiesDataSourceFactory(), + factory: new PatternDataSourceFactory(), repository: new VulnerabilitiesDataSourceRepository() }); @@ -103,8 +103,8 @@ const DashboardVulsComponent: React.FC = () => { <> - {isLoading ? : null} - {!isLoading ? ( + {isLoading || isDataSourceLoading ? : null} + {!isDataSourceLoading ? ( Date: Tue, 26 Mar 2024 16:53:33 -0300 Subject: [PATCH 23/36] Update data source with inner filter manager --- .../data-source/hooks/use-data-source.test.ts | 106 ++++++++++++++++++ .../data-source/hooks/use-data-source.ts | 60 ++++++---- .../components/common/data-source/index.ts | 1 - .../data-source/pattern/alerts/index.ts | 1 + .../common/data-source/pattern/index.ts | 6 +- .../pattern-data-source-filter-manager.ts | 43 +++++-- .../pattern/pattern-data-source.ts | 12 +- 7 files changed, 186 insertions(+), 43 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts diff --git a/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts b/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts new file mode 100644 index 0000000000..96d81718c4 --- /dev/null +++ b/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts @@ -0,0 +1,106 @@ +import { useDataSource } from './use-data-source'; +// react testing library to test hooks +import { renderHook } from '@testing-library/react-hooks'; +import { + tDataSource, + tDataSourceRepository, + tFilter, + tSearchParams, + PatternDataSource, + tParsedIndexPattern +} from '../index'; +import { IndexPatternsService, IndexPattern } from '../../../../../../../src/plugins/data/common'; + + +const mockedGetFilters = jest.fn().mockReturnValue([]); + +class DataSourceMocked implements PatternDataSource { + constructor(public id: string, public title: string) { + this.id = id; + this.title = title; + } + fields: any[]; + patternService: IndexPatternsService; + indexPattern: IndexPattern; + defaultFixedFilters: tFilter[]; + filters: tFilter[]; + init = jest.fn(); + select = jest.fn(); + fetch = jest.fn(); + getFilters = mockedGetFilters; + setFilters = jest.fn(); + getFields = mockedGetFilters + getFixedFilters = mockedGetFilters + getFetchFilters = mockedGetFilters + toJSON(): tParsedIndexPattern { + return { + id: this.id, + title: this.title, + } as tParsedIndexPattern; + } + getClusterManagerFilters = mockedGetFilters + getPinnedAgentFilter = mockedGetFilters + getExcludeManagerFilter = mockedGetFilters + getAllowAgentsFilter = mockedGetFilters +} + +class ExampleRepository implements tDataSourceRepository { + getDefault = jest.fn(); + setDefault = jest.fn(); + get = jest.fn(); + getAll = jest.fn(); +} + +describe('useDataSource hook', () => { + + it('shoudl throw ERROR when the repository is not defined', () => { + + try { + renderHook(() => useDataSource({ + DataSource: DataSourceMocked, + repository: undefined as any + })); + } catch(error){ + expect(error).toBeDefined(); + expect(error.message).toBe('DataSource and repository are required'); + } + + }) + + it('shoudl throw ERROR when the DataSource is not defined', () => { + + try { + renderHook(() => useDataSource({ + DataSource: undefined as any, + repository: new ExampleRepository() + })); + } catch(error){ + expect(error).toBeDefined(); + expect(error.message).toBe('DataSource and repository are required'); + } + + }) + + it('should initialize the hook with only receiving the dataSource and repository', async () => { + const repository = new ExampleRepository(); + const indexMocked = { + id: 'test', + title: 'Test' + } + jest.spyOn(repository, 'getAll').mockResolvedValueOnce([indexMocked]); + jest.spyOn(repository, 'getDefault').mockResolvedValueOnce(indexMocked); + const { result, waitForNextUpdate } = renderHook(() => useDataSource({ + DataSource: DataSourceMocked, + repository + })); + // wait for the promise to resolve + await waitForNextUpdate(); + expect(result.current.isLoading).toBeFalsy(); + expect(result.current.dataSource).toBeDefined(); + expect(result.current.dataSource?.id).toBe('test'); + expect(result.current.dataSource?.title).toBe('Test'); + }) + + +}) + 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 578a2f76fd..be80ac276c 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 @@ -1,21 +1,27 @@ import React, { useEffect, useState } from "react"; import { + IDataSourceFactoryConstructor, tDataSource, tDataSourceRepository, tFilter, tSearchParams, PatternDataSourceSelector, PatternDataSourceFactory, - VulnerabilitiesDataSource, PatternDataSource, tParsedIndexPattern, PatternDataSourceFilterManager } from '../index'; +import { FilterManager } from "../../../../../../../src/plugins/data/public"; + +// create a new type using the FilterManager type but only the getFilters, setFilters, addFilters, getUpdates$ +type tFilterManager = Pick; type tUseDataSourceProps = { - filters: tFilter[]; - factory: PatternDataSourceFactory; + DataSource: IDataSourceFactoryConstructor; repository: tDataSourceRepository; + factory?: PatternDataSourceFactory; + filterManager?: tFilterManager; + filters?: tFilter[]; } type tUseDataSourceLoadedReturns = { @@ -39,10 +45,16 @@ type tUseDataSourceNotLoadedReturns = { } export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { - const { filters: defaultFilters = [], factory, repository } = props; + const { + filterManager, + filters: defaultFilters = [], + DataSource: DataSourceConstructor, + repository, + factory: injectedFactory, + } = props; - if (!factory || !repository) { - throw new Error('Factory and repository are required'); + if (!repository || !DataSourceConstructor) { + throw new Error('DataSource and repository are required'); } const [dataSource, setDataSource] = useState(); @@ -73,7 +85,9 @@ export function useDataSource { setIsLoading(true); - const dataSources = await factory.createAll(VulnerabilitiesDataSource, await repository.getAll()); + 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) { @@ -83,6 +97,15 @@ export function useDataSource { + console.log('update filter manager'); + setAllFilters(dataSourceFilterManager.getFilters()); + setFetchFilters(dataSourceFilterManager.getFetchFilters()); + }, + }); setAllFilters(dataSourceFilterManager.getFilters()); setFetchFilters(dataSourceFilterManager.getFetchFilters()); setDataSourceFilterManager(dataSourceFilterManager); @@ -93,23 +116,22 @@ export function useDataSource = {}): Promise { return this.dataSource.fetch({ ...params, - filters: [], + filters: this.getFetchFilters(), }); } setFilters(filters: tFilter[]) { - this.filters = this.filterUserFilters(filters) as tFilter[] || []; + this.filterManager && this.filterManager.setFilters(filters); + } + + getDefaultFilters(filters: tFilter[]) { + const defaultFilters = filters.length ? filters : this.getFilters(); + return [ + ...this.getFixedFilters(), + ...this.filterUserFilters(defaultFilters) || [], + ]; } /** @@ -37,7 +57,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager if (!filters) return []; return this.removeRepeatedFilters(filters.filter( filter => !(filter?.$state?.['isImplicit'] || filter.meta?.controlledBy || filter.meta?.index !== this.dataSource.id) - ) as tFilter[] || []); + )) as tFilter[]; } /** @@ -48,7 +68,6 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager */ getFixedFilters(): tFilter[] { const fixedFilters = this.dataSource.getFixedFilters(); - return [ ...fixedFilters, ] @@ -60,10 +79,9 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager * @returns */ getFilters() { - return [ - ...this.getFixedFilters(), - ...this.filters, - ] + return [ + ...this.filterManager.getFilters() + ] } /** @@ -72,6 +90,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager */ getFetchFilters(): tFilter[] { return [ + ...this.getFilters(), ...this.dataSource.getFetchFilters() ] 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 43dc3f746a..442c3bce88 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 @@ -4,7 +4,7 @@ import { Filter, IndexPatternsContract, IndexPattern } from "../../../../../../. import { search } from '../../search-bar/search-bar-service'; import store from '../../../../redux/store'; import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../../react-services/data-sources/vulnerabilities-states'; -import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER, VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from "../../../../../common/constants"; +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER } from "../../../../../common/constants"; import { AppState } from '../../../../react-services/app-state'; import { FilterHandler } from '../../../../utils/filter-handler'; @@ -81,7 +81,7 @@ export class PatternDataSource implements tDataSource { async fetch(params: tSearchParams){ const indexPattern = await this.patternService.get(this.id); - const { filters: defaultFilters = [], query, pagination, sorting, fields } = params; + const { filters: defaultFilters = [], query, pagination, sorting, fields, dateRange } = params; if(!indexPattern){ return; } @@ -93,7 +93,8 @@ export class PatternDataSource implements tDataSource { query, pagination, sorting, - fields: fields + fields: fields, + dateRange } ); @@ -132,10 +133,7 @@ export class PatternDataSource implements tDataSource { isCluster ? AppState.getClusterInfo().cluster : AppState.getClusterInfo().manager, - true, - VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ - AppState.getClusterInfo().status - ], + true ); managerFilter.meta.index = this.id; managerFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER; From 5af0f2342d9d74787d2aa51c922d3af1026ee51f Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Tue, 26 Mar 2024 16:53:57 -0300 Subject: [PATCH 24/36] Implement in vulnerability detection tabs --- .../alerts-vulnerabilities-data-source.ts | 6 +- .../alerts/alerts-vulnerabilities/index.ts | 1 + .../vulnerabilities-data-source.ts | 26 ++++++- .../common/modules/modules-defaults.tsx | 39 +++++------ .../common/wazuh-discover/wz-discover.tsx | 67 ++++++------------- .../dashboards/inventory/inventory.tsx | 23 ++----- .../dashboards/overview/dashboard.tsx | 34 +++------- 7 files changed, 83 insertions(+), 113 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/index.ts diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts index c8c6a28559..b1b0b11fee 100644 --- a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts @@ -1,8 +1,9 @@ -import { PatternDataSource, tFilter } from "../../../index"; +import { tFilter } from "../../../index"; +import { PatternDataSource } from '../../pattern-data-source'; import { DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP } from '../../../../../../../common/constants'; -const VULNERABILITIES_GROUP_KEY = 'rules.group'; +const VULNERABILITIES_GROUP_KEY = 'rule.groups'; const VULNERABILITIES_GROUP_VALUE = 'vulnerability-detector'; export class AlertsVulnerabilitiesDataSource extends PatternDataSource { @@ -44,6 +45,7 @@ export class AlertsVulnerabilitiesDataSource extends PatternDataSource { getFixedFilters(): tFilter[] { return [ ...super.getFixedFilters(), + ...this.getRuleGroupsFilter() ] } } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/index.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/index.ts new file mode 100644 index 0000000000..3726d4e201 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/index.ts @@ -0,0 +1 @@ +export * from './alerts-vulnerabilities-data-source'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts index 9cfd29d210..d510fbfe99 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts @@ -1,9 +1,8 @@ +import { DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER, VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../../../../../common/constants'; +import { FilterHandler } from '../../../../../utils/filter-handler'; import { tFilter } from '../../index'; import { PatternDataSource } from '../pattern-data-source'; - import { AppState } from '../../../../../react-services/app-state'; -import { FilterHandler } from '../../../../../utils/filter-handler'; -import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER } from '../../../../../../common/constants'; export class VulnerabilitiesDataSource extends PatternDataSource { @@ -17,4 +16,25 @@ export class VulnerabilitiesDataSource extends PatternDataSource { ] } + getClusterManagerFilters() { + const filterHandler = new FilterHandler(); + const isCluster = AppState.getClusterInfo().status == 'enabled'; + const managerFilter = filterHandler.managerQuery( + isCluster + ? AppState.getClusterInfo().cluster + : AppState.getClusterInfo().manager, + true, + VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ + AppState.getClusterInfo().status + ], + + ); + managerFilter.meta.index = this.id; + managerFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER; + managerFilter.$state = { + store: 'appState' + } + return [managerFilter] as tFilter[]; + } + } \ No newline at end of file diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index a81a67fea1..27fcc0d0da 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.tsx +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -20,7 +20,7 @@ import { OfficePanel } from '../../overview/office-panel'; import { GitHubPanel } from '../../overview/github-panel'; import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities'; import { withModuleNotForAgent } from '../hocs'; -import { WazuhDiscover } from '../wazuh-discover/wz-discover'; +import { WazuhDiscover, WazuhDiscoverProps } from '../wazuh-discover/wz-discover'; import { threatHuntingColumns } from '../wazuh-discover/config/data-grid-columns'; import { vulnerabilitiesColumns } from '../../overview/vulnerabilities/events/vulnerabilities-columns'; import { DashboardFim } from '../../overview/fim/dashboard/dashboard'; @@ -42,7 +42,7 @@ import { mitreAttackColumns } from '../../overview/mitre/events/mitre-attack-col import { virustotalColumns } from '../../overview/virustotal/events/virustotal-columns'; import { malwareDetectionColumns } from '../../overview/malware-detection/events/malware-detection-columns'; import { WAZUH_VULNERABILITIES_PATTERN } from '../../../../common/constants'; -import { withVulnerabilitiesStateDataSource } from '../../overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern'; +import { AlertsVulnerabilitiesDataSource } from '../data-source'; const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*'; const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN; @@ -54,13 +54,14 @@ const DashboardTab = { component: Dashboard, }; -const renderDiscoverTab = (indexName = DEFAULT_INDEX_PATTERN, columns) => { +const renderDiscoverTab = (props: WazuhDiscoverProps) => { + const { DataSource, tableColumns } = props; return { id: 'events', name: 'Events', buttons: [ButtonModuleExploreAgent], component: () => ( - + ), }; }; @@ -73,7 +74,7 @@ const RegulatoryComplianceTabs = columns => [ buttons: [ButtonModuleExploreAgent], component: ComplianceTable, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN,columns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN, columns), ]; export const ModulesDefaults = { @@ -81,7 +82,7 @@ export const ModulesDefaults = { init: 'events', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN,threatHuntingColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN, threatHuntingColumns), ], availableFor: ['manager', 'agent'], }, @@ -100,7 +101,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: InventoryFim, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN,fileIntegrityMonitoringColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN, fileIntegrityMonitoringColumns), ], availableFor: ['manager', 'agent'], }, @@ -108,7 +109,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN,amazonWebServicesColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN, amazonWebServicesColumns), ], availableFor: ['manager', 'agent'], }, @@ -116,7 +117,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN,googleCloudColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN, googleCloudColumns), ], availableFor: ['manager', 'agent'], }, @@ -144,7 +145,7 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: MainSca, }, - renderDiscoverTab(DEFAULT_INDEX_PATTERN,configurationAssessmentColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN, configurationAssessmentColumns), ], buttons: ['settings'], availableFor: ['manager', 'agent'], @@ -165,7 +166,7 @@ export const ModulesDefaults = { component: withModuleNotForAgent(OfficePanel), }, { - ...renderDiscoverTab(DEFAULT_INDEX_PATTERN,office365Columns), + ...renderDiscoverTab(DEFAULT_INDEX_PATTERN, office365Columns), component: withModuleNotForAgent(() => ( ( - - )), - }, + renderDiscoverTab({ + tableColumns: vulnerabilitiesColumns, + DataSource: AlertsVulnerabilitiesDataSource + }) ], buttons: ['settings'], availableFor: ['manager', 'agent'], @@ -263,7 +260,7 @@ export const ModulesDefaults = { init: 'dashboard', tabs: [ DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN,dockerColumns), + renderDiscoverTab(DEFAULT_INDEX_PATTERN, 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 eb99a4188d..37c032ad8e 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -34,65 +34,55 @@ import { import { HitsCounter } from '../../../kibana-integrations/discover/application/components/hits_counter'; import { formatNumWithCommas } from '../../../kibana-integrations/discover/application/helpers'; import useSearchBar from '../search-bar/use-search-bar'; -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'; import { withErrorBoundary } from '../hocs'; -import { useFilterManager } from '../hooks'; -import { FilterManager } from '../../../../../../src/plugins/data/public'; import { + IDataSourceFactoryConstructor, useDataSource, tParsedIndexPattern, PatternDataSource, AlertsDataSourceRepository, - PatternDataSourceFactory } from '../data-source'; export const MAX_ENTRIES_PER_QUERY = 10000; -type WazuhDiscoverProps = { - defaultIndexPattern?: IndexPattern; +export type WazuhDiscoverProps = { tableColumns: tDataGridColumn[]; + DataSource: IDataSourceFactoryConstructor; }; const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { - const { defaultIndexPattern, tableColumns: defaultTableColumns } = props; + const { DataSource, tableColumns: defaultTableColumns } = props; + + if (!DataSource) { + throw new Error('DataSource is required'); + } + const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); const [inspectedHit, setInspectedHit] = useState(undefined); const [indexPattern, setIndexPattern] = useState( undefined, ); - const [isSearching, setIsSearching] = useState(false); const [isExporting, setIsExporting] = useState(false); const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav(); - const [indexPatternTitle, setIndexPatternTitle] = useState(''); - const filterManager = useFilterManager().filterManager as FilterManager; const { dataSource, - filters: defaultFilters, fetchFilters, setFilters, isLoading: isDataSourceLoading, fetchData, } = useDataSource({ - filters: filterManager.getFilters(), - factory: new PatternDataSourceFactory(), - repository: new AlertsDataSourceRepository() + repository: new AlertsDataSourceRepository(), // this makes only works with alerts index pattern + DataSource }); - useEffect(() => { - if (!isDataSourceLoading) { - filterManager.setFilters(defaultFilters); - } - }, [isDataSourceLoading]) - const onClickInspectDoc = useMemo( () => (index: number) => { const rowClicked = results.hits.hits[index]; @@ -117,11 +107,10 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { }; const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: indexPatternTitle, + defaultIndexPatternID: dataSource?.id, }); const { isLoading, - filters: searchBarFilters, query, dateRangeFrom, dateRangeTo, @@ -147,21 +136,17 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { indexPattern: indexPattern as IndexPattern, }); - - useEffect(() => { - if (!isLoading) { - setFilters(searchBarFilters as tFilter[]); - } - }, [ - JSON.stringify(searchBarFilters) - ]); - useEffect(() => { if (isLoading || isDataSourceLoading) { return; } setIndexPattern(dataSource?.indexPattern); - fetchData({ query, pagination, sorting }) + fetchData({ + query, + pagination, + sorting, + dateRange: { from: dateRangeFrom || '', to: dateRangeTo || '' }, + }) .then(results => { console.log('results', results); setResults(results); @@ -178,18 +163,10 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { JSON.stringify(query), JSON.stringify(pagination), JSON.stringify(sorting), + dateRangeFrom, + dateRangeTo, ]) - - const currentIndexPattern = useIndexPattern(); - - useEffect(() => { - if (currentIndexPattern) { - setIndexPattern(currentIndexPattern); - setIndexPatternTitle(currentIndexPattern.title); - } - }, [currentIndexPattern]) - const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; @@ -241,7 +218,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { {!isLoading && results?.hits?.total === 0 ? ( ) : null} - {!isLoading && results?.hits?.total > 0 ? ( + {!isLoading && !isDataSourceLoading && results?.hits?.total > 0 ? ( <> { { isLoading: isDataSourceLoading, fetchData, } = useDataSource({ - filters: filterManager.getFilters(), - factory: new PatternDataSourceFactory(), + DataSource: VulnerabilitiesDataSource, repository: new VulnerabilitiesDataSourceRepository() }); - useEffect(() => { - if (!isDataSourceLoading) { - filterManager.setFilters(defaultFilters); - } - }, [isDataSourceLoading]) - const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, }); - const { isLoading, filters: searchBarFilters, query } = searchBarProps; + const { isLoading, query } = searchBarProps; const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); @@ -160,14 +154,6 @@ const InventoryVulsComponent = () => { } }; - useEffect(() => { - if (!isLoading) { - setFilters(searchBarFilters as tFilter[]); - } - }, [ - JSON.stringify(searchBarFilters) - ]); - useEffect(() => { if (isLoading || isDataSourceLoading) { return; @@ -176,6 +162,7 @@ const InventoryVulsComponent = () => { fetchData({ query, pagination, sorting }) .then(results => { console.log('results', results); + console.log('filters', fetchFilters); setResults(results); }) .catch(error => { diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index acb4ceb822..5c69832a2d 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -23,6 +23,7 @@ import { ModuleEnabledCheck } from '../../common/components/check-module-enabled import { VulnerabilitiesDataSourceRepository, + VulnerabilitiesDataSource, PatternDataSourceFactory, PatternDataSource, tFilter, @@ -52,17 +53,10 @@ const DashboardVulsComponent: React.FC = () => { isLoading: isDataSourceLoading, fetchData, } = useDataSource({ - filters: filterManager.getFilters(), - factory: new PatternDataSourceFactory(), - repository: new VulnerabilitiesDataSourceRepository() + DataSource: VulnerabilitiesDataSource, + repository: new VulnerabilitiesDataSourceRepository(), }); - useEffect(() => { - if (!isDataSourceLoading) { - filterManager.setFilters(defaultFilters); - } - }, [isDataSourceLoading]) - const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, }); @@ -70,22 +64,13 @@ const DashboardVulsComponent: React.FC = () => { const { isLoading, query, filters: searchBarFilters } = searchBarProps; const [results, setResults] = useState({} as SearchResponse); - useEffect(() => { - if (!isLoading) { - setFilters(searchBarFilters as tFilter[]); - } - }, [ - JSON.stringify(searchBarFilters) - ]); - useEffect(() => { if (isLoading || isDataSourceLoading) { return; } fetchData({ query }).then(results => { - console.log('results', results); - setResults(results); - }) + setResults(results); + }) .catch(error => { const searchError = ErrorFactory.create(HttpError, { error, @@ -95,7 +80,7 @@ const DashboardVulsComponent: React.FC = () => { }); }, [ JSON.stringify(fetchFilters), - JSON.stringify(query) + JSON.stringify(query), ]) return ( @@ -103,8 +88,9 @@ const DashboardVulsComponent: React.FC = () => { <> - {isLoading || isDataSourceLoading ? : null} - {!isDataSourceLoading ? ( + { + isLoading || isDataSourceLoading ? + : { showQueryInput={true} showQueryBar={true} /> - ) : null} + } {!isLoading && results?.hits?.total === 0 ? ( ) : null} From 2ac433ce91dbf72c917ac4483ab32e3cb5540774 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 27 Mar 2024 17:22:59 -0300 Subject: [PATCH 25/36] Fix unit tests --- .../data-source/hooks/use-data-source.test.ts | 29 ++- .../data-source/hooks/use-data-source.ts | 20 +- .../pattern-data-source-factory.test.ts | 61 ++++-- ...pattern-data-source-filter-manager.test.ts | 89 ++++---- .../pattern-data-source-repository.test.ts | 9 +- .../pattern/pattern-data-source-repository.ts | 4 +- .../pattern-data-source-selector.test.ts | 191 +++++++++--------- .../pattern/pattern-data-source-selector.ts | 2 +- .../pattern/pattern-data-source.test.ts | 4 +- .../pattern/pattern-data-source.ts | 15 +- .../vulnerabilities-data-source-repository.ts | 6 +- .../components/common/data-source/types.ts | 4 + 12 files changed, 270 insertions(+), 164 deletions(-) diff --git a/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts b/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts index 96d81718c4..d44e3ef224 100644 --- a/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts +++ b/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts @@ -10,7 +10,34 @@ import { tParsedIndexPattern } from '../index'; import { IndexPatternsService, IndexPattern } from '../../../../../../../src/plugins/data/common'; +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({}), + }, + query: { + filterManager: { + getFilters: jest.fn().mockReturnValue([]), + setFilters: jest.fn(), + getUpdates$: jest.fn().mockReturnValue({ + subscribe: jest.fn() + }) + } + } + }), +})); const mockedGetFilters = jest.fn().mockReturnValue([]); @@ -67,7 +94,7 @@ describe('useDataSource hook', () => { }) - it('shoudl throw ERROR when the DataSource is not defined', () => { + it('should throw ERROR when the DataSource is not defined', () => { try { renderHook(() => useDataSource({ 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 be80ac276c..fc12f33d2a 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 @@ -9,12 +9,9 @@ import { PatternDataSourceFactory, PatternDataSource, tParsedIndexPattern, - PatternDataSourceFilterManager + PatternDataSourceFilterManager, + tFilterManager } from '../index'; -import { FilterManager } from "../../../../../../../src/plugins/data/public"; - -// create a new type using the FilterManager type but only the getFilters, setFilters, addFilters, getUpdates$ -type tFilterManager = Pick; type tUseDataSourceProps = { DataSource: IDataSourceFactoryConstructor; @@ -30,8 +27,9 @@ type tUseDataSourceLoadedReturns = { filters: tFilter[]; fetchFilters: tFilter[]; fixedFilters: tFilter[]; - fetchData: () => Promise; + fetchData: (params: Omit) => Promise; setFilters: (filters: tFilter[]) => void; + filterManager: PatternDataSourceFilterManager; } type tUseDataSourceNotLoadedReturns = { @@ -42,6 +40,7 @@ type tUseDataSourceNotLoadedReturns = { fixedFilters: []; fetchData: (params: Omit) => Promise; setFilters: (filters: tFilter[]) => void; + filterManager: null; } export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { @@ -93,6 +92,7 @@ export function useDataSource { - console.log('update filter manager'); setAllFilters(dataSourceFilterManager.getFilters()); setFetchFilters(dataSourceFilterManager.getFetchFilters()); }, @@ -109,7 +108,6 @@ export function useDataSource ({ + ...(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({}), + }, + }), +})); + const mockedItem: tParsedIndexPattern = { attributes: { @@ -20,7 +38,6 @@ const mockedItem: tParsedIndexPattern = { updated_at: '2021-08-23T14:05:54.000Z', version: 'WzIwMjEsM', _fields: [], - select: (): Promise => Promise.resolve() }; describe('PatternDataSourceFactory', () => { @@ -30,17 +47,37 @@ describe('PatternDataSourceFactory', () => { factory = new PatternDataSourceFactory(); }); - it('should create a single pattern data source', () => { - const createdItem = factory.create(mockedItem); - expect(createdItem).toBeInstanceOf(PatternDataSource); + afterEach(() => { + jest.restoreAllMocks(); + }) + + it('should create a single pattern data source', async () => { + // spy is init method PatternDataSource is called + jest.spyOn(PatternDataSource.prototype, 'init').mockResolvedValueOnce(); + const createdItem = await factory.create(PatternDataSource , mockedItem); + expect(createdItem.id).toBe(mockedItem.id); + expect(createdItem.title).toBe(mockedItem.title); }); - it('should create an array of pattern data sources', () => { - const items: PatternDataSource[] = [mockedItem]; - const createdItems = factory.createAll(items); + it('should create a instance of pattern data source', async () => { + const createdItem = await factory.create(PatternDataSource, mockedItem); + expect(createdItem).toBeInstanceOf(PatternDataSource); + expect(createdItem.id).toBe(mockedItem.id); + expect(createdItem.title).toBe(mockedItem.title); + }) + + it('should call the init method of pattern data source', async () => { + const initSpy = jest.spyOn(PatternDataSource.prototype, 'init'); + await factory.create(PatternDataSource, mockedItem); + expect(initSpy).toHaveBeenCalledTimes(1); + }) + + it('should create an array of pattern data sources', async () => { + const items: tParsedIndexPattern[] = [mockedItem, mockedItem, mockedItem]; + const createdItems = await factory.createAll(PatternDataSource, items); expect(createdItems).toBeInstanceOf(Array); expect(createdItems.length).toBe(items.length); - createdItems.forEach((createdItem, index) => { + createdItems.forEach((createdItem) => { expect(createdItem).toBeInstanceOf(PatternDataSource); }); }); diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts index 7935aba31d..05782e3918 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts @@ -1,33 +1,52 @@ -import { DataSourceFilterManager, tDataSource, tSearchParams, tFilter } from './index'; +import { + PatternDataSourceFilterManager, + tParsedIndexPattern, + tDataSource, + tSearchParams, + tFilter, + PatternDataSource } from '../index'; +import store from '../../../../redux/store'; +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT } from '../../../../../common/constants'; +import { IndexPatternsService, IndexPattern } from '../../../../../../../src/plugins/data/public'; -// mock the store -import store from '../../../redux/store'; -import { DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT } from '../../../../common/constants'; - -// mock the AppState -jest.mock('../../../redux/store', () => ({ +jest.mock('../../../../redux/store', () => ({ getState: jest.fn().mockReturnValue({ appStateReducers: { } }) })); -class DataSourceMocked implements tDataSource { - constructor(id: string, title: string) { +let mockedGetFilters = jest.fn().mockReturnValue([]); +class DataSourceMocked implements PatternDataSource { + constructor(public id: string, public title: string) { this.id = id; this.title = title; } - id: string; - title: string; + fields: any[]; + patternService: IndexPatternsService; + indexPattern: IndexPattern; + defaultFixedFilters: tFilter[]; + filters: tFilter[]; + init = jest.fn(); select = jest.fn(); - getFilters = jest.fn(); - setFilters = jest.fn(); - getFields = jest.fn(); - getFixedFilters = jest.fn(); fetch = jest.fn(); + getFilters = mockedGetFilters; + setFilters = jest.fn(); + getFields = mockedGetFilters + getFixedFilters = mockedGetFilters + getFetchFilters = mockedGetFilters + toJSON(): tParsedIndexPattern { + return { + id: this.id, + title: this.title, + } as tParsedIndexPattern; + } + getClusterManagerFilters = mockedGetFilters + getPinnedAgentFilter = mockedGetFilters + getExcludeManagerFilter = mockedGetFilters + getAllowAgentsFilter = mockedGetFilters } - const createFilter = (id: string, value: string, index: string) => { return { meta: { @@ -55,7 +74,7 @@ const createFilter = (id: string, value: string, index: string) => { } -describe('DataSourceFilterManager', () => { +describe('PatternDataSourceFilterManager', () => { beforeEach(() => { jest.clearAllMocks(); @@ -63,13 +82,13 @@ describe('DataSourceFilterManager', () => { describe('constructor', () => { it('should initialize the data source filter manager', () => { - const filterManager = new DataSourceFilterManager(new DataSourceMocked('my-id', 'my-title'), []); + const filterManager = new PatternDataSourceFilterManager(new DataSourceMocked('my-id', 'my-title'), []); expect(filterManager).toBeDefined(); }) it('should return ERROR when the data source is not defined', () => { try { - new DataSourceFilterManager(null as any, []); + new PatternDataSourceFilterManager(null as any, []); } catch (error) { expect(error.message).toBe('Data source is required'); } @@ -80,7 +99,7 @@ describe('DataSourceFilterManager', () => { const filterDifIndex = createFilter('agent.id', '1', 'different filter'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, [filterDifIndex]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [filterDifIndex]); expect(filterManager.getFilters()).not.toContainEqual(filterDifIndex); expect(filterManager.getFilters()).toContainEqual(fixedFilter); }) @@ -88,10 +107,10 @@ describe('DataSourceFilterManager', () => { it('should filter the filters received in the constructor then no keep the filters with meta.controlledBy property (with same index)', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const filterSameIndexControlled = createFilter('agent.id', '1', 'my-index'); - filterSameIndexControlled.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLER_PINNED_AGENT; + filterSameIndexControlled.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT; const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, [filterSameIndexControlled]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [filterSameIndexControlled]); expect(filterManager.getFilters()).not.toContainEqual(filterSameIndexControlled); expect(filterManager.getFilters()).toEqual([fixedFilter]); }) @@ -107,7 +126,7 @@ describe('DataSourceFilterManager', () => { } const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, [filterSameIndexControlled]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [filterSameIndexControlled]); expect(filterManager.getFilters()).not.toContainEqual(filterSameIndexControlled); expect(filterManager.getFilters()).toContainEqual(fixedFilter); }) @@ -118,7 +137,7 @@ describe('DataSourceFilterManager', () => { const sameIndexFilter = createFilter('agent.id', '1', 'my-index'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, [sameIndexFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [sameIndexFilter]); expect(filterManager.getFilters()).toEqual([sameIndexFilter, fixedFilter]); }); @@ -130,7 +149,7 @@ describe('DataSourceFilterManager', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, []); + const filterManager = new PatternDataSourceFilterManager(dataSource, []); const filters = filterManager.getFilters(); expect(filters).toEqual([fixedFilter]); }) @@ -140,7 +159,7 @@ describe('DataSourceFilterManager', () => { const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); const sameIndexFilter = createFilter('agent.id', '1', 'my-index'); - const filterManager = new DataSourceFilterManager(dataSource, [sameIndexFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [sameIndexFilter]); const filters = filterManager.getFilters(); expect(filters).toEqual([fixedFilter, sameIndexFilter]); }) @@ -150,7 +169,7 @@ describe('DataSourceFilterManager', () => { const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); const anotherIndexFilter = createFilter('agent.id', '1', 'another-index'); - const filterManager = new DataSourceFilterManager(dataSource, [anotherIndexFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [anotherIndexFilter]); const filters = filterManager.getFilters(); expect(filters).toEqual([fixedFilter]); expect(filters).not.toContainEqual(anotherIndexFilter); @@ -168,7 +187,7 @@ describe('DataSourceFilterManager', () => { } }); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, []); + const filterManager = new PatternDataSourceFilterManager(dataSource, []); const filters = filterManager.getFixedFilters(); expect(filters).toContainEqual(fixedFilter); expect(dataSource.getFixedFilters).toHaveBeenCalledTimes(1); @@ -187,7 +206,7 @@ describe('DataSourceFilterManager', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, []); + const filterManager = new PatternDataSourceFilterManager(dataSource, []); const filters = filterManager.getFixedFilters(); const agentPinnedFilter = filterManager.getPinnedAgentFilter(); expect(filters).toContainEqual(fixedFilter); @@ -204,7 +223,7 @@ describe('DataSourceFilterManager', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new DataSourceFilterManager(dataSource, []); + const filterManager = new PatternDataSourceFilterManager(dataSource, []); const filters = filterManager.getFixedFilters(); expect(filters).toContainEqual(fixedFilter); expect(filters).not.toContainEqual(filterManager.getPinnedAgentFilter()); @@ -217,7 +236,7 @@ describe('DataSourceFilterManager', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const storedFilter = createFilter('agent.id', '1', 'my-index'); - const filterManager = new DataSourceFilterManager(dataSource, []); + const filterManager = new PatternDataSourceFilterManager(dataSource, []); jest.spyOn(filterManager, 'getFilters').mockReturnValue([storedFilter]); }) @@ -232,7 +251,7 @@ describe('DataSourceFilterManager', () => { } } }); - const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); const excludeManager = filterManager.getExcludeManagerFilter(); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); const filters = filterManager.getFetchFilters(); @@ -249,7 +268,7 @@ describe('DataSourceFilterManager', () => { } } }); - const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); const filters = filterManager.getFetchFilters(); expect(filters).toEqual([storedFilter]); @@ -264,7 +283,7 @@ describe('DataSourceFilterManager', () => { allowedAgents: ['001'] } }); - const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); const allowedAgents = filterManager.getAllowAgentsFilter(); const filters = filterManager.getFetchFilters(); @@ -279,7 +298,7 @@ describe('DataSourceFilterManager', () => { appStateReducers: { } }); - const filterManager = new DataSourceFilterManager(dataSource, [storedFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); const filters = filterManager.getFetchFilters(); expect(filters).toEqual([storedFilter]); 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 index 351c909bd9..f21c512e0a 100644 --- 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 @@ -1,5 +1,5 @@ import { PatternDataSourceRepository, tSavedObjectResponse } from './pattern-data-source-repository'; -import { tDataSource } from '../data-source'; +import { PatternDataSource, tDataSource } from '../index'; import { GenericRequest } from '../../../../react-services/generic-request'; jest.mock('../../../../react-services/generic-request'); @@ -106,11 +106,11 @@ describe('PatternDataSourceRepository', () => { title: mockTitle, } } - }; + } as tSavedObjectResponse; const mockRequest = jest.fn().mockResolvedValue(expectedResponse); GenericRequest.request = mockRequest; - const result = await repository.get(mockId) as tDataSource; + const result = await repository.get(mockId); expect(result.id).toEqual(expectedResponse.data.id); expect(result.title).toEqual(expectedResponse.data.attributes.title); @@ -241,7 +241,8 @@ describe('PatternDataSourceRepository', () => { GenericRequest.request = mockRequest; const result = await repository.getDefault(); // mock the get method to return the current pattern - expect(result.id).toEqual('test-pattern-id'); + expect(result).toBeDefined(); + expect(result?.id).toEqual('test-pattern-id'); }); it('should return an ERROR when default index pattern is not saved in storage', async () => { 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 index 7c9e323ba7..d0ef7a6931 100644 --- 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 @@ -1,4 +1,4 @@ -import { tDataSourceRepository } from "../data-source-repository"; +import { tDataSourceRepository } from "../index"; import { GenericRequest } from '../../../../react-services/generic-request'; import { AppState } from '../../../../react-services'; @@ -38,7 +38,7 @@ export type tParsedIndexPattern = { updated_at: string; version: string; _fields: any[]; -} +} & object; export class PatternDataSourceRepository implements tDataSourceRepository{ async get(id: string): Promise { diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.test.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.test.ts index 3360119aeb..6e5f5599c6 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.test.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.test.ts @@ -1,42 +1,74 @@ -import { DataSourceSelector } from './data-source-selector'; -import { DataSourceRepository } from './data-source-repository'; -import { tDataSourceFactory } from './data-source-factory'; -import { tDataSource } from './data-source'; +import { IndexPatternsService, IndexPattern } from '../../../../../../../src/plugins/data/public'; +import { + tDataSourceSelector, + tDataSourceRepository, + tParsedIndexPattern, + PatternDataSourceSelector, + PatternDataSource, + tFilter +} from '../index'; + + +const mockedGetFilters = jest.fn().mockReturnValue([]); + +class DataSourceMocked implements PatternDataSource { + constructor(public id: string, public title: string) { + this.id = id; + this.title = title; + } + fields: any[]; + patternService: IndexPatternsService; + indexPattern: IndexPattern; + defaultFixedFilters: tFilter[]; + filters: tFilter[]; + init = jest.fn(); + select = jest.fn(); + fetch = jest.fn(); + getFilters = mockedGetFilters; + setFilters = jest.fn(); + getFields = mockedGetFilters + getFixedFilters = mockedGetFilters + getFetchFilters = mockedGetFilters + toJSON(): tParsedIndexPattern { + return { + id: this.id, + title: this.title, + } as tParsedIndexPattern; + } + getClusterManagerFilters = mockedGetFilters + getPinnedAgentFilter = mockedGetFilters + getExcludeManagerFilter = mockedGetFilters + getAllowAgentsFilter = mockedGetFilters +} -class ExampleRepository implements DataSourceRepository { +class ExampleRepository implements tDataSourceRepository { getDefault = jest.fn(); setDefault = jest.fn(); get = jest.fn(); getAll = jest.fn(); } -class ExampleFactory implements tDataSourceFactory { - create = jest.fn(); - createAll = jest.fn(); +const createListPatternsMocked = (qty: number) => { + const list: DataSourceMocked[] = []; + for (let i = 0; i < qty; i++) { + list.push(new DataSourceMocked(`id ${i.toString()}`, `title ${i}`)); + } + return list; } -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() }, -]; +let repository = new ExampleRepository(); +let mockedList = createListPatternsMocked(3); -describe('DataSourceSelector', () => { +describe('PatternDataSourceSelector', () => { - beforeEach(() => { - repository = new ExampleRepository(); - factory = new ExampleFactory(); - dataSourceSelector = new DataSourceSelector(repository, factory); - }); + afterEach(() => { + jest.clearAllMocks(); + }) describe('constructor', () => { it('should return ERROR when the selector not receive a repository', () => { try { - new DataSourceSelector(null as any, factory); + new PatternDataSourceSelector(mockedList, null as any); } catch (error) { expect(error.message).toBe('Data source repository is required'); } @@ -44,66 +76,56 @@ describe('DataSourceSelector', () => { 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); + new PatternDataSourceSelector([], new ExampleRepository()); } catch (error) { - expect(error.message).toBe('Invalid data source factory'); + expect(error.message).toBe('Data sources list is required'); } }) - }) 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'); + const selector = new PatternDataSourceSelector(mockedList, repository); + const result = await selector.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'); + const selector = new PatternDataSourceSelector(mockedList, repository); + const result = await selector.existsDataSource('fake-id'); expect(result).toBe(false); expect(repository.get).toHaveBeenCalledTimes(1); }); + + it('should throw ERROR when not receive an id', async () => { + jest.spyOn(repository, 'get').mockResolvedValue(null); + try { + let selector = new PatternDataSourceSelector(mockedList, repository); + await selector.existsDataSource(null as any); + }catch(error){ + expect(error.message).toBe('Error checking data source. ID is required'); + } + }); }) 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); + jest.spyOn(repository, 'get').mockResolvedValueOnce(null).mockResolvedValueOnce(true); + let selector = new PatternDataSourceSelector(mockedList,repository); + const result = await selector.getFirstValidDataSource(); + expect(result).toEqual(mockedList[1]); + expect(repository.get).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); + jest.spyOn(repository, 'get').mockResolvedValueOnce(null).mockResolvedValueOnce(null).mockResolvedValueOnce(null); + let selector = new PatternDataSourceSelector(mockedList,repository); try { - await dataSourceSelector.getFirstValidDataSource(); + await selector.getFirstValidDataSource(); } catch (error) { expect(error.message).toBe('No valid data sources found'); } @@ -112,47 +134,33 @@ describe('DataSourceSelector', () => { 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); + let selector = new PatternDataSourceSelector(mockedList,repository); + const result = await selector.getAllDataSources(); + expect(result).toEqual(mockedList); }); }) 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); + jest.spyOn(repository, 'getDefault').mockResolvedValue(mockedList[0]); + let selector = new PatternDataSourceSelector(mockedList,repository); + const result = await selector.getSelectedDataSource(); + expect(result.id).toEqual(mockedList[0].id); 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); + let selector = new PatternDataSourceSelector(mockedList,repository); // 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); + jest.spyOn(selector, 'existsDataSource').mockResolvedValueOnce(false).mockResolvedValueOnce(true); + jest.spyOn(selector, 'selectDataSource').mockResolvedValue(); + const result = await selector.getSelectedDataSource(); + expect(result.id).toEqual(mockedList[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); + expect(selector.existsDataSource).toHaveBeenCalledTimes(2); + expect(selector.selectDataSource).toHaveBeenCalledTimes(1); }) }) @@ -160,19 +168,18 @@ describe('DataSourceSelector', () => { 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'); + let selector = new PatternDataSourceSelector(mockedList,repository); + await selector.selectDataSource('id 1'); expect(repository.setDefault).toHaveBeenCalledTimes(1); - expect(repository.setDefault).toHaveBeenCalledWith(dataSourcesMocked[0]); + expect(repository.setDefault).toHaveBeenCalledWith({ id: 'id 1', title: 'title 1' }); }); 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'); + let selector = new PatternDataSourceSelector(mockedList,repository); + await selector.selectDataSource('fake id'); } catch (error) { expect(error.message).toBe('Data source not found'); } diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts index 1426141f1e..46ac60750e 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-selector.ts @@ -93,7 +93,7 @@ export class PatternDataSourceSelector implements tDataSourceSelector ({ })); describe('PatternDataSource', () => { - it('should create a new data source handler', () => { + it('should create a new data source instance', () => { const patternDataSource = new PatternDataSource('id', 'title'); - expect(patternDataSource).toEqual({ id: 'id', title: 'title' }); + expect(patternDataSource).toBeInstanceOf(PatternDataSource); }); it('should have the correct id', () => { 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 442c3bce88..e828c34fab 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 @@ -135,11 +135,16 @@ export class PatternDataSource implements tDataSource { : AppState.getClusterInfo().manager, true ); - managerFilter.meta.index = this.id; - managerFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER; + managerFilter.meta = { + ...managerFilter.meta, + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER, + index: this.id + } + //@ts-ignore managerFilter.$state = { store: 'appState' } + //@ts-ignore return [managerFilter] as tFilter[]; } @@ -148,7 +153,11 @@ export class PatternDataSource implements tDataSource { */ getPinnedAgentFilter(): tFilter[] { const agentId = store.getState().appStateReducers?.currentAgentData?.id; - if (!agentId) return []; + const url = window.location.href; + const regex = new RegExp('agentId=' + '[^&]*'); + const match = url.match(regex); + const isPinnedAgentByUrl = match && match[0]; + if (!agentId && !isPinnedAgentByUrl) return []; return [{ meta: { removable: false, // used to hide the close icon in the filter diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts index 387311e001..d565aa62a4 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts @@ -34,11 +34,13 @@ export class VulnerabilitiesDataSourceRepository extends PatternDataSourceReposi } getDefault(){ + console.warn('getDefault not implemented for vulnerabilities data source repository'); return null; } - setDefault(dataSource: tDataSource){ - return null; + setDefault(dataSource: tParsedIndexPattern){ + console.warn('setDefault not implemented for vulnerabilities data source repository'); + return Promise.resolve(); } } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/types.ts b/plugins/main/public/components/common/data-source/types.ts index 8ecd4c326f..0dd7592086 100644 --- a/plugins/main/public/components/common/data-source/types.ts +++ b/plugins/main/public/components/common/data-source/types.ts @@ -1,4 +1,5 @@ import { Filter } from "../../../../../../src/plugins/data/common"; +import { FilterManager } from "../../../../../../src/plugins/data/public"; export type tFilter = Filter & { meta?: { @@ -26,6 +27,9 @@ export type tSearchParams = { }; } +// create a new type using the FilterManager type but only the getFilters, setFilters, addFilters, getUpdates$ +export type tFilterManager = Pick; + export type tDataSource = { id: string; title: string; From 3c7c25981bc1e5807569cb5f39b03a642935e36f Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 27 Mar 2024 17:23:18 -0300 Subject: [PATCH 26/36] Update use search bar hook props --- .../common/search-bar/use-search-bar.ts | 110 ++++-------------- 1 file changed, 21 insertions(+), 89 deletions(-) diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index e5cf2b1999..df005b95bb 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -8,32 +8,18 @@ import { IIndexPattern, IndexPatternsContract, } from '../../../../../../src/plugins/data/public'; -import { getDataPlugin, getWazuhCorePlugin } from '../../../kibana-services'; +import { getDataPlugin } from '../../../kibana-services'; import { useFilterManager, useQueryManager, useTimeFilter } from '../hooks'; -import { - AUTHORIZED_AGENTS, - DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, -} from '../../../../common/constants'; // Input - types type tUseSearchBarCustomInputs = { - defaultIndexPatternID: IIndexPattern['id']; + indexPattern: IIndexPattern; + filterManager?: FilterManager; onFiltersUpdated?: (filters: Filter[]) => void; onQuerySubmitted?: ( payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean, ) => void; - onMount?: ( - filterManager: FilterManager, - defaultIndexPatternID: string, - ) => void; - onUpdate?: (filters: Filter[], filterManager: FilterManager) => void; - onUnMount?: ( - previousFilters: Filter[], - toIndexPattern: string | null, - filterManager: FilterManager, - defaultIndexPatternID: string, - ) => void; }; type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; @@ -48,59 +34,34 @@ type tUserSearchBarResponse = { * @returns */ const useSearchBarConfiguration = ( - props?: tUseSearchBarProps, + props: tUseSearchBarProps, ): tUserSearchBarResponse => { + const { indexPattern, filterManager: defaultFilterManager, filters: defaultFilters } = props; // dependencies - const SESSION_STORAGE_FILTERS_NAME = 'wazuh_persistent_searchbar_filters'; - const SESSION_STORAGE_PREV_FILTER_NAME = 'wazuh_persistent_current_filter'; - const filterManager = useFilterManager().filterManager as FilterManager; - const filters = props?.filters ? props.filters : filterManager.getFilters(); + const filterManager = defaultFilterManager ? defaultFilterManager : useFilterManager().filterManager as FilterManager; + const filters = defaultFilters ? defaultFilters : filterManager.getFilters(); const [query, setQuery] = props?.query ? useState(props?.query) : useQueryManager(); const { timeFilter, timeHistory, setTimeFilter } = useTimeFilter(); // states const [isLoading, setIsLoading] = useState(false); - const [indexPatternSelected, setIndexPatternSelected] = - useState(); + const [indexPatternSelected, setIndexPatternSelected] = useState(indexPattern); useEffect(() => { - initSearchBar(); - return () => { - /* Upon unmount, the previous filters are restored */ - const prevStoragePattern = sessionStorage.getItem( - SESSION_STORAGE_PREV_FILTER_NAME, - ); - sessionStorage.removeItem(SESSION_STORAGE_PREV_FILTER_NAME); - const storagePreviousFilters = sessionStorage.getItem( - SESSION_STORAGE_FILTERS_NAME, - ); - if (storagePreviousFilters) { - const previousFilters = JSON.parse(storagePreviousFilters); - if (props?.onUnMount) { - props.onUnMount( - previousFilters, - prevStoragePattern, - filterManager, - props?.defaultIndexPatternID, - ); - } - } - }; - }, []); + // default index pattern id is required + if (indexPattern) { + initSearchBar(); + } + }, [indexPattern]); /** * Initialize the searchbar props with the corresponding index pattern and filters */ const initSearchBar = async () => { setIsLoading(true); - const indexPattern = await getIndexPattern(props?.defaultIndexPatternID); - setIndexPatternSelected(indexPattern); - if (props?.onMount) { - props.onMount(filterManager, props?.defaultIndexPatternID); - } else { - filterManager.setFilters(filters); - } + const defaultIndexPattern = await getDefaultIndexPattern(); + setIndexPatternSelected(indexPattern || defaultIndexPattern); setIsLoading(false); }; @@ -109,20 +70,9 @@ const useSearchBarConfiguration = ( * If not receive a ID return the default index from the index pattern service * @returns */ - const getIndexPattern = async (indexPatternID?: string) => { - const indexPatternService = getDataPlugin() - .indexPatterns as IndexPatternsContract; - if (indexPatternID) { - try { - return await indexPatternService.get(indexPatternID); - } catch (error) { - // when the index pattern id not exists will get the default - console.error(error); - return await indexPatternService.getDefault(); - } - } else { - return await indexPatternService.getDefault(); - } + const getDefaultIndexPattern = async (): Promise => { + const indexPatternService = getDataPlugin().indexPatterns as IndexPatternsContract; + return await indexPatternService.getDefault(); }; /** @@ -131,32 +81,14 @@ const useSearchBarConfiguration = ( const searchBarProps: Partial = { isLoading, ...(indexPatternSelected && { indexPatterns: [indexPatternSelected] }), // indexPattern cannot be empty or empty [] - filters: filters - .filter( - (filter: Filter) => - ![ - AUTHORIZED_AGENTS, - DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, - ].includes(filter?.meta?.controlledBy), // remove auto loaded agent.id filters - ) - .sort((a: Filter, b: Filter) => { - return a?.$state?.isImplicit && !(a?.meta?.key === 'agent.id') - ? -1 - : b?.$state?.isImplicit - ? 1 - : -1; - }), + filters: filters, query, timeHistory, dateRangeFrom: timeFilter.from, dateRangeTo: timeFilter.to, onFiltersUpdated: (filters: Filter[]) => { - if (props?.onUpdate) { - props.onUpdate(filters, filterManager, props?.onFiltersUpdated); - } else { - filterManager.setFilters(filters); - props?.onFiltersUpdated && props?.onFiltersUpdated(filters); - } + filterManager.setFilters(filters); + props?.onFiltersUpdated && props?.onFiltersUpdated(filters); }, onQuerySubmit: ( payload: { dateRange: TimeRange; query?: Query }, From 4a959f45023c59e25390ce6178fcf527af4da2ac Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 27 Mar 2024 17:23:33 -0300 Subject: [PATCH 27/36] Apply hook on vuls tabs --- .../common/wazuh-discover/wz-discover.tsx | 14 ++--- .../dashboards/inventory/inventory.tsx | 33 +++------- .../dashboards/overview/dashboard.tsx | 63 ++++++++++--------- 3 files changed, 44 insertions(+), 66 deletions(-) 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 37c032ad8e..aef7605322 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -17,7 +17,6 @@ import { } from '@elastic/eui'; import { IntlProvider } from 'react-intl'; import { - Filter, IndexPattern, } from '../../../../../../src/plugins/data/common'; import { SearchResponse } from '../../../../../../src/core/server'; @@ -75,7 +74,6 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const { dataSource, fetchFilters, - setFilters, isLoading: isDataSourceLoading, fetchData, } = useDataSource({ @@ -107,10 +105,9 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { }; const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: dataSource?.id, + indexPattern: dataSource?.indexPattern as IndexPattern, }); const { - isLoading, query, dateRangeFrom, dateRangeTo, @@ -137,7 +134,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { }); useEffect(() => { - if (isLoading || isDataSourceLoading) { + if (isDataSourceLoading) { return; } setIndexPattern(dataSource?.indexPattern); @@ -148,7 +145,6 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { dateRange: { from: dateRangeFrom || '', to: dateRangeTo || '' }, }) .then(results => { - console.log('results', results); setResults(results); }) .catch(error => { @@ -206,7 +202,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { grow > <> - {isLoading || isDataSourceLoading ? ( + {isDataSourceLoading ? ( ) : ( { showSaveQuery={true} /> )} - {!isLoading && results?.hits?.total === 0 ? ( + {!isDataSourceLoading && results?.hits?.total === 0 ? ( ) : null} - {!isLoading && !isDataSourceLoading && results?.hits?.total > 0 ? ( + {!isDataSourceLoading && dataSource && results?.hits?.total > 0 ? ( <> { - const appConfig = useAppConfig(); - const VULNERABILITIES_INDEX_PATTERN_ID = - appConfig.data['vulnerabilities.pattern']; - const filterManager = useFilterManager().filterManager as FilterManager; const { dataSource, - filters: defaultFilters, fetchFilters, - setFilters, isLoading: isDataSourceLoading, fetchData, } = useDataSource({ DataSource: VulnerabilitiesDataSource, repository: new VulnerabilitiesDataSourceRepository() }); - const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, + indexPattern: dataSource?.indexPattern as IndexPattern, }); - const { isLoading, query } = searchBarProps; + const { query } = searchBarProps; const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); @@ -155,14 +138,12 @@ const InventoryVulsComponent = () => { }; useEffect(() => { - if (isLoading || isDataSourceLoading) { + if (isDataSourceLoading) { return; } setIndexPattern(dataSource?.indexPattern); fetchData({ query, pagination, sorting }) .then(results => { - console.log('results', results); - console.log('filters', fetchFilters); setResults(results); }) .catch(error => { @@ -189,7 +170,7 @@ const InventoryVulsComponent = () => { grow > <> - {isLoading || isDataSourceLoading ? ( + {isDataSourceLoading ? ( ) : ( { showQueryBar={true} /> )} - {!isLoading && results?.hits?.total === 0 ? ( + {!isDataSourceLoading && results?.hits?.total === 0 ? ( ) : null} - {!isLoading && results?.hits?.total > 0 ? ( + {!isDataSourceLoading && results?.hits?.total > 0 ? ( { - const appConfig = useAppConfig(); - const VULNERABILITIES_INDEX_PATTERN_ID = - appConfig.data['vulnerabilities.pattern']; - const filterManager = useFilterManager().filterManager as FilterManager; const { dataSource, - filters: defaultFilters, fetchFilters, - setFilters, isLoading: isDataSourceLoading, fetchData, } = useDataSource({ @@ -57,15 +48,15 @@ const DashboardVulsComponent: React.FC = () => { repository: new VulnerabilitiesDataSourceRepository(), }); + const [results, setResults] = useState({} as SearchResponse); + const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, + indexPattern: dataSource?.indexPattern as IndexPattern, }); - - const { isLoading, query, filters: searchBarFilters } = searchBarProps; - const [results, setResults] = useState({} as SearchResponse); + const { query } = searchBarProps; useEffect(() => { - if (isLoading || isDataSourceLoading) { + if (isDataSourceLoading) { return; } fetchData({ query }).then(results => { @@ -89,28 +80,26 @@ const DashboardVulsComponent: React.FC = () => { <> { - isLoading || isDataSourceLoading ? - : - + isDataSourceLoading && !dataSource ? + : + } - {!isLoading && results?.hits?.total === 0 ? ( + {dataSource && results?.hits?.total === 0 ? ( ) : null} - {!isLoading && results?.hits?.total > 0 ? ( + {dataSource && results?.hits?.total > 0 ? (
{ }, hidePanelTitles: true, }} + + onInputUpdated={(value) => { + console.log('onInputUpdated', value); + }} />
{ }, hidePanelTitles: true, }} + + onInputUpdated={(value) => { + console.log('onInputUpdated', value); + }} /> { }, hidePanelTitles: false, }} + + onInputUpdated={(value) => { + console.log('onInputUpdated', value); + }} />
) : null} From 750cb88cd25a11fa283fd44c821bc12a5fc997eb Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Sun, 31 Mar 2024 17:20:49 -0300 Subject: [PATCH 28/36] Applied use data source in tabs and added hide remove filter button in search bar --- .../common/data-source/components/index.ts | 1 - .../wz-data-source-selector.tsx | 4 -- .../pattern/pattern-data-source.test.ts | 47 ------------------- .../common/search-bar/search-bar-styles.scss | 7 +++ .../common/wazuh-discover/wz-discover.tsx | 16 +++++-- .../dashboards/inventory/inventory.tsx | 20 +++++--- .../dashboards/overview/dashboard.tsx | 32 +++++-------- 7 files changed, 44 insertions(+), 83 deletions(-) delete mode 100644 plugins/main/public/components/common/data-source/components/index.ts delete mode 100644 plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts create mode 100644 plugins/main/public/components/common/search-bar/search-bar-styles.scss diff --git a/plugins/main/public/components/common/data-source/components/index.ts b/plugins/main/public/components/common/data-source/components/index.ts deleted file mode 100644 index 2557ca05a1..0000000000 --- a/plugins/main/public/components/common/data-source/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export WzDataSourceSelector from './wz-data-source-selector/wz-data-source-selector'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx b/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx index fe044faa27..e5f3f18d97 100644 --- a/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx +++ b/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx @@ -6,12 +6,10 @@ import { } from '@elastic/eui'; import { tDataSource, - tDataSourceFactory, tDataSourceSelector, PatternDataSourceFactory, PatternDataSource, AlertsDataSourceRepository, - tDataSourceRepository, tParsedIndexPattern } from '../../index'; import { @@ -23,8 +21,6 @@ import { PatternDataSourceSelector, } from '../../pattern/pattern-data-source-selector'; - - type tWzDataSourceSelector = { name: 'string'; onChange?: (dataSource: T) => void; 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 deleted file mode 100644 index aaef71e324..0000000000 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -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 instance', () => { - const patternDataSource = new PatternDataSource('id', 'title'); - expect(patternDataSource).toBeInstanceOf(PatternDataSource); - }); - - 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/search-bar/search-bar-styles.scss b/plugins/main/public/components/common/search-bar/search-bar-styles.scss new file mode 100644 index 0000000000..51456c9619 --- /dev/null +++ b/plugins/main/public/components/common/search-bar/search-bar-styles.scss @@ -0,0 +1,7 @@ +.wz-search-bar #GlobalFilterGroup .globalFilterBar__flexItem { + .globalFilterItem-isPinned button[aria-label="Delete"] { + // all the button close with the aria-label="Delete" will be hidden + display: none; + } + +} \ No newline at end of file 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 aef7605322..3a35724454 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -73,9 +73,11 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const { dataSource, + filters, fetchFilters, isLoading: isDataSourceLoading, fetchData, + setFilters, } = useDataSource({ repository: new AlertsDataSourceRepository(), // this makes only works with alerts index pattern DataSource @@ -106,6 +108,8 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const { searchBarProps } = useSearchBar({ indexPattern: dataSource?.indexPattern as IndexPattern, + filters, + setFilters }); const { query, @@ -205,11 +209,13 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { {isDataSourceLoading ? ( ) : ( - +
+ +
)} {!isDataSourceLoading && results?.hits?.total === 0 ? ( diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index da3e9aa5bf..b97d7c33db 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -49,15 +49,19 @@ import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; const InventoryVulsComponent = () => { const { dataSource, + filters, fetchFilters, isLoading: isDataSourceLoading, fetchData, + setFilters, } = useDataSource({ DataSource: VulnerabilitiesDataSource, repository: new VulnerabilitiesDataSourceRepository() }); const { searchBarProps } = useSearchBar({ indexPattern: dataSource?.indexPattern as IndexPattern, + filters, + setFilters }); const { query } = searchBarProps; @@ -173,13 +177,15 @@ const InventoryVulsComponent = () => { {isDataSourceLoading ? ( ) : ( - +
+ +
)} {!isDataSourceLoading && results?.hits?.total === 0 ? ( diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index 80247a011c..8993003001 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -39,10 +39,12 @@ the rest of the visualizations have different configurations at the dashboard le const DashboardVulsComponent: React.FC = () => { const { + filters, dataSource, fetchFilters, isLoading: isDataSourceLoading, fetchData, + setFilters } = useDataSource({ DataSource: VulnerabilitiesDataSource, repository: new VulnerabilitiesDataSourceRepository(), @@ -52,6 +54,8 @@ const DashboardVulsComponent: React.FC = () => { const { searchBarProps } = useSearchBar({ indexPattern: dataSource?.indexPattern as IndexPattern, + filters, + setFilters, }); const { query } = searchBarProps; @@ -82,13 +86,15 @@ const DashboardVulsComponent: React.FC = () => { { isDataSourceLoading && !dataSource ? : - +
+ +
} {dataSource && results?.hits?.total === 0 ? ( @@ -118,10 +124,6 @@ const DashboardVulsComponent: React.FC = () => { }, hidePanelTitles: true, }} - - onInputUpdated={(value) => { - console.log('onInputUpdated', value); - }} />
{ }, hidePanelTitles: true, }} - - onInputUpdated={(value) => { - console.log('onInputUpdated', value); - }} /> { }, hidePanelTitles: false, }} - - onInputUpdated={(value) => { - console.log('onInputUpdated', value); - }} />
) : null} From 55687f2487173da8b76caf7b51fb8cc06ae19678 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Sun, 31 Mar 2024 17:21:25 -0300 Subject: [PATCH 29/36] Remove hide close filter button via js --- plugins/main/public/kibana-integrations/kibana-discover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/main/public/kibana-integrations/kibana-discover.js b/plugins/main/public/kibana-integrations/kibana-discover.js index ab41ef5b8a..64aa9ca099 100644 --- a/plugins/main/public/kibana-integrations/kibana-discover.js +++ b/plugins/main/public/kibana-integrations/kibana-discover.js @@ -1127,7 +1127,7 @@ function discoverController( $scope.$watch('fetchStatus', () => { if ($scope.fetchStatus !== fetchStatuses.UNINITIALIZED) { setTimeout(() => { - modulesHelper.hideCloseButtons(); + //modulesHelper.hideCloseButtons(); }, 100); } }); From 691b0d0236808ddf3ce09b69b74daaf4edda96f1 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Sun, 31 Mar 2024 17:21:41 -0300 Subject: [PATCH 30/36] Add new controllerBy value to pinned agent --- .../public/components/wz-agent-selector/wz-agent-selector.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js index 6a5dbef912..0862552a9a 100644 --- a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js +++ b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js @@ -29,6 +29,7 @@ import { getWazuhCorePlugin, } from '../../kibana-services'; import { getServices } from '../../kibana-integrations/discover/kibana_services'; +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT } from '../../../common/constants'; class WzAgentSelector extends Component { constructor(props) { @@ -90,6 +91,7 @@ class WzAgentSelector extends Component { params: { query: agentIdList[0] }, type: 'phrase', index: currentPattern, + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT }, query: { match: { @@ -99,7 +101,7 @@ class WzAgentSelector extends Component { }, }, }, - $state: { store: 'appState', isImplicit: true }, + $state: { store: 'globalState' }, }; agentFilters.push(filter); filterManager.setFilters(agentFilters); From c36af3ffd111c949b14948b23ef57b859ccc7154 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Sun, 31 Mar 2024 17:22:00 -0300 Subject: [PATCH 31/36] Adapt use-search-bar to data source --- .../common/search-bar/use-search-bar.test.ts | 116 +++++++----------- .../common/search-bar/use-search-bar.ts | 39 +++--- 2 files changed, 66 insertions(+), 89 deletions(-) diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts index 23329515ad..6b0fc60ab1 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts @@ -6,7 +6,6 @@ import { dataPluginMock, } from '../../../../../../src/plugins/data/public/mocks'; import { - Filter, IndexPattern, Query, TimeRange, @@ -87,6 +86,7 @@ describe('[hook] useSearchBarConfiguration', () => { jest .spyOn(mockDataPlugin.query.filterManager, 'getFilters') .mockReturnValue([]); + // @ts-ignore const { result, waitForNextUpdate } = renderHook(() => useSearchBar({})); await waitForNextUpdate(); expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled(); @@ -111,94 +111,48 @@ describe('[hook] useSearchBarConfiguration', () => { .mockResolvedValue(mockedIndexPatternData); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ - defaultIndexPatternID: 'wazuh-index-pattern', + indexPattern: mockedIndexPatternData as IndexPattern, + setFilters: jest.fn(), }), ); - await waitForNextUpdate(); - expect(mockDataPlugin.indexPatterns.get).toBeCalledWith( - exampleIndexPatternId, - ); expect(result.current.searchBarProps.indexPatterns).toMatchObject([ mockedIndexPatternData, ]); }); - it('should show an ERROR message and get the default app index pattern when not found the index pattern data by the ID received', async () => { - const INDEX_NOT_FOUND_ERROR = new Error('Index Pattern not found'); + it('should return empty filters when NOT equal a default filters', async () => { + const exampleIndexPatternId = 'wazuh-index-pattern'; + const mockedExampleIndexPatternData: Partial = { + // used partial not avoid fill all the interface, it's only for testing purpose + id: exampleIndexPatternId, + title: '', + }; jest .spyOn(AppState, 'getCurrentPattern') - .mockImplementation(() => 'default-index-pattern'); + .mockImplementation(() => exampleIndexPatternId); jest.spyOn(AppState, 'setCurrentPattern').mockImplementation(jest.fn()); - jest.spyOn(mockDataPlugin.indexPatterns, 'get').mockImplementation(() => { - throw INDEX_NOT_FOUND_ERROR; - }); + jest + .spyOn(mockDataPlugin.indexPatterns, 'get') + .mockResolvedValue(mockedExampleIndexPatternData); jest .spyOn(mockDataPlugin.indexPatterns, 'getDefault') .mockResolvedValue(mockedDefaultIndexPatternData); jest .spyOn(mockDataPlugin.query.filterManager, 'getFilters') .mockReturnValue([]); - - // mocking console error to avoid logs in test and check if is called - const mockedConsoleError = jest - .spyOn(console, 'error') - .mockImplementationOnce(() => {}); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ - defaultIndexPatternID: 'invalid-index-pattern-id', + indexPattern: mockedExampleIndexPatternData as IndexPattern, + setFilters: jest.fn() }), ); - - await waitForNextUpdate(); - expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled(); - expect(mockDataPlugin.indexPatterns.get).toBeCalledWith( - 'invalid-index-pattern-id', - ); expect(result.current.searchBarProps.indexPatterns).toMatchObject([ - mockedDefaultIndexPatternData, + mockedExampleIndexPatternData, ]); - expect(mockedConsoleError).toBeCalledWith(INDEX_NOT_FOUND_ERROR); - }); - - it('should return the same filters and apply them to the filter manager when are received by props', async () => { - const exampleIndexPatternId = 'wazuh-index-pattern'; - const defaultFilters: Filter[] = [ - { - query: 'something to filter', - meta: { - alias: 'filter-mocked', - disabled: false, - negate: true, - }, - }, - ]; - jest - .spyOn(AppState, 'getCurrentPattern') - .mockImplementation(() => 'default-index-pattern'); - jest.spyOn(AppState, 'setCurrentPattern').mockImplementation(jest.fn()); - jest - .spyOn(mockDataPlugin.indexPatterns, 'getDefault') - .mockResolvedValue(mockedDefaultIndexPatternData); - jest - .spyOn(mockDataPlugin.query.filterManager, 'getFilters') - .mockReturnValue(defaultFilters); - const { result, waitForNextUpdate } = renderHook(() => - useSearchBar({ - defaultIndexPatternID: exampleIndexPatternId, - filters: defaultFilters, - }), - ); - - await waitForNextUpdate(); - - expect(result.current.searchBarProps.filters).toMatchObject(defaultFilters); - expect(mockDataPlugin.query.filterManager.setFilters).toBeCalledWith( - defaultFilters, - ); - expect(mockDataPlugin.query.filterManager.getFilters).toBeCalled(); + expect(result.current.searchBarProps.filters).toStrictEqual([]); }); - it('should return empty filters when the index pattern is NOT equal to the default app index pattern', async () => { + it('should reload the index pattern when the index pattern changes', async () => { const exampleIndexPatternId = 'wazuh-index-pattern'; const mockedExampleIndexPatternData: Partial = { // used partial not avoid fill all the interface, it's only for testing purpose @@ -218,15 +172,33 @@ describe('[hook] useSearchBarConfiguration', () => { jest .spyOn(mockDataPlugin.query.filterManager, 'getFilters') .mockReturnValue([]); - const { result, waitForNextUpdate } = renderHook(() => - useSearchBar({ - defaultIndexPatternID: exampleIndexPatternId, - }), + const { result, waitForNextUpdate, rerender } = renderHook( + // @ts-ignore + (props) => useSearchBar(props), + { + initialProps: { + indexPattern: mockedExampleIndexPatternData as IndexPattern, + setFilters: jest.fn() + }, + }, ); - await waitForNextUpdate(); expect(result.current.searchBarProps.indexPatterns).toMatchObject([ mockedExampleIndexPatternData, ]); - expect(result.current.searchBarProps.filters).toStrictEqual([]); - }); + const newExampleIndexPatternData: Partial = { + // used partial not avoid fill all the interface, it's only for testing purpose + id: 'new-wazuh-index-pattern', + title: '', + }; + jest + .spyOn(mockDataPlugin.indexPatterns, 'get') + .mockResolvedValue(newExampleIndexPatternData); + rerender({ + indexPattern: newExampleIndexPatternData as IndexPattern, + setFilters: jest.fn() + }); + expect(result.current.searchBarProps.indexPatterns).toMatchObject([ + newExampleIndexPatternData, + ]); + }) }); diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index df005b95bb..54346fb118 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -1,20 +1,19 @@ import { useEffect, useState } from 'react'; import { SearchBarProps, - FilterManager, TimeRange, Query, Filter, - IIndexPattern, + IndexPattern, IndexPatternsContract, } from '../../../../../../src/plugins/data/public'; import { getDataPlugin } from '../../../kibana-services'; -import { useFilterManager, useQueryManager, useTimeFilter } from '../hooks'; - +import { useQueryManager, useTimeFilter } from '../hooks'; +import './search-bar-styles.scss'; // Input - types type tUseSearchBarCustomInputs = { - indexPattern: IIndexPattern; - filterManager?: FilterManager; + indexPattern: IndexPattern; + setFilters: (filters: Filter[]) => void; onFiltersUpdated?: (filters: Filter[]) => void; onQuerySubmitted?: ( payload: { dateRange: TimeRange; query?: Query }, @@ -25,7 +24,7 @@ type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; // Output types type tUserSearchBarResponse = { - searchBarProps: Partial; + searchBarProps: Partial }; /** @@ -36,32 +35,38 @@ type tUserSearchBarResponse = { const useSearchBarConfiguration = ( props: tUseSearchBarProps, ): tUserSearchBarResponse => { - const { indexPattern, filterManager: defaultFilterManager, filters: defaultFilters } = props; + const { indexPattern, filters: defaultFilters, setFilters } = props; // dependencies - const filterManager = defaultFilterManager ? defaultFilterManager : useFilterManager().filterManager as FilterManager; - const filters = defaultFilters ? defaultFilters : filterManager.getFilters(); + const filters = defaultFilters ? defaultFilters : []; const [query, setQuery] = props?.query ? useState(props?.query) : useQueryManager(); const { timeFilter, timeHistory, setTimeFilter } = useTimeFilter(); // states const [isLoading, setIsLoading] = useState(false); - const [indexPatternSelected, setIndexPatternSelected] = useState(indexPattern); + const [indexPatternSelected, setIndexPatternSelected] = useState(indexPattern); useEffect(() => { - // default index pattern id is required if (indexPattern) { - initSearchBar(); + setIndexPatternSelected(indexPattern); } }, [indexPattern]); + useEffect(() => { + initSearchBar(); + }, []); + /** * Initialize the searchbar props with the corresponding index pattern and filters */ const initSearchBar = async () => { setIsLoading(true); - const defaultIndexPattern = await getDefaultIndexPattern(); - setIndexPatternSelected(indexPattern || defaultIndexPattern); + if(!indexPattern) { + const defaultIndexPattern = await getDefaultIndexPattern(); + setIndexPatternSelected(defaultIndexPattern); + }else{ + setIndexPatternSelected(indexPattern); + } setIsLoading(false); }; @@ -78,7 +83,7 @@ const useSearchBarConfiguration = ( /** * Search bar properties necessary to render and initialize the osd search bar component */ - const searchBarProps: Partial = { + const searchBarProps: Partial = { isLoading, ...(indexPatternSelected && { indexPatterns: [indexPatternSelected] }), // indexPattern cannot be empty or empty [] filters: filters, @@ -87,7 +92,7 @@ const useSearchBarConfiguration = ( dateRangeFrom: timeFilter.from, dateRangeTo: timeFilter.to, onFiltersUpdated: (filters: Filter[]) => { - filterManager.setFilters(filters); + setFilters ? setFilters(filters) : console.warn('setFilters function is not defined'); props?.onFiltersUpdated && props?.onFiltersUpdated(filters); }, onQuerySubmit: ( From e5c9c2fa490615bb4b6493e04b4d3c08c68e9e19 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Sun, 31 Mar 2024 17:22:10 -0300 Subject: [PATCH 32/36] Fix unit tests --- plugins/main/common/constants.ts | 4 +- .../data-source/hooks/use-data-source.test.ts | 1 - .../data-source/hooks/use-data-source.ts | 2 + .../pattern/alerts/alerts-data-source.ts | 55 ++++ .../alerts-vulnerabilities-data-source.ts | 37 +-- .../data-source/pattern/alerts/index.ts | 3 +- ...pattern-data-source-filter-manager.test.ts | 253 +++++++++++------- .../pattern-data-source-filter-manager.ts | 223 +++++++++++++-- .../pattern/pattern-data-source.ts | 110 +------- .../vulnerabilities-data-source.ts | 26 +- .../components/common/data-source/types.ts | 14 +- 11 files changed, 451 insertions(+), 277 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index ab80158608..673ec7043f 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -223,8 +223,8 @@ export enum WAZUH_MENU_SETTINGS_SECTIONS_ID { ABOUT = 'about', } -export const AUTHORIZED_AGENTS = 'authorized-agents'; -export const DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER = 'exclude-server'; +export const AUTHORIZED_AGENTS = 'hidden-authorized-agents'; +export const DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER = 'hidden-exclude-server'; export const DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT = 'pinned-agent'; export const DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER = 'cluster-manager'; export const DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP = 'vulnerabilities-rule-group'; diff --git a/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts b/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts index d44e3ef224..8e8e6adbe3 100644 --- a/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts +++ b/plugins/main/public/components/common/data-source/hooks/use-data-source.test.ts @@ -1,5 +1,4 @@ import { useDataSource } from './use-data-source'; -// react testing library to test hooks import { renderHook } from '@testing-library/react-hooks'; import { tDataSource, 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 fc12f33d2a..a4a716b1b7 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 @@ -101,6 +101,8 @@ export function useDataSource { + // 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()); }, diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts new file mode 100644 index 0000000000..e52359e12f --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts @@ -0,0 +1,55 @@ +import { PatternDataSource } from '../pattern-data-source'; +import { tFilter, PatternDataSourceFilterManager } from '../../index'; +import { DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER } from '../../../../../../common/constants'; + +export class AlertsDataSource extends PatternDataSource { + constructor(id: string, title: string) { + super(id, title); + } + + getFixedFilters(): tFilter[] { + return [ + ...this.getClusterManagerFilters(), + ...super.getFixedFilters(), + ] + } + + getRuleGroupsFilter(key: string, value: string, controlledByValue: string) { + if (!key || !value){ + console.warn('key or value is missing to create the getRuleGroupsFilter') + return []; + } + return [{ + meta: { + index: this.id, + negate: false, + disabled: false, + alias: null, + type: 'phrase', + key: key, + value: value, + params: { + query: value, + type: 'phrase', + }, + controlledBy: controlledByValue + }, + query: { + match: { + [key]: { + query: value, + type: 'phrase', + } + }, + }, + $state: { + store: 'globalState', + }, + } as tFilter]; + } + + getClusterManagerFilters() { + return PatternDataSourceFilterManager.getClusterManagerFilters(this.title, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER); + } + +} \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts index b1b0b11fee..230e7dd4a7 100644 --- a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-vulnerabilities/alerts-vulnerabilities-data-source.ts @@ -1,51 +1,24 @@ import { tFilter } from "../../../index"; -import { PatternDataSource } from '../../pattern-data-source'; import { DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP } from '../../../../../../../common/constants'; - +import { AlertsDataSource } from "../alerts-data-source"; const VULNERABILITIES_GROUP_KEY = 'rule.groups'; const VULNERABILITIES_GROUP_VALUE = 'vulnerability-detector'; -export class AlertsVulnerabilitiesDataSource extends PatternDataSource { +export class AlertsVulnerabilitiesDataSource extends AlertsDataSource { constructor(id: string, title: string) { super(id, title); } + getRuleGroupsFilter() { - return [{ - meta: { - removable: false, // used to hide the close icon in the filter - index: this.id, - negate: false, - disabled: false, - alias: null, - type: 'phrase', - key: VULNERABILITIES_GROUP_KEY, - value: VULNERABILITIES_GROUP_VALUE, - params: { - query: VULNERABILITIES_GROUP_VALUE, - type: 'phrase', - }, - controlledBy: DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP - }, - query: { - match: { - [VULNERABILITIES_GROUP_KEY]: { - query: VULNERABILITIES_GROUP_VALUE, - type: 'phrase', - } - }, - }, - $state: { - store: 'appState', - }, - } as tFilter]; + return super.getRuleGroupsFilter(VULNERABILITIES_GROUP_KEY, VULNERABILITIES_GROUP_VALUE, DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP); } getFixedFilters(): tFilter[] { return [ + ...this.getRuleGroupsFilter(), ...super.getFixedFilters(), - ...this.getRuleGroupsFilter() ] } } \ 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 index 711b39a7b0..58def960bf 100644 --- a/plugins/main/public/components/common/data-source/pattern/alerts/index.ts +++ b/plugins/main/public/components/common/data-source/pattern/alerts/index.ts @@ -1,2 +1,3 @@ export * from './alerts-vulnerabilities'; -export * from './alerts-data-source-repository'; \ No newline at end of file +export * from './alerts-data-source-repository'; +export * from './alerts-data-source'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts index 05782e3918..16a3f9f635 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.test.ts @@ -1,19 +1,47 @@ -import { - PatternDataSourceFilterManager, +import { + PatternDataSourceFilterManager, tParsedIndexPattern, - tDataSource, - tSearchParams, - tFilter, - PatternDataSource } from '../index'; + tFilter, + tFilterManager, + PatternDataSource +} from '../index'; import store from '../../../../redux/store'; -import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT } from '../../../../../common/constants'; +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT, DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, AUTHORIZED_AGENTS } from '../../../../../common/constants'; import { IndexPatternsService, IndexPattern } from '../../../../../../../src/plugins/data/public'; +import { getDataPlugin } from '../../../../kibana-services'; jest.mock('../../../../redux/store', () => ({ getState: jest.fn().mockReturnValue({ appStateReducers: { } - }) + }), +})); + +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({}), + }, + query: { + filterManager: { + getFilters: jest.fn().mockReturnValue([]), + setFilters: jest.fn(), + getUpdates$: jest.fn().mockReturnValue({ + subscribe: jest.fn() + }) + } + } + }), })); let mockedGetFilters = jest.fn().mockReturnValue([]); @@ -47,7 +75,7 @@ class DataSourceMocked implements PatternDataSource { getAllowAgentsFilter = mockedGetFilters } -const createFilter = (id: string, value: string, index: string) => { +const createFilter = (id: string, value: string, index: string): tFilter => { return { meta: { index: index, @@ -73,11 +101,22 @@ const createFilter = (id: string, value: string, index: string) => { } as tFilter; } +// mocked tFilterManager + +const mockedFilterManager: tFilterManager = { + getFilters: jest.fn().mockReturnValue([]), + setFilters: jest.fn(), + addFilters: jest.fn(), + getUpdates$: jest.fn(), +} + describe('PatternDataSourceFilterManager', () => { beforeEach(() => { jest.clearAllMocks(); + mockedGetFilters.mockClear(); + }) describe('constructor', () => { @@ -94,14 +133,21 @@ describe('PatternDataSourceFilterManager', () => { } }) + it('should use the received filter manager instead the global filter manager (getDataPlugin) when receive an filter manager', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + new PatternDataSourceFilterManager(dataSource, [], mockedFilterManager); + expect(mockedFilterManager.getFilters).toHaveBeenCalledTimes(1); + expect(getDataPlugin().query.filterManager.getFilters).not.toHaveBeenCalled(); + }) + it('should filter the filters received in the constructor then no keep the filters with different index and merge with the fixed filters', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const filterDifIndex = createFilter('agent.id', '1', 'different filter'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, [filterDifIndex]); - expect(filterManager.getFilters()).not.toContainEqual(filterDifIndex); - expect(filterManager.getFilters()).toContainEqual(fixedFilter); + new PatternDataSourceFilterManager(dataSource, [filterDifIndex], mockedFilterManager); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(1); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter]); }) it('should filter the filters received in the constructor then no keep the filters with meta.controlledBy property (with same index)', () => { @@ -110,9 +156,9 @@ describe('PatternDataSourceFilterManager', () => { filterSameIndexControlled.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT; const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, [filterSameIndexControlled]); - expect(filterManager.getFilters()).not.toContainEqual(filterSameIndexControlled); - expect(filterManager.getFilters()).toEqual([fixedFilter]); + new PatternDataSourceFilterManager(dataSource, [filterSameIndexControlled], mockedFilterManager); + expect(mockedFilterManager.setFilters).not.toHaveBeenCalledWith([filterSameIndexControlled]); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter]); }) it('should filter the filters received in the constructor then no keep the filters with $state.isImplicit property (with same index)', () => { @@ -126,42 +172,43 @@ describe('PatternDataSourceFilterManager', () => { } const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, [filterSameIndexControlled]); - expect(filterManager.getFilters()).not.toContainEqual(filterSameIndexControlled); - expect(filterManager.getFilters()).toContainEqual(fixedFilter); + new PatternDataSourceFilterManager(dataSource, [filterSameIndexControlled], mockedFilterManager); + expect(mockedFilterManager.setFilters).not.toHaveBeenCalledWith([filterSameIndexControlled]); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter]); }) it('should filter the filters received in the constructor and keep the filters with the same index and merge with the fixed ', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const sameIndexFilter = createFilter('agent.id', '1', 'my-index'); - const fixedFilter = createFilter('agent.id', '1', 'my-index'); + const fixedFilter = createFilter('agent.id', '2', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, [sameIndexFilter]); - expect(filterManager.getFilters()).toEqual([sameIndexFilter, fixedFilter]); + new PatternDataSourceFilterManager(dataSource, [sameIndexFilter], mockedFilterManager); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(1); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter, sameIndexFilter]); }); - - }) describe('getFilters', () => { it('should return the filters with the fixed filters included defined in data source', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); + fixedFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT; jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, []); - const filters = filterManager.getFilters(); - expect(filters).toEqual([fixedFilter]); + new PatternDataSourceFilterManager(dataSource, [], mockedFilterManager); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(1); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter]); }) it('should return the filters merged the fixed filters included defined in data source', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); const fixedFilter = createFilter('agent.id', '1', 'my-index'); + fixedFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT; jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); const sameIndexFilter = createFilter('agent.id', '1', 'my-index'); - const filterManager = new PatternDataSourceFilterManager(dataSource, [sameIndexFilter]); - const filters = filterManager.getFilters(); - expect(filters).toEqual([fixedFilter, sameIndexFilter]); + new PatternDataSourceFilterManager(dataSource, [sameIndexFilter], mockedFilterManager); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(1); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter, sameIndexFilter]); }) it('should return only the fixed filters when receives invalid filters', () => { @@ -169,10 +216,9 @@ describe('PatternDataSourceFilterManager', () => { const fixedFilter = createFilter('agent.id', '1', 'my-index'); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); const anotherIndexFilter = createFilter('agent.id', '1', 'another-index'); - const filterManager = new PatternDataSourceFilterManager(dataSource, [anotherIndexFilter]); - const filters = filterManager.getFilters(); - expect(filters).toEqual([fixedFilter]); - expect(filters).not.toContainEqual(anotherIndexFilter); + new PatternDataSourceFilterManager(dataSource, [anotherIndexFilter], mockedFilterManager); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(1); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter]); }) }) @@ -187,62 +233,55 @@ describe('PatternDataSourceFilterManager', () => { } }); jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, []); - const filters = filterManager.getFixedFilters(); - expect(filters).toContainEqual(fixedFilter); - expect(dataSource.getFixedFilters).toHaveBeenCalledTimes(1); + new PatternDataSourceFilterManager(dataSource, [], mockedFilterManager); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(1); + expect(mockedFilterManager.setFilters).toHaveBeenCalledWith([fixedFilter]); }); - it('should return the fixed filters merged with the pinned agent filter when correspond', () => { - // mock store.getState - (store.getState as jest.Mock).mockReturnValue( - { - appStateReducers: { - currentAgentData: { - id: '001' - } - } - }); - const dataSource = new DataSourceMocked('my-index', 'my-title'); - const fixedFilter = createFilter('agent.id', '1', 'my-index'); - jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, []); - const filters = filterManager.getFixedFilters(); - const agentPinnedFilter = filterManager.getPinnedAgentFilter(); - expect(filters).toContainEqual(fixedFilter); - expect(filters).toEqual([fixedFilter, ...agentPinnedFilter]); - }); + }) - it('should return only the fixed filters from the data source when the pinned agent filter is not defined', () => { - // mock store.getState - (store.getState as jest.Mock).mockReturnValue( - { - appStateReducers: { - } - }); + describe('getFetchFilters', () => { + it('should return the filters to fetch the data from the data source', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); - const fixedFilter = createFilter('agent.id', '1', 'my-index'); - jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([fixedFilter]); - const filterManager = new PatternDataSourceFilterManager(dataSource, []); - const filters = filterManager.getFixedFilters(); - expect(filters).toContainEqual(fixedFilter); - expect(filters).not.toContainEqual(filterManager.getPinnedAgentFilter()); + const storedFilter = createFilter('agent.id', '1', 'my-index'); + storedFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT; + jest.spyOn(dataSource, 'getFetchFilters').mockReturnValue([storedFilter]); + const filterManager = new PatternDataSourceFilterManager(dataSource, [], mockedFilterManager); + jest.spyOn(filterManager, 'getFilters').mockReturnValue([]); + const filters = filterManager.getFetchFilters(); + expect(filters).toEqual([storedFilter]); }) + }) - describe('getFetchFilters', () => { - it('should return the filters to fetch the data from the data source merging filters', () => { + describe('setFilters', () => { + it('should remove the hidden filters from the filters received after initialize', () => { const dataSource = new DataSourceMocked('my-index', 'my-title'); - const storedFilter = createFilter('agent.id', '1', 'my-index'); + const filterManager = new PatternDataSourceFilterManager(dataSource, [], mockedFilterManager); + const hiddenFilter = createFilter('agent.id', '1', 'my-index'); + hiddenFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER; + filterManager.setFilters([hiddenFilter]); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(2); + expect(mockedFilterManager.setFilters).toHaveBeenLastCalledWith([]); + }) - const filterManager = new PatternDataSourceFilterManager(dataSource, []); - jest.spyOn(filterManager, 'getFilters').mockReturnValue([storedFilter]); + it('should remove the second filter received when recevies two filters with the same controlledBy value', () => { + const dataSource = new DataSourceMocked('my-index', 'my-title'); + const filterManager = new PatternDataSourceFilterManager(dataSource, [], mockedFilterManager); + const hiddenFilter = createFilter('agent.id', '1', 'my-index'); + hiddenFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT; + const hiddenFilter2 = createFilter('agent.id', '2', 'my-index'); + hiddenFilter2.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT; + filterManager.setFilters([hiddenFilter, hiddenFilter2]); + expect(mockedFilterManager.setFilters).toHaveBeenCalledTimes(2); + expect(mockedFilterManager.setFilters).toHaveBeenLastCalledWith([hiddenFilter]); }) + }) + + describe('wazuh filters', () => { it('should return the filters to fetch the data merging the filters stored and the excluded manager filter', () => { - const dataSource = new DataSourceMocked('my-index', 'my-title'); - const storedFilter = createFilter('agent.id', '1', 'my-index'); (store.getState as jest.Mock).mockReturnValue( { appConfig: { @@ -251,16 +290,13 @@ describe('PatternDataSourceFilterManager', () => { } } }); - const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); - const excludeManager = filterManager.getExcludeManagerFilter(); - jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); - const filters = filterManager.getFetchFilters(); - expect(filters).toEqual([storedFilter, ...excludeManager]); + const filter = PatternDataSourceFilterManager.getExcludeManagerFilter('index-title'); + expect(filter.length).toBe(1); + expect(filter[0].meta.controlledBy).toBe(DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER); + }) it('should return the filters to fetch the data merging the filters stored without the excluded manager filter when is not defined', () => { - const dataSource = new DataSourceMocked('my-index', 'my-title'); - const storedFilter = createFilter('agent.id', '1', 'my-index'); (store.getState as jest.Mock).mockReturnValue( { appConfig: { @@ -268,40 +304,57 @@ describe('PatternDataSourceFilterManager', () => { } } }); - const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); - jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); - const filters = filterManager.getFetchFilters(); - expect(filters).toEqual([storedFilter]); + const filter = PatternDataSourceFilterManager.getExcludeManagerFilter('index-title'); + expect(filter.length).toBe(0); }) it('should return the filters to fetch the data merging the filters stored and the allowed agents filter', () => { - const dataSource = new DataSourceMocked('my-index', 'my-title'); - const storedFilter = createFilter('agent.id', '1', 'my-index'); (store.getState as jest.Mock).mockReturnValue( { appStateReducers: { allowedAgents: ['001'] } }); - const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); - jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); - const allowedAgents = filterManager.getAllowAgentsFilter(); - const filters = filterManager.getFetchFilters(); - expect(filters).toEqual([storedFilter, ...allowedAgents]); + const filter = PatternDataSourceFilterManager.getAllowAgentsFilter('index-title'); + expect(filter.length).toBe(1); + expect(filter[0].meta.controlledBy).toBe(AUTHORIZED_AGENTS); }) it('should return the filters to fetch the data merging the filters stored without the allowed agents filter when is not defined', () => { - const dataSource = new DataSourceMocked('my-index', 'my-title'); - const storedFilter = createFilter('agent.id', '1', 'my-index'); (store.getState as jest.Mock).mockReturnValue( { appStateReducers: { } }); - const filterManager = new PatternDataSourceFilterManager(dataSource, [storedFilter]); - jest.spyOn(dataSource, 'getFixedFilters').mockReturnValue([]); - const filters = filterManager.getFetchFilters(); - expect(filters).toEqual([storedFilter]); + const filter = PatternDataSourceFilterManager.getAllowAgentsFilter('index-title'); + expect(filter.length).toBe(0); }) + + it('should return the fixed filters merged with the pinned agent filter when correspond', () => { + // mock store.getState + (store.getState as jest.Mock).mockReturnValue( + { + appStateReducers: { + currentAgentData: { + id: '001' + } + } + }); + const filter = PatternDataSourceFilterManager.getPinnedAgentFilter('index-title'); + expect(filter.length).toBe(1); + expect(filter[0].meta.controlledBy).toBe(DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT); + }); + + it('should return only the fixed filters from the data source when the pinned agent filter is not defined', () => { + // mock store.getState + (store.getState as jest.Mock).mockReturnValue( + { + appStateReducers: { + } + }); + const filter = PatternDataSourceFilterManager.getPinnedAgentFilter('index-title'); + expect(filter.length).toBe(0); + }) + }) }) \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts index 6bfaf6eb46..fde410eab4 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts @@ -1,10 +1,77 @@ +import store from '../../../../redux/store'; +import { AppState } from '../../../../react-services/app-state'; import { FilterManager } from '../../../../../../../src/plugins/data/public'; import { getDataPlugin } from '../../../../kibana-services'; -import { tDataSourceFilterManager, tFilter, tSearchParams, tDataSource } from '../index'; +import { FilterHandler } from '../../../../utils/filter-handler'; +import { tDataSourceFilterManager, tFilter, tSearchParams, tDataSource, tFilterManager } from '../index'; +import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT, DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, AUTHORIZED_AGENTS } from '../../../../../common/constants'; + +/** + * Get the filter that excludes the data related to Wazuh servers + * @param indexPatternTitle Index pattern title + * @returns + */ +export function getFilterExcludeManager(indexPatternTitle: string) { + return { + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: true, + params: { query: '000' }, + type: 'phrase', + index: indexPatternTitle, + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, + }, + query: { match_phrase: { 'agent.id': '000' } }, + $state: { store: 'appState' }, + }; +} + +/** + * Get the filter that restrict the search to the allowed agents + * @param agentsIds + * @param indexPatternTitle + * @returns + */ +export function getFilterAllowedAgents( + agentsIds: string[], + indexPatternTitle: string, +) { + const field = 'agent.id'; + return { + meta: { + index: indexPatternTitle, + type: 'phrases', + key: field, + value: agentsIds.toString(), + params: agentsIds, + alias: null, + negate: false, + disabled: false, + controlledBy: AUTHORIZED_AGENTS, + }, + query: { + bool: { + should: agentsIds.map(id => { + return { + match_phrase: { + [field]: id, + }, + }; + }), + minimum_should_match: 1, + }, + }, + $state: { + store: 'appState', + }, + }; +} export class PatternDataSourceFilterManager implements tDataSourceFilterManager { - private filterManager: FilterManager; - constructor(private dataSource: tDataSource, filters: tFilter[] = [], filterStorage?: FilterManager) { + private filterManager: tFilterManager; + constructor(private dataSource: tDataSource, filters: tFilter[] = [], filterStorage?: tFilterManager) { if (!dataSource) { throw new Error('Data source is required'); } @@ -15,7 +82,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager throw new Error('Filter manager is required'); } - this.filterManager.setFilters(this.getDefaultFilters(filters)); + this.setFilters(this.getDefaultFilters(filters)); } getUpdates$(): any { @@ -34,10 +101,20 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager } setFilters(filters: tFilter[]) { - this.filterManager && this.filterManager.setFilters(filters); + // remove hidden filters, is used to remove the fetch filters that are applied by an external source that add filters to the global filter manager + let cleanedFilters = this.removeHiddenFilters(filters); + // to prevent repeted filter the controlledBy value cannot be the same, cannot exists to filters with the same controlledBy value + cleanedFilters = this.removeWithSameControlledBy(cleanedFilters); + this.filterManager && this.filterManager.setFilters(cleanedFilters); } - getDefaultFilters(filters: tFilter[]) { + /** + * Get all the filters from the filters manager and only returns the filters added by the user and + * adds the fixed filters defined in the data source. + * @param filters + * @returns + */ + private getDefaultFilters(filters: tFilter[]) { const defaultFilters = filters.length ? filters : this.getFilters(); return [ ...this.getFixedFilters(), @@ -80,9 +157,18 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager */ getFilters() { return [ + //...this.getDefaultFilters(this.filterManager.getFilters()) ...this.filterManager.getFilters() ] } + + /** + * Return the filters without the filters that have the property meta.controlledBy with the prefix hidden- + */ + private removeHiddenFilters(filters: tFilter[]) { + if (!filters) return filters; + return filters.filter(filter => !filter.meta?.controlledBy?.startsWith('hidden-')); + } /** * Concatenate the filters to fetch the data from the data source @@ -90,10 +176,30 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager */ getFetchFilters(): tFilter[] { return [ + ...this.dataSource.getFetchFilters(), ...this.getFilters(), - ...this.dataSource.getFetchFilters() ] + } + + /** + * Prevent duplicated filters, cannot exists with the same controlledBy value. + * This ignore the filters that have the controlledBy value null + * @param filters + * @returns + */ + private removeWithSameControlledBy(filters: tFilter[]): tFilter[]{ + if (!filters) return filters; + const controlledList: string[] = []; + const cleanedFilters: tFilter[] = []; + filters.forEach(filter => { + const controlledBy = filter.meta?.controlledBy; + if (!controlledBy || !controlledList.includes(controlledBy as string)) { + controlledList.push(controlledBy as string); + cleanedFilters.push(filter); + } + }); + return cleanedFilters; } /** @@ -102,18 +208,103 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager * @returns */ - removeRepeatedFilters(filters: tFilter) { + private removeRepeatedFilters(filters: tFilter[]): tFilter[] { if (!filters) return filters; - const query = filters.query; - if (!query) return filters; - const keys = Object.keys(query); - if (keys.length === 1) { - const key = keys[0]; - if (query[key].query) { - query[key].query = Array.isArray(query[key].query) ? Array.from(new Set(query[key].query)) : query[key].query; + const filtersMap = filters.reduce((acc, filter) => { + const key = JSON.stringify(filter.query); + if (!acc[key]) { + acc[key] = filter; } + return acc; + }, {}); + return Object.values(filtersMap); + } + + + /** + * Return the filter when the cluster or manager are enabled + */ + static getClusterManagerFilters(indexPatternTitle: string, controlledByValue: string, key?: string): tFilter[] { + const filterHandler = new FilterHandler(); + const isCluster = AppState.getClusterInfo().status == 'enabled'; + const managerFilter = filterHandler.managerQuery( + isCluster + ? AppState.getClusterInfo().cluster + : AppState.getClusterInfo().manager, + true, + key + ); + managerFilter.meta = { + ...managerFilter.meta, + controlledBy: controlledByValue, + index: indexPatternTitle + } + //@ts-ignore + managerFilter.$state = { + store: 'globalState' + } + //@ts-ignore + return [managerFilter] as tFilter[]; + } + + /** + * Returns the filter when the an agent is pinned (saved in the session storage or redux store) + */ + static getPinnedAgentFilter(indexPatternTitle: string): tFilter[] { + const agentId = store.getState().appStateReducers?.currentAgentData?.id; + const url = window.location.href; + const regex = new RegExp('agentId=' + '[^&]*'); + const match = url.match(regex); + const isPinnedAgentByUrl = match && match[0]; + if (!agentId && !isPinnedAgentByUrl) return []; + const agentValueUrl = isPinnedAgentByUrl?.split('=')[1]; + return [{ + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: false, + params: { query: agentId || agentValueUrl }, + type: 'phrase', + index: indexPatternTitle, + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT + }, + query: { + match: { + 'agent.id': { + query: agentId, + type: 'phrase', + }, + }, + }, + $state: { + store: 'globalState' // this makes that the filter is pinned and can be remove the close icon by css + }, + } as tFilter] + } + + /** + * Return the filter to exclude the data related to servers (managers) due to the setting hideManagerAlerts is enabled + */ + static getExcludeManagerFilter(indexPatternTitle: string): tFilter[] { + if(store.getState().appConfig?.data?.hideManagerAlerts){ + let excludeManagerFilter = getFilterExcludeManager(indexPatternTitle) as tFilter; + return [excludeManagerFilter]; + } + return []; + } + + /** + * Return the allowed agents related to the user permissions to read data from agents in the + API server + */ + static getAllowAgentsFilter(indexPatternTitle: string): tFilter[] { + const allowedAgents = store.getState().appStateReducers?.allowedAgents || []; + if(allowedAgents.length > 0){ + const allowAgentsFilter = getFilterAllowedAgents(allowedAgents, indexPatternTitle) as tFilter; + return [allowAgentsFilter]; } - return filters; + return [] } } \ No newline at end of file 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 e828c34fab..8cbb07d0e0 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 @@ -1,12 +1,8 @@ import { tDataSource, tSearchParams, tFilter, tParsedIndexPattern } from "../index"; import { getDataPlugin } from '../../../../kibana-services'; -import { Filter, IndexPatternsContract, IndexPattern } from "../../../../../../../src/plugins/data/public"; +import { IndexPatternsContract, IndexPattern } from "../../../../../../../src/plugins/data/public"; import { search } from '../../search-bar/search-bar-service'; -import store from '../../../../redux/store'; -import { getFilterExcludeManager, getFilterAllowedAgents } from '../../../../react-services/data-sources/vulnerabilities-states'; -import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT, DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER } from "../../../../../common/constants"; -import { AppState } from '../../../../react-services/app-state'; -import { FilterHandler } from '../../../../utils/filter-handler'; +import { PatternDataSourceFilterManager } from '../index'; export class PatternDataSource implements tDataSource { id: string; @@ -14,8 +10,6 @@ export class PatternDataSource implements tDataSource { fields: any[]; patternService: IndexPatternsContract; indexPattern: IndexPattern; - defaultFixedFilters: tFilter[]; - filters: tFilter[] = []; constructor(id: string, title: string) { this.id = id; @@ -33,22 +27,9 @@ export class PatternDataSource implements tDataSource { return this.fields; } - setFilters(filters: Filter[]) { - this.filters = filters; - } - - - getFilters(){ - return [ - ...this.getFixedFilters(), - ...this.filters - ]; - } - getFixedFilters(): tFilter[]{ // return all filters return [ - ...this.getClusterManagerFilters(), ...this.getPinnedAgentFilter(), ]; } @@ -56,8 +37,7 @@ export class PatternDataSource implements tDataSource { getFetchFilters(): tFilter[]{ return [ ...this.getAllowAgentsFilter(), - ...this.getExcludeManagerFilter(), - ...this.getFilters(), + ...this.getExcludeManagerFilter() ]; } @@ -126,81 +106,19 @@ export class PatternDataSource implements tDataSource { } } - getClusterManagerFilters() { - const filterHandler = new FilterHandler(); - const isCluster = AppState.getClusterInfo().status == 'enabled'; - const managerFilter = filterHandler.managerQuery( - isCluster - ? AppState.getClusterInfo().cluster - : AppState.getClusterInfo().manager, - true - ); - managerFilter.meta = { - ...managerFilter.meta, - controlledBy: DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER, - index: this.id - } - //@ts-ignore - managerFilter.$state = { - store: 'appState' - } - //@ts-ignore - return [managerFilter] as tFilter[]; - } - /** * Returns the filter when the an agent is pinned (saved in the session storage or redux store) */ - getPinnedAgentFilter(): tFilter[] { - const agentId = store.getState().appStateReducers?.currentAgentData?.id; - const url = window.location.href; - const regex = new RegExp('agentId=' + '[^&]*'); - const match = url.match(regex); - const isPinnedAgentByUrl = match && match[0]; - if (!agentId && !isPinnedAgentByUrl) return []; - return [{ - meta: { - removable: false, // used to hide the close icon in the filter - alias: null, - disabled: false, - key: 'agent.id', - negate: false, - params: { query: agentId }, - type: 'phrase', - index: this.id, - controlledBy: DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT - }, - query: { - match: { - 'agent.id': { - query: agentId, - type: 'phrase', - }, - }, - }, - $state: { - store: 'appState' // check appStore is not assignable, why is stored here? - }, - } as tFilter] - } - - /** - * Return the filter to exclude the data related to servers (managers) due to the setting hideManagerAlerts is enabled - */ - getExcludeManagerFilter(): tFilter[] { - return store.getState().appConfig?.data?.hideManagerAlerts ? - [getFilterExcludeManager(this.title) as tFilter] : []; - } - - /** - * Return the allowed agents related to the user permissions to read data from agents in the - API server - */ - getAllowAgentsFilter(): tFilter[] { - const allowedAgents = store.getState().appStateReducers?.allowedAgents || []; - return allowedAgents.lenght > 0 ? - [getFilterAllowedAgents(allowedAgents, this.title) as tFilter] : [] - } - + getPinnedAgentFilter(): tFilter[] { + return PatternDataSourceFilterManager.getPinnedAgentFilter(this.title); + } + + getAllowAgentsFilter(): tFilter[] { + return PatternDataSourceFilterManager.getAllowAgentsFilter(this.title); + } + + getExcludeManagerFilter(): tFilter[] { + return PatternDataSourceFilterManager.getExcludeManagerFilter(this.title); + } } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts index d510fbfe99..15e530832d 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source.ts @@ -1,8 +1,7 @@ import { DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER, VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../../../../../common/constants'; -import { FilterHandler } from '../../../../../utils/filter-handler'; -import { tFilter } from '../../index'; -import { PatternDataSource } from '../pattern-data-source'; +import { tFilter, PatternDataSourceFilterManager } from '../../index'; import { AppState } from '../../../../../react-services/app-state'; +import { PatternDataSource } from '../pattern-data-source'; export class VulnerabilitiesDataSource extends PatternDataSource { @@ -12,29 +11,18 @@ export class VulnerabilitiesDataSource extends PatternDataSource { getFixedFilters(): tFilter[] { return [ + ...this.getClusterManagerFilters(), ...super.getFixedFilters(), ] } getClusterManagerFilters() { - const filterHandler = new FilterHandler(); - const isCluster = AppState.getClusterInfo().status == 'enabled'; - const managerFilter = filterHandler.managerQuery( - isCluster - ? AppState.getClusterInfo().cluster - : AppState.getClusterInfo().manager, - true, + return PatternDataSourceFilterManager.getClusterManagerFilters(this.title, + DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER, VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ - AppState.getClusterInfo().status + AppState.getClusterInfo().status ], - - ); - managerFilter.meta.index = this.id; - managerFilter.meta.controlledBy = DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER; - managerFilter.$state = { - store: 'appState' - } - return [managerFilter] as tFilter[]; + ); } } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/types.ts b/plugins/main/public/components/common/data-source/types.ts index 0dd7592086..d80961f325 100644 --- a/plugins/main/public/components/common/data-source/types.ts +++ b/plugins/main/public/components/common/data-source/types.ts @@ -1,14 +1,8 @@ import { Filter } from "../../../../../../src/plugins/data/common"; import { FilterManager } from "../../../../../../src/plugins/data/public"; -export type tFilter = Filter & { - meta?: { - removable?: boolean; - } -}; - export type tSearchParams = { - filters?: tFilter[]; + filters?: Filter[]; query?: any; pagination?: { pageIndex?: number; @@ -27,6 +21,8 @@ export type tSearchParams = { }; } +export type tFilter = Filter; + // create a new type using the FilterManager type but only the getFilters, setFilters, addFilters, getUpdates$ export type tFilterManager = Pick; @@ -34,9 +30,7 @@ export type tDataSource = { id: string; title: string; select(): Promise; - getFilters: () => Promise | tFilter[]; - setFilters: (filters: tFilter[]) => Promise | void; - getFields: () => Promise | any[]; + getFields(): any[]; getFixedFilters: () => tFilter[]; getFetchFilters: () => tFilter[]; fetch: (params: tSearchParams) => Promise; From c43ad2b61479197fc23b9971adb76b593b480bc3 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Wed, 3 Apr 2024 12:21:21 -0300 Subject: [PATCH 33/36] Fix error on merge 4.9.0 --- .../overview/vulnerabilities/dashboards/overview/dashboard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index 709a74c0b6..4c0fa32952 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -122,7 +122,6 @@ const DashboardVulsComponent: React.FC = () => { }, hidePanelTitles: true, }} - onInputUpdated={handleFilterByVisualization} />
Date: Wed, 3 Apr 2024 14:13:19 -0300 Subject: [PATCH 34/36] Add hide remove filter in search bar --- .../pattern/alerts/alerts-data-source.ts | 2 +- .../pattern-data-source-filter-manager.ts | 6 +-- .../common/search-bar/search-bar-service.ts | 50 ++++++++++++++++--- .../common/search-bar/search-bar-styles.scss | 7 --- .../common/search-bar/use-search-bar.ts | 30 +++++++++-- .../wz-agent-selector/wz-agent-selector.js | 2 +- 6 files changed, 74 insertions(+), 23 deletions(-) delete mode 100644 plugins/main/public/components/common/search-bar/search-bar-styles.scss diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts index e52359e12f..3e97cfd942 100644 --- a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-data-source.ts @@ -43,7 +43,7 @@ export class AlertsDataSource extends PatternDataSource { }, }, $state: { - store: 'globalState', + store: 'appState', }, } as tFilter]; } diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts index fde410eab4..8399c449f1 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts @@ -125,7 +125,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager /** * Filter the filters that was added by the user * The filters must not have: - * - the property isImplicit ($state.isImplicit) defined + * - the property isImplicit ($state.isImplicit) defined --- DEPRECATED THE USE OF isImplicity PROPERTY INSIDE THE FILTERS * - the meta.controlledBy property defined * - the meta.index is not the same as the dataSource.id * @@ -241,7 +241,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager } //@ts-ignore managerFilter.$state = { - store: 'globalState' + store: 'appState' } //@ts-ignore return [managerFilter] as tFilter[]; @@ -278,7 +278,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager }, }, $state: { - store: 'globalState' // this makes that the filter is pinned and can be remove the close icon by css + store: 'appState' // this makes that the filter is pinned and can be remove the close icon by css }, } as tFilter] } diff --git a/plugins/main/public/components/common/search-bar/search-bar-service.ts b/plugins/main/public/components/common/search-bar/search-bar-service.ts index f7457faf4f..a6add002c5 100644 --- a/plugins/main/public/components/common/search-bar/search-bar-service.ts +++ b/plugins/main/public/components/common/search-bar/search-bar-service.ts @@ -1,6 +1,7 @@ import { getPlugins } from '../../../kibana-services'; import { IndexPattern, Filter, OpenSearchQuerySortValue } from "../../../../../../src/plugins/data/public"; import { SearchResponse } from "../../../../../../src/core/server"; +import { tFilter } from '../data-source/index'; export interface SearchParams { indexPattern: IndexPattern; @@ -25,7 +26,7 @@ export interface SearchParams { export const search = async (params: SearchParams): Promise => { const { indexPattern, filters: defaultFilters = [], query, pagination, sorting, fields } = params; - if(!indexPattern){ + if (!indexPattern) { return; } const data = getPlugins().data; @@ -38,11 +39,12 @@ export const search = async (params: SearchParams): Promise 0){ + if (fields && Array.isArray(fields) && fields.length > 0) { searchParams.setField('fields', fields); } - try{ + try { return await searchParams.fetch(); - }catch(error){ - if(error.body){ + } catch (error) { + if (error.body) { throw error.body; } throw error; } -}; \ No newline at end of file +}; + +export const hideCloseButtonOnFixedFilters = (filters: tFilter[], elements: NodeListOf) => { + const fixedFilters = filters.map((filter, index) => { + if (filter.meta.controlledBy && !filter.meta.controlledBy.startsWith('hidden')) { + return { + index, + filter, + field: filter.meta?.key, + value: filter.meta?.params?.query + } + } + }).filter((filter) => filter); + + elements.forEach((element, index) => { + // the filter badge will be changed only when the field and value are the same and the position in the array is the same + const filterField = element.querySelector('.euiBadge__content .euiBadge__childButton > span')?.textContent?.split(':')[0]; + const filterValue = element.querySelector('.euiBadge__content .globalFilterLabel__value')?.textContent; + // when the field,value and index is the same, hide the remove button + const filter = fixedFilters.find((filter) => filter?.field === filterField && filter?.value === filterValue && filter?.index === index); + if (filter) { + // hide the remove button + const iconButton = element.querySelector('.euiBadge__iconButton') as HTMLElement; + iconButton?.style?.setProperty('display', 'none'); + // change the cursor to not-allowed + const badgeButton = element.querySelector('.euiBadge__content .euiBadge__childButton') as HTMLElement; + badgeButton?.style?.setProperty('cursor', 'not-allowed'); + // remove the popup on click to prevent the filter from being removed + element.addEventListener('click', (event) => { + event.preventDefault(); + event.stopPropagation(); + }) + } + }) +} \ No newline at end of file diff --git a/plugins/main/public/components/common/search-bar/search-bar-styles.scss b/plugins/main/public/components/common/search-bar/search-bar-styles.scss deleted file mode 100644 index 51456c9619..0000000000 --- a/plugins/main/public/components/common/search-bar/search-bar-styles.scss +++ /dev/null @@ -1,7 +0,0 @@ -.wz-search-bar #GlobalFilterGroup .globalFilterBar__flexItem { - .globalFilterItem-isPinned button[aria-label="Delete"] { - // all the button close with the aria-label="Delete" will be hidden - display: none; - } - -} \ No newline at end of file diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index 54346fb118..5c762fbc2f 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -9,7 +9,7 @@ import { } from '../../../../../../src/plugins/data/public'; import { getDataPlugin } from '../../../kibana-services'; import { useQueryManager, useTimeFilter } from '../hooks'; -import './search-bar-styles.scss'; +import { hideCloseButtonOnFixedFilters } from './search-bar-service'; // Input - types type tUseSearchBarCustomInputs = { indexPattern: IndexPattern; @@ -43,8 +43,30 @@ const useSearchBarConfiguration = ( : useQueryManager(); const { timeFilter, timeHistory, setTimeFilter } = useTimeFilter(); // states - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [indexPatternSelected, setIndexPatternSelected] = useState(indexPattern); + const TIMEOUT_MILISECONDS = 100; + + const hideRemoveFilter = (retry: number = 0) => { + let elements = document.querySelectorAll('.wz-search-bar .globalFilterItem'); + if ((!elements || !filters.length) && retry < 10) { + // the setTimeout is used to wait for the DOM elements to be rendered + setTimeout(() => { + // this is a workaround to hide the close button on fixed filters via vanilla js + hideRemoveFilter(++retry); + }, TIMEOUT_MILISECONDS); + + }else{ + hideCloseButtonOnFixedFilters(filters, elements); + } + } + + useEffect(() => { + setTimeout(() => { + // this is a workaround to hide the close button on fixed filters via vanilla js + hideRemoveFilter(); + }, TIMEOUT_MILISECONDS); + }, [filters]); useEffect(() => { if (indexPattern) { @@ -61,10 +83,10 @@ const useSearchBarConfiguration = ( */ const initSearchBar = async () => { setIsLoading(true); - if(!indexPattern) { + if (!indexPattern) { const defaultIndexPattern = await getDefaultIndexPattern(); setIndexPatternSelected(defaultIndexPattern); - }else{ + } else { setIndexPatternSelected(indexPattern); } setIsLoading(false); diff --git a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js index 0862552a9a..eca33d03eb 100644 --- a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js +++ b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js @@ -101,7 +101,7 @@ class WzAgentSelector extends Component { }, }, }, - $state: { store: 'globalState' }, + $state: { store: 'appState' }, }; agentFilters.push(filter); filterManager.setFilters(agentFilters); From 904c03fd54eaa3db21311224b92d1273ee371d23 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Thu, 4 Apr 2024 10:21:08 -0300 Subject: [PATCH 35/36] Remove unused code --- .../main/public/kibana-integrations/kibana-discover.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugins/main/public/kibana-integrations/kibana-discover.js b/plugins/main/public/kibana-integrations/kibana-discover.js index 64aa9ca099..a666e157f6 100644 --- a/plugins/main/public/kibana-integrations/kibana-discover.js +++ b/plugins/main/public/kibana-integrations/kibana-discover.js @@ -1124,14 +1124,6 @@ function discoverController( ////////////////////////////////////////////////////// WAZUH ////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - $scope.$watch('fetchStatus', () => { - if ($scope.fetchStatus !== fetchStatuses.UNINITIALIZED) { - setTimeout(() => { - //modulesHelper.hideCloseButtons(); - }, 100); - } - }); - $scope.loadFilters = async (wzCurrentFilters, tab) => { filterManager.removeAll(); const appState = appStateContainer.getState(); From 6198d115e0c926270c73f4b841cb937ea14d6153 Mon Sep 17 00:00:00 2001 From: Maximiliano Date: Fri, 5 Apr 2024 10:13:49 -0300 Subject: [PATCH 36/36] Solve requested changes --- .../wz-data-source-selector.tsx | 5 +- .../data-source/hooks/use-data-source.test.ts | 3 - .../data-source/hooks/use-data-source.ts | 1 - .../pattern-data-source-filter-manager.ts | 17 +++--- .../pattern/pattern-data-source-repository.ts | 10 ++-- .../vulnerabilities-data-source-repository.ts | 4 +- .../components/common/data-source/types.ts | 4 +- .../loading-spinner/loading-spinner.scss | 4 ++ .../loading-spinner/loading-spinner.tsx | 21 +++++++ .../common/no-results/no-results.tsx | 60 +++++++++++++++++++ .../common/search-bar/use-search-bar.ts | 4 +- .../common/wazuh-discover/wz-discover.tsx | 5 +- .../dashboards/inventory/inventory.tsx | 2 +- .../dashboards/overview/dashboard.tsx | 1 + 14 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 plugins/main/public/components/common/loading-spinner/loading-spinner.scss create mode 100644 plugins/main/public/components/common/loading-spinner/loading-spinner.tsx create mode 100644 plugins/main/public/components/common/no-results/no-results.tsx diff --git a/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx b/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx index e5f3f18d97..dda0bb5089 100644 --- a/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx +++ b/plugins/main/public/components/common/data-source/components/wz-data-source-selector/wz-data-source-selector.tsx @@ -29,9 +29,8 @@ type tWzDataSourceSelector const WzDataSourceSelector = (props: tWzDataSourceSelector) => { const { onChange, name = 'data source', dataSourceSelector: defaultDataSourceSelector } = props; - const [dataSourceList, setDataSourceList] = useState([]); + const [dataSourceList, setDataSourceList] = useState([]); const [selectedPattern, setSelectedPattern] = useState(); - const [isLoading, setIsLoading] = useState(false); const [dataSourceSelector, setDataSourceSelector] = useState | undefined>(defaultDataSourceSelector); useEffect(() => { @@ -41,7 +40,6 @@ const WzDataSourceSelector = (props: tWzDataSourceSelector ({ ...(jest.requireActual('../../../../kibana-services') as object), 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 a4a716b1b7..11c5453af3 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 @@ -45,7 +45,6 @@ type tUseDataSourceNotLoadedReturns = { export function useDataSource(props: tUseDataSourceProps): tUseDataSourceLoadedReturns | tUseDataSourceNotLoadedReturns { const { - filterManager, filters: defaultFilters = [], DataSource: DataSourceConstructor, repository, diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts index 8399c449f1..20a04174a8 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts @@ -1,10 +1,11 @@ import store from '../../../../redux/store'; import { AppState } from '../../../../react-services/app-state'; -import { FilterManager } from '../../../../../../../src/plugins/data/public'; import { getDataPlugin } from '../../../../kibana-services'; import { FilterHandler } from '../../../../utils/filter-handler'; import { tDataSourceFilterManager, tFilter, tSearchParams, tDataSource, tFilterManager } from '../index'; import { DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT, DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, AUTHORIZED_AGENTS } from '../../../../../common/constants'; +const MANAGER_AGENT_ID = '000'; +const AGENT_ID_KEY = 'agent.id'; /** * Get the filter that excludes the data related to Wazuh servers @@ -16,14 +17,14 @@ export function getFilterExcludeManager(indexPatternTitle: string) { meta: { alias: null, disabled: false, - key: 'agent.id', + key: AGENT_ID_KEY, negate: true, - params: { query: '000' }, + params: { query: MANAGER_AGENT_ID }, type: 'phrase', index: indexPatternTitle, controlledBy: DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, }, - query: { match_phrase: { 'agent.id': '000' } }, + query: { match_phrase: { [AGENT_ID_KEY]: MANAGER_AGENT_ID } }, $state: { store: 'appState' }, }; } @@ -38,7 +39,7 @@ export function getFilterAllowedAgents( agentsIds: string[], indexPatternTitle: string, ) { - const field = 'agent.id'; + const field = AGENT_ID_KEY; return { meta: { index: indexPatternTitle, @@ -262,7 +263,7 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager meta: { alias: null, disabled: false, - key: 'agent.id', + key: AGENT_ID_KEY, negate: false, params: { query: agentId || agentValueUrl }, type: 'phrase', @@ -271,8 +272,8 @@ export class PatternDataSourceFilterManager implements tDataSourceFilterManager }, query: { match: { - 'agent.id': { - query: agentId, + [AGENT_ID_KEY]: { + query: agentId || agentValueUrl, type: 'phrase', }, }, 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 index d0ef7a6931..f1f1270adb 100644 --- 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 @@ -87,19 +87,19 @@ export class PatternDataSourceRepository implements tDataSourceRepository { + setDefault(dataSource: tParsedIndexPattern): void { if(!dataSource){ throw new Error('Index pattern is required'); } AppState.setCurrentPattern(dataSource.id); - return Promise.resolve(); + return; } - getDefault(): Promise | tParsedIndexPattern | null { + async getDefault(): Promise { const currentPattern = AppState.getCurrentPattern(); if(!currentPattern){ - return null; + return Promise.resolve(null); } - return this.get(currentPattern); + return await this.get(currentPattern); } } diff --git a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts index d565aa62a4..a1ef336a6f 100644 --- a/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts +++ b/plugins/main/public/components/common/data-source/pattern/vulnerabilities/vulnerabilities-data-source-repository.ts @@ -35,12 +35,12 @@ export class VulnerabilitiesDataSourceRepository extends PatternDataSourceReposi getDefault(){ console.warn('getDefault not implemented for vulnerabilities data source repository'); - return null; + return Promise.resolve(null); } setDefault(dataSource: tParsedIndexPattern){ console.warn('setDefault not implemented for vulnerabilities data source repository'); - return Promise.resolve(); + return; } } \ No newline at end of file diff --git a/plugins/main/public/components/common/data-source/types.ts b/plugins/main/public/components/common/data-source/types.ts index d80961f325..7e8a5c5caa 100644 --- a/plugins/main/public/components/common/data-source/types.ts +++ b/plugins/main/public/components/common/data-source/types.ts @@ -57,8 +57,8 @@ export type tDataSourceFilterManager = { export type tDataSourceRepository = { get(id: string): Promise; getAll(): Promise; - setDefault(dataSourceData: T): Promise | void; - getDefault(): Promise | T | null; + setDefault(dataSourceData: T): void; + getDefault(): Promise; } diff --git a/plugins/main/public/components/common/loading-spinner/loading-spinner.scss b/plugins/main/public/components/common/loading-spinner/loading-spinner.scss new file mode 100644 index 0000000000..051ab642c1 --- /dev/null +++ b/plugins/main/public/components/common/loading-spinner/loading-spinner.scss @@ -0,0 +1,4 @@ +.discoverNoResults { + display: flex; + align-items: center; +} diff --git a/plugins/main/public/components/common/loading-spinner/loading-spinner.tsx b/plugins/main/public/components/common/loading-spinner/loading-spinner.tsx new file mode 100644 index 0000000000..5f1d8567fb --- /dev/null +++ b/plugins/main/public/components/common/loading-spinner/loading-spinner.tsx @@ -0,0 +1,21 @@ +import './loading-spinner.scss'; +import React from 'react'; +import { EuiTitle, EuiPanel, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedMessage } from '@osd/i18n/react'; + +export function LoadingSpinner() { + return ( + + } + title={ + +

+ +

+
+ } + /> +
+ ); +} diff --git a/plugins/main/public/components/common/no-results/no-results.tsx b/plugins/main/public/components/common/no-results/no-results.tsx new file mode 100644 index 0000000000..babfd51d32 --- /dev/null +++ b/plugins/main/public/components/common/no-results/no-results.tsx @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; + +import { EuiCallOut, EuiPanel } from '@elastic/eui'; + +interface Props { + message?: string; +} + +export const DiscoverNoResults = ({ message }: Props) => { + return ( + + + + ) + } + color='warning' + iconType='help' + data-test-subj='discoverNoResults' + /> + + + ); +}; diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index 5c762fbc2f..27137a0803 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useLayoutEffect } from 'react'; import { SearchBarProps, TimeRange, @@ -61,7 +61,7 @@ const useSearchBarConfiguration = ( } } - useEffect(() => { + useLayoutEffect(() => { setTimeout(() => { // this is a workaround to hide the close button on fixed filters via vanilla js hideRemoveFilter(); 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 3a35724454..2efd1f289d 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -22,8 +22,8 @@ import { import { SearchResponse } from '../../../../../../src/core/server'; import { useDocViewer } from '../doc-viewer'; import DocViewer from '../doc-viewer/doc-viewer'; -import { DiscoverNoResults } from '../../overview/vulnerabilities/common/components/no_results'; -import { LoadingSpinner } from '../../overview/vulnerabilities/common/components/loading_spinner'; +import { DiscoverNoResults } from '../no-results/no-results'; +import { LoadingSpinner } from '../loading-spinner/loading-spinner'; import { useDataGrid, tDataGridColumn, exportSearchToCSV } from '../data-grid'; import { ErrorHandler, @@ -253,7 +253,6 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { {}} tooltip={ results?.hits?.total && results?.hits?.total > MAX_ENTRIES_PER_QUERY diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index b97d7c33db..abff592dc0 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -184,6 +184,7 @@ const InventoryVulsComponent = () => { showDatePicker={false} showQueryInput={true} showQueryBar={true} + showSaveQuery={true} />
)} @@ -200,7 +201,6 @@ const InventoryVulsComponent = () => { { }} tooltip={ results?.hits?.total && results?.hits?.total > MAX_ENTRIES_PER_QUERY diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index 4c0fa32952..cb4d17e82c 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -93,6 +93,7 @@ const DashboardVulsComponent: React.FC = () => { showDatePicker={false} showQueryInput={true} showQueryBar={true} + showSaveQuery={true} /> }