From f57bceb9bf1912aba111509aa8e58e9c169ab8e4 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Tue, 10 Sep 2024 17:00:41 -0300 Subject: [PATCH 01/24] refactor: Update default pagination options and sorting columns --- .../common/data-grid/use-data-grid.test.tsx | 38 ++++--- .../common/data-grid/use-data-grid.ts | 104 +++++++++--------- 2 files changed, 73 insertions(+), 69 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx b/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx index 3d6cdc7d5f..3329a1b90b 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx +++ b/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx @@ -3,21 +3,23 @@ import { renderHook } from '@testing-library/react-hooks'; import React from 'react'; describe('useDataGrid hook', () => { - it('should return override the numbers of rows per page', () => { - - const dataGridProps: tDataGridProps = { - indexPattern: 'mocked-index-pattern', - results: {}, - defaultColumns: [], - DocViewInspectButton: () =>
, - ariaLabelledBy: '', - pagination: { - pageSize: 10, - pageSizeOptions: [10, 20, 30], - } - } - const { result } = renderHook(() => useDataGrid(dataGridProps)); - expect(result.current.pagination.pageSize).toEqual(10); - expect(result.current.pagination.pageSizeOptions).toEqual([10, 20, 30]); - }) -}); \ No newline at end of file + it('should return override the numbers of rows per page', () => { + const dataGridProps: tDataGridProps = { + // @ts-expect-error Type 'string' is not assignable to type 'IndexPattern' + indexPattern: 'mocked-index-pattern', + // @ts-expect-error Type '{}' is missing the following properties from type 'SearchResponse': took, timed_out, _shards, hits + results: {}, + defaultColumns: [], + DocViewInspectButton: () =>
, + ariaLabelledBy: '', + pagination: { + pageIndex: 0, + pageSize: 15, + pageSizeOptions: [15, 25, 50, 100], + }, + }; + const { result } = renderHook(() => useDataGrid(dataGridProps)); + expect(result.current?.pagination?.pageSize).toEqual(15); + expect(result.current?.pagination?.pageSizeOptions).toEqual([15, 25, 50, 100]); + }); +}); diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts index ab0235bedf..995693c43a 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.ts +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -4,36 +4,45 @@ import { EuiDataGridProps, EuiDataGridSorting, } from '@elastic/eui'; -import React, { useEffect, useMemo, useState, Fragment } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { SearchResponse } from '@opensearch-project/opensearch/api/types'; // ToDo: check how create this methods -import { - parseData, - getFieldFormatted, - parseColumns, -} from './data-grid-service'; +import { parseData, getFieldFormatted, parseColumns } from './data-grid-service'; import { IndexPattern } from '../../../../../../src/plugins/data/common'; +import { EuiDataGridPaginationProps } from '@opensearch-project/oui'; + +export interface PaginationOptions + extends Pick {} + +type SortingColumns = EuiDataGridSorting['columns']; const MAX_ENTRIES_PER_QUERY = 10000; -const DEFAULT_PAGE_SIZE_OPTIONS = [20, 50, 100]; +const DEFAULT_PAGE_INDEX = 0; +const DEFAULT_PAGE_SIZE = 15; +const DEFAULT_PAGE_SIZE_OPTIONS = [DEFAULT_PAGE_SIZE, 25, 50, 100]; +const DEFAULT_PAGINATION_OPTIONS: PaginationOptions = { + pageIndex: DEFAULT_PAGE_INDEX, + pageSize: DEFAULT_PAGE_SIZE, + pageSizeOptions: DEFAULT_PAGE_SIZE_OPTIONS, +}; + +export interface RenderColumn { + render: (value: any, rowItem: object) => string | React.ReactNode; +} -export type tDataGridColumn = { - render?: (value: any, rowItem: object) => string | React.ReactNode; -} & EuiDataGridColumn; +export type tDataGridColumn = Partial & EuiDataGridColumn; -export type tDataGridRenderColumn = Required> & - Omit; +export type tDataGridRenderColumn = RenderColumn & EuiDataGridColumn; export type tDataGridProps = { indexPattern: IndexPattern; results: SearchResponse; defaultColumns: tDataGridColumn[]; renderColumns?: tDataGridRenderColumn[]; - DocViewInspectButton: ({ - rowIndex, - }: EuiDataGridCellValueElementProps) => React.JSX.Element; + DocViewInspectButton: ({ rowIndex }: EuiDataGridCellValueElementProps) => React.JSX.Element; ariaLabelledBy: string; - pagination?: Partial; + useDefaultPagination?: boolean; + pagination?: typeof DEFAULT_PAGINATION_OPTIONS; }; export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { @@ -43,22 +52,19 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { results, defaultColumns, renderColumns, - pagination: defaultPagination, + useDefaultPagination = false, + pagination: paginationProps = {}, } = props; /** Columns **/ - const [columns, setColumns] = useState(defaultColumns); - const [columnVisibility, setVisibility] = useState(() => - columns.map(({ id }) => id), - ); + const [columns /* , setColumns */] = useState(defaultColumns); + const [columnVisibility, setVisibility] = useState(() => columns.map(({ id }) => id)); /** Rows */ const [rows, setRows] = useState([]); const rowCount = results ? (results?.hits?.total as number) : 0; /** Sorting **/ // get default sorting from default columns const getDefaultSorting = () => { - const defaultSort = columns.find( - column => column.isSortable || column.defaultSortDirection, - ); + const defaultSort = columns.find((column) => column.isSortable || column.defaultSortDirection); return defaultSort ? [ { @@ -68,40 +74,42 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { ] : []; }; - const defaultSorting: EuiDataGridSorting['columns'] = getDefaultSorting(); + const defaultSorting: SortingColumns = getDefaultSorting(); const [sortingColumns, setSortingColumns] = useState(defaultSorting); - const onSort = sortingColumns => { + const onSort = (sortingColumns: SortingColumns) => { setSortingColumns(sortingColumns); }; /** Pagination **/ - const [pagination, setPagination] = useState( - defaultPagination || { - pageIndex: 0, - pageSize: 20, - pageSizeOptions: DEFAULT_PAGE_SIZE_OPTIONS, - }, + const [pagination, setPagination] = useState( + useDefaultPagination + ? DEFAULT_PAGINATION_OPTIONS + : { + ...DEFAULT_PAGINATION_OPTIONS, + ...paginationProps, + } ); + const onChangeItemsPerPage = useMemo( - () => pageSize => - setPagination(pagination => ({ + () => (pageSize: number) => + setPagination((pagination) => ({ ...pagination, pageSize, pageIndex: 0, })), - [rows, rowCount], + [rows, rowCount] ); - const onChangePage = pageIndex => - setPagination(pagination => ({ ...pagination, pageIndex })); + const onChangePage = (pageIndex: number) => + setPagination((pagination) => ({ ...pagination, pageIndex })); useEffect(() => { setRows(results?.hits?.hits || []); }, [results, results?.hits, results?.hits?.total]); useEffect(() => { - setPagination(pagination => ({ ...pagination, pageIndex: 0 })); + setPagination((pagination) => ({ ...pagination, pageIndex: 0 })); }, [rowCount]); - const renderCellValue = ({ rowIndex, columnId, setCellProps }) => { + const renderCellValue = ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => { const rowsParsed = parseData(rows); // On the context data always is stored the current page data (pagination) // then the rowIndex is relative to the current page @@ -111,22 +119,17 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { relativeRowIndex, columnId, indexPattern, - rowsParsed, + rowsParsed ); // check if column have render method initialized - const column = columns.find(column => column.id === columnId); + const column = columns.find((column) => column.id === columnId); if (column && column.render) { return column.render(fieldFormatted, rowsParsed[relativeRowIndex]); } // check if column have render method in renderColumns prop - const renderColumn = renderColumns?.find( - column => column.id === columnId, - ); + const renderColumn = renderColumns?.find((column) => column.id === columnId); if (renderColumn) { - return renderColumn.render( - fieldFormatted, - rowsParsed[relativeRowIndex], - ); + return renderColumn.render(fieldFormatted, rowsParsed[relativeRowIndex]); } return fieldFormatted; @@ -139,7 +142,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { { id: 'inspectCollapseColumn', headerCellRender: () => null, - rowCellRender: props => + rowCellRender: (props: EuiDataGridCellValueElementProps) => DocViewInspectButton({ ...props, rowIndex: props.rowIndex % pagination.pageSize, @@ -158,8 +161,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { }, renderCellValue: renderCellValue, leadingControlColumns: leadingControlColumns, - rowCount: - rowCount < MAX_ENTRIES_PER_QUERY ? rowCount : MAX_ENTRIES_PER_QUERY, + rowCount: rowCount < MAX_ENTRIES_PER_QUERY ? rowCount : MAX_ENTRIES_PER_QUERY, sorting: { columns: sortingColumns, onSort }, pagination: { ...pagination, From 61296c3f9a16fbe5a667b0721257dd5451e8454d Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Tue, 10 Sep 2024 17:01:09 -0300 Subject: [PATCH 02/24] feat: Add tsconfig.json for main plugin --- plugins/main/tsconfig.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 plugins/main/tsconfig.json diff --git a/plugins/main/tsconfig.json b/plugins/main/tsconfig.json new file mode 100644 index 0000000000..b7ff5b1390 --- /dev/null +++ b/plugins/main/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "./**/*" + ] +} \ No newline at end of file From 9e1f3f375a33760333174107fbce266b0176882a Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Tue, 10 Sep 2024 17:03:43 -0300 Subject: [PATCH 03/24] style: Remove unnecessary white-space and formatting --- .../common/wazuh-data-grid/wz-data-grid.tsx | 52 +++++-------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx index fa89cda502..adb7bbd0dc 100644 --- a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx +++ b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx @@ -5,7 +5,6 @@ import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiDataGrid, - EuiButtonEmpty, EuiFlyout, EuiFlyoutHeader, EuiTitle, @@ -17,18 +16,12 @@ import { exportSearchToCSV, tDataGridColumn, getAllCustomRenders, + PaginationOptions, } from '../data-grid'; import { getWazuhCorePlugin } from '../../../kibana-services'; -import { - IndexPattern, - SearchResponse, -} from '../../../../../../src/plugins/data/public'; +import { IndexPattern, SearchResponse } from '../../../../../../src/plugins/data/public'; import { useDocViewer } from '../doc-viewer'; -import { - ErrorHandler, - ErrorFactory, - HttpError, -} from '../../../react-services/error-management'; +import { ErrorHandler, ErrorFactory, HttpError } from '../../../react-services/error-management'; import { LoadingSpinner } from '../loading-spinner/loading-spinner'; import { DiscoverNoResults } from '../no-results/no-results'; import { DocumentViewTableAndJson } from '../wazuh-discover/components/document-view-table-and-json'; @@ -44,18 +37,11 @@ export type tWazuhDataGridProps = { results: SearchResponse; defaultColumns: tDataGridColumn[]; isLoading: boolean; - defaultPagination: { - pageIndex: number; - pageSize: number; - pageSizeOptions: number[]; - }; + defaultPagination: PaginationOptions; query: any; exportFilters: tFilter[]; dateRange: TimeRange; - onChangePagination: (pagination: { - pageIndex: number; - pageSize: number; - }) => void; + onChangePagination: (pagination: { pageIndex: number; pageSize: number }) => void; onChangeSorting: (sorting: { columns: any[]; onSort: any }) => void; }; @@ -81,18 +67,16 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => { const rowClicked = results.hits.hits[index]; setInspectedHit(rowClicked); }, - [results], + [results] ); - const DocViewInspectButton = ({ - rowIndex, - }: EuiDataGridCellValueElementProps) => { + const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { const inspectHintMsg = 'Inspect document details'; return ( onClickInspectDoc(rowIndex)} - iconType='inspect' + iconType="inspect" aria-label={inspectHintMsg} /> @@ -128,9 +112,7 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => { indexPattern: indexPattern as IndexPattern, }); - const timeField = indexPattern?.timeFieldName - ? indexPattern.timeFieldName - : undefined; + const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; const onClickExportResults = async () => { const params = { @@ -165,7 +147,7 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => { ) : null} {!isLoading && results?.hits?.total > 0 ? ( -
+
{
) : null} {inspectedHit && ( - setInspectedHit(undefined)} size='m'> + setInspectedHit(undefined)} size="m"> - + - + From 4088177e190582d913bb2250a98e5a1eca032f67 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Tue, 10 Sep 2024 17:04:00 -0300 Subject: [PATCH 04/24] style: Simplify function parameters and formatting --- .../common/search-bar/use-search-bar.ts | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 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 8de3137690..5710333d40 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 @@ -18,17 +18,18 @@ type tUseSearchBarCustomInputs = { setTimeFilter?: (timeRange: TimeRange) => void; setQuery?: (query: Query) => void; onFiltersUpdated?: (filters: Filter[]) => void; - onQuerySubmitted?: ( - payload: { dateRange: TimeRange; query?: Query }, - isUpdate?: boolean, - ) => void; + onQuerySubmitted?: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; }; -export type tUseSearchBarProps = Partial & - tUseSearchBarCustomInputs; +export type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; // Output types type tUserSearchBarResponse = { - searchBarProps: Partial; + searchBarProps: Partial< + SearchBarProps & { + useDefaultBehaviors: boolean; + absoluteDateRange: TimeRange; + } + >; }; /** @@ -36,9 +37,7 @@ type tUserSearchBarResponse = { * @param props * @returns */ -const useSearchBarConfiguration = ( - props: tUseSearchBarProps, -): tUserSearchBarResponse => { +const useSearchBarConfiguration = (props: tUseSearchBarProps): tUserSearchBarResponse => { const { indexPattern, filters: defaultFilters, setFilters } = props; // dependencies @@ -56,12 +55,11 @@ const useSearchBarConfiguration = ( // This absoluteDateRange is used to ensure that the date range is the same when we make the // pagination request with relative dates like "Last 24 hours" const [absoluteDateRange, setAbsoluteDateRange] = useState( - transformDateRange(globalTimeFilter), + transformDateRange(globalTimeFilter) ); // states const [isLoading, setIsLoading] = useState(true); - const [indexPatternSelected, setIndexPatternSelected] = - useState(indexPattern); + const [indexPatternSelected, setIndexPatternSelected] = useState(indexPattern); useEffect(() => { if (indexPattern) { @@ -93,8 +91,7 @@ const useSearchBarConfiguration = ( * @returns */ const getDefaultIndexPattern = async (): Promise => { - const indexPatternService = getDataPlugin() - .indexPatterns as IndexPatternsContract; + const indexPatternService = getDataPlugin().indexPatterns as IndexPatternsContract; return await indexPatternService.getDefault(); }; @@ -116,21 +113,17 @@ const useSearchBarConfiguration = ( dateRangeFrom: timeFilter.from, dateRangeTo: timeFilter.to, onFiltersUpdated: (userFilters: Filter[]) => { - setFilters - ? setFilters(userFilters) - : console.warn('setFilters function is not defined'); + setFilters ? setFilters(userFilters) : console.warn('setFilters function is not defined'); props?.onFiltersUpdated && props?.onFiltersUpdated(userFilters); }, onQuerySubmit: ( payload: { dateRange: TimeRange; query?: Query }, - _isUpdate?: boolean, + _isUpdate?: boolean ): void => { const { dateRange, query } = payload; // its necessary execute setter to apply query filters // when the hook receives the dateRange use the setter instead the global setTimeFilter - props?.setTimeFilter - ? props?.setTimeFilter(dateRange) - : setGlobalTimeFilter(dateRange); + props?.setTimeFilter ? props?.setTimeFilter(dateRange) : setGlobalTimeFilter(dateRange); props?.setQuery ? props?.setQuery(query) : setQuery(query); props?.onQuerySubmitted && props?.onQuerySubmitted(payload); setTimeFilter(dateRange); From c6451dc4360c3d264a139c1e35801b601e6e7ffe Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Tue, 10 Sep 2024 17:04:10 -0300 Subject: [PATCH 05/24] style: Remove unnecessary whitespace and fix formatting --- .../common/wazuh-discover/wz-discover.tsx | 86 ++++++------------- 1 file changed, 28 insertions(+), 58 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 a57ef42b7a..99c7df6ba3 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -10,7 +10,6 @@ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, - EuiTitle, EuiPanel, } from '@elastic/eui'; import { IntlProvider } from 'react-intl'; @@ -18,24 +17,15 @@ import { IndexPattern } from '../../../../../../src/plugins/data/common'; import { SearchResponse } from '../../../../../../src/core/server'; import { DiscoverNoResults } from '../no-results/no-results'; import { LoadingSpinner } from '../loading-spinner/loading-spinner'; -import { - useDataGrid, - tDataGridColumn, - exportSearchToCSV, - getAllCustomRenders, -} from '../data-grid'; +import { useDataGrid, tDataGridColumn, exportSearchToCSV, getAllCustomRenders } from '../data-grid'; import { DocumentViewTableAndJson } from './components/document-view-table-and-json'; -import { - ErrorHandler, - ErrorFactory, - HttpError, -} from '../../../react-services/error-management'; +import { ErrorHandler, ErrorFactory, HttpError } from '../../../react-services/error-management'; import useSearchBar from '../search-bar/use-search-bar'; import { getPlugins } from '../../../kibana-services'; import { histogramChartInput } from './config/histogram-chart'; import { getWazuhCorePlugin } from '../../../kibana-services'; -const DashboardByRenderer = - getPlugins().dashboard.DashboardContainerByValueRenderer; + +const DashboardByRenderer = getPlugins().dashboard.DashboardContainerByValueRenderer; import './discover.scss'; import { withErrorBoundary } from '../hocs'; import { @@ -66,9 +56,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const [results, setResults] = useState({} as SearchResponse); const [inspectedHit, setInspectedHit] = useState(undefined); - const [indexPattern, setIndexPattern] = useState( - undefined, - ); + const [indexPattern, setIndexPattern] = useState(undefined); const [isExporting, setIsExporting] = useState(false); const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav(); @@ -92,18 +80,16 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const rowClicked = results.hits.hits[index]; setInspectedHit(rowClicked); }, - [results], + [results] ); - const DocViewInspectButton = ({ - rowIndex, - }: EuiDataGridCellValueElementProps) => { + const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { const inspectHintMsg = 'Inspect document details'; return ( onClickInspectDoc(rowIndex)} - iconType='inspect' + iconType="inspect" aria-label={inspectHintMsg} /> @@ -124,11 +110,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { results, indexPattern: indexPattern as IndexPattern, DocViewInspectButton, - pagination: { - pageIndex: 0, - pageSize: 15, - pageSizeOptions: [15, 25, 50, 100], - }, + useDefaultPagination: true, }); const { pagination, sorting, columnVisibility } = dataGridProps; @@ -144,8 +126,8 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { sorting, dateRange: absoluteDateRange, }) - .then(results => setResults(results)) - .catch(error => { + .then((results) => setResults(results)) + .catch((error) => { const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching data', @@ -160,9 +142,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { JSON.stringify(absoluteDateRange), ]); - const timeField = indexPattern?.timeFieldName - ? indexPattern.timeFieldName - : undefined; + const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; const onClickExportResults = async () => { const params = { @@ -191,20 +171,20 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { }; return ( - + {isDataSourceLoading ? ( ) : ( { ) : null}
0 - ? '' - : 'wz-no-display' + !isDataSourceLoading && dataSource && results?.hits?.total > 0 ? '' : 'wz-no-display' } > - - - + + + { fetchFilters, query, absoluteDateRange.from, - absoluteDateRange.to, + absoluteDateRange.to )} /> @@ -271,22 +244,19 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { {inspectedHit && ( - setInspectedHit(undefined)} size='m'> + setInspectedHit(undefined)} size="m"> - + - + From 7d36ad53a1909e51555226fda90fd8cf6d04e672 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Tue, 10 Sep 2024 17:04:42 -0300 Subject: [PATCH 06/24] style: Update quotes and remove unnecessary line breaks --- .../threat-hunting/dashboard/dashboard.tsx | 53 +++++++------------ 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx index 64e9081100..812dc6c8e8 100644 --- a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx @@ -20,11 +20,7 @@ import { EuiTitle, EuiButtonEmpty, } from '@elastic/eui'; -import { - ErrorFactory, - ErrorHandler, - HttpError, -} from '../../../../react-services/error-management'; +import { ErrorFactory, ErrorHandler, HttpError } from '../../../../react-services/error-management'; import { MAX_ENTRIES_PER_QUERY, exportSearchToCSV, @@ -32,8 +28,6 @@ import { } from '../../../common/data-grid/data-grid-service'; import { useDocViewer } from '../../../common/doc-viewer/use-doc-viewer'; import { useDataGrid } from '../../../common/data-grid/use-data-grid'; -import { HitsCounter } from '../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter'; -import { formatNumWithCommas } from '../../../../kibana-integrations/discover/application/helpers/format_number_with_commas'; import DocViewer from '../../../common/doc-viewer/doc-viewer'; import { withErrorBoundary } from '../../../common/hocs/error-boundary/with-error-boundary'; import './threat_hunting_dashboard.scss'; @@ -97,18 +91,16 @@ const DashboardTH: React.FC = () => { const rowClicked = results.hits.hits[index]; setInspectedHit(rowClicked); }, - [results], + [results] ); - const DocViewInspectButton = ({ - rowIndex, - }: EuiDataGridCellValueElementProps) => { + const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { const inspectHintMsg = 'Inspect document details'; return ( onClickInspectDoc(rowIndex)} - iconType='inspect' + iconType="inspect" aria-label={inspectHintMsg} /> @@ -120,7 +112,7 @@ const DashboardTH: React.FC = () => { defaultColumns: threatHuntingTableDefaultColumns, renderColumns: wzDiscoverRenderColumns, results, - indexPattern: dataSource?.indexPattern, + indexPattern: dataSource?.indexPattern as IndexPattern, DocViewInspectButton, }); @@ -128,12 +120,11 @@ const DashboardTH: React.FC = () => { const docViewerProps = useDocViewer({ doc: inspectedHit, - indexPattern: dataSource?.indexPattern, + indexPattern: dataSource?.indexPattern as IndexPattern, }); const pinnedAgent = - PatternDataSourceFilterManager.getPinnedAgentFilter(dataSource?.id!) - .length > 0; + PatternDataSourceFilterManager.getPinnedAgentFilter(dataSource?.id!).length > 0; useEffect(() => { const currentColumns = !pinnedAgent @@ -145,7 +136,7 @@ const DashboardTH: React.FC = () => { useReportingCommunicateSearchContext({ isSearching: isDataSourceLoading, totalResults: results?.hits?.total ?? 0, - indexPattern: dataSource?.indexPattern, + indexPattern: dataSource?.indexPattern as IndexPattern, filters: fetchFilters, query: query, time: absoluteDateRange, @@ -161,10 +152,10 @@ const DashboardTH: React.FC = () => { sorting, dateRange: absoluteDateRange, }) - .then(results => { + .then((results) => { setResults(results); }) - .catch(error => { + .catch((error) => { const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching data', @@ -212,7 +203,7 @@ const DashboardTH: React.FC = () => { ) : ( { ) : null}
0 - ? '' - : 'wz-no-display' + !isDataSourceLoading && dataSource && results?.hits?.total > 0 ? '' : 'wz-no-display' }`} > -
+
{ { ) : null}
{inspectedHit && ( - setInspectedHit(undefined)} size='m'> + setInspectedHit(undefined)} size="m"> - + - + From 24b427291b722f2125466e4c67b1786e8ebc4d76 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 11:11:05 -0300 Subject: [PATCH 07/24] style: Update pagination to use default setting --- .../components/common/wazuh-data-grid/wz-data-grid.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx index adb7bbd0dc..9396ba6c74 100644 --- a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx +++ b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx @@ -90,11 +90,7 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => { results, indexPattern: indexPattern as IndexPattern, DocViewInspectButton, - pagination: defaultPagination || { - pageIndex: 0, - pageSize: 15, - pageSizeOptions: [15, 25, 50, 100], - }, + useDefaultPagination: true, }); const { pagination, sorting, columnVisibility } = dataGridProps; From 41eafb0025c6b780f892df43f2d196ade2136277 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 11:12:50 -0300 Subject: [PATCH 08/24] feat: Add function to get cell actions in data grid --- .../common/data-grid/get-cell-actions.tsx | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 plugins/main/public/components/common/data-grid/get-cell-actions.tsx diff --git a/plugins/main/public/components/common/data-grid/get-cell-actions.tsx b/plugins/main/public/components/common/data-grid/get-cell-actions.tsx new file mode 100644 index 0000000000..201d1bcd4a --- /dev/null +++ b/plugins/main/public/components/common/data-grid/get-cell-actions.tsx @@ -0,0 +1,80 @@ +import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React from 'react'; +import { IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; +import { FILTER_OPERATOR } from '../data-source'; + +// https://github.com/opensearch-project/OpenSearch-Dashboards/blob/2.13.0/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_actions.tsx +export function getCellActions( + field: IFieldType, + indexPattern: IndexPattern, + rows: any[], + onFilter: ( + columndId: string, + value: any, + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + ) => void +) { + let cellActions: + | (({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => React.JSX.Element)[] + | undefined = undefined; + if (field.filterable) { + cellActions = [ + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { + const filterForValueText = i18n.translate('discover.filterForValue', { + defaultMessage: 'Filter for value', + }); + const filterForValueLabel = i18n.translate('discover.filterForValueLabel', { + defaultMessage: 'Filter for value: {value}', + values: { value: columnId }, + }); + + return ( + { + const row = rows[rowIndex]; + const flattened = indexPattern.flattenHit(row); + + if (flattened) { + onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS); + } + }} + iconType="plusInCircle" + aria-label={filterForValueLabel} + data-test-subj="filterForValue" + > + {filterForValueText} + + ); + }, + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { + const filterOutValueText = i18n.translate('discover.filterOutValue', { + defaultMessage: 'Filter out value', + }); + const filterOutValueLabel = i18n.translate('discover.filterOutValueLabel', { + defaultMessage: 'Filter out value: {value}', + values: { value: columnId }, + }); + + return ( + { + const row = rows[rowIndex]; + const flattened = indexPattern.flattenHit(row); + + if (flattened) { + onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS_NOT); + } + }} + iconType="minusInCircle" + aria-label={filterOutValueLabel} + data-test-subj="filterOutValue" + > + {filterOutValueText} + + ); + }, + ]; + } + return cellActions; +} From 26626a61c3c484e4089a32fb9b95d4ea0cbdcd70 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 11:14:59 -0300 Subject: [PATCH 09/24] feat: Add filtering functionality to data grid --- .../common/data-grid/data-grid-service.ts | 27 +++++++++++++++++-- .../common/data-grid/use-data-grid.ts | 17 +++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 426f346e4f..36fb4dbf4a 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -2,10 +2,11 @@ import { SearchResponse } from '../../../../../../src/core/server'; import * as FileSaver from '../../../services/file-saver'; import { beautifyDate } from '../../agents/vuls/inventory/lib'; import { SearchParams, search } from '../search-bar/search-bar-service'; -import { IFieldType } from '../../../../../../src/plugins/data/common'; +import { Filter, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; export const MAX_ENTRIES_PER_QUERY = 10000; -import { EuiDataGridColumn } from '@elastic/eui'; import { tDataGridColumn } from './use-data-grid'; +import { getCellActions } from './get-cell-actions'; +import { FILTER_OPERATOR, PatternDataSourceFilterManager } from '../data-source'; export const parseData = ( resultsHits: SearchResponse['hits']['hits'], @@ -172,6 +173,10 @@ export const exportSearchToCSV = async ( export const parseColumns = ( fields: IFieldType[], defaultColumns: tDataGridColumn[] = [], + indexPattern: IndexPattern, + rows: any[], + filters: Filter[], + setFilters: (filters: Filter[]) => void ): tDataGridColumn[] => { // remove _source field becuase is a object field and is not supported // merge the properties of the field with the default columns @@ -194,6 +199,24 @@ export const parseColumns = ( showHide: true, }, ...defaultColumn, + cellActions: getCellActions( + field, + indexPattern, + rows, + ( + columndId: string, + value: any, + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + ) => { + const newFilter = PatternDataSourceFilterManager.createFilter( + operation, + columndId, + value, + indexPattern.id ?? '' + ); + setFilters([...filters, newFilter]); + } + ), }; }) as tDataGridColumn[]; return columns; diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts index 995693c43a..7c7c61b01e 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.ts +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -8,7 +8,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { SearchResponse } from '@opensearch-project/opensearch/api/types'; // ToDo: check how create this methods import { parseData, getFieldFormatted, parseColumns } from './data-grid-service'; -import { IndexPattern } from '../../../../../../src/plugins/data/common'; +import { Filter, IndexPattern } from '../../../../../../src/plugins/data/common'; import { EuiDataGridPaginationProps } from '@opensearch-project/oui'; export interface PaginationOptions @@ -43,6 +43,8 @@ export type tDataGridProps = { ariaLabelledBy: string; useDefaultPagination?: boolean; pagination?: typeof DEFAULT_PAGINATION_OPTIONS; + filters?: Filter[]; + setFilters?: (filters: Filter[]) => void; }; export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { @@ -54,6 +56,8 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { renderColumns, useDefaultPagination = false, pagination: paginationProps = {}, + filters, + setFilters, } = props; /** Columns **/ const [columns /* , setColumns */] = useState(defaultColumns); @@ -154,7 +158,14 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { return { 'aria-labelledby': props.ariaLabelledBy, - columns: parseColumns(indexPattern?.fields || [], defaultColumns), + columns: parseColumns( + indexPattern?.fields || [], + defaultColumns, + indexPattern, + rows, + filters, + setFilters + ), columnVisibility: { visibleColumns: columnVisibility, setVisibleColumns: setVisibility, @@ -168,5 +179,5 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { onChangeItemsPerPage: onChangeItemsPerPage, onChangePage: onChangePage, }, - }; + } as EuiDataGridProps; }; From e635f97479e5c442ba5008e2211ebda3058af740 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 11:15:20 -0300 Subject: [PATCH 10/24] feat: Add filters and setFilters to data grid props --- .../public/components/common/wazuh-discover/wz-discover.tsx | 2 ++ .../components/overview/threat-hunting/dashboard/dashboard.tsx | 2 ++ .../overview/vulnerabilities/dashboards/inventory/inventory.tsx | 2 ++ 3 files changed, 6 insertions(+) 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 99c7df6ba3..b5badfe5de 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -111,6 +111,8 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { indexPattern: indexPattern as IndexPattern, DocViewInspectButton, useDefaultPagination: true, + filters, + setFilters, }); const { pagination, sorting, columnVisibility } = dataGridProps; diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx index 812dc6c8e8..cd00ad54a0 100644 --- a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx @@ -114,6 +114,8 @@ const DashboardTH: React.FC = () => { results, indexPattern: dataSource?.indexPattern as IndexPattern, DocViewInspectButton, + filters, + setFilters, }); const { pagination, sorting, columnVisibility } = dataGridProps; 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 8b8b9fd247..377438dc3e 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -111,6 +111,8 @@ const InventoryVulsComponent = () => { results, indexPattern: indexPattern as IndexPattern, DocViewInspectButton, + filters, + setFilters, }); const { pagination, sorting, columnVisibility } = dataGridProps; From 35ede56fed1c3e3bc4f170626e8de227f457d10d Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 11:15:29 -0300 Subject: [PATCH 11/24] refactor: Simplify arrow function parameters and formatting --- .../common/data-grid/data-grid-service.ts | 60 +++++++------------ 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 36fb4dbf4a..3808a3a20e 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -8,10 +8,8 @@ import { tDataGridColumn } from './use-data-grid'; import { getCellActions } from './get-cell-actions'; import { FILTER_OPERATOR, PatternDataSourceFilterManager } from '../data-source'; -export const parseData = ( - resultsHits: SearchResponse['hits']['hits'], -): any[] => { - const data = resultsHits.map(hit => { +export const parseData = (resultsHits: SearchResponse['hits']['hits']): any[] => { + const data = resultsHits.map((hit) => { if (!hit) { return {}; } @@ -28,20 +26,15 @@ export const parseData = ( return data; }; -export const getFieldFormatted = ( - rowIndex, - columnId, - indexPattern, - rowsParsed, -) => { - const field = indexPattern.fields.find(field => field.name === columnId); +export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => { + const field = indexPattern.fields.find((field) => field.name === columnId); let fieldValue = null; if (columnId.includes('.')) { // when the column is a nested field. The column could have 2 to n levels // get dinamically the value of the nested field const nestedFields = columnId.split('.'); fieldValue = rowsParsed[rowIndex]; - nestedFields.forEach(field => { + nestedFields.forEach((field) => { if (fieldValue) { fieldValue = fieldValue[field]; } @@ -74,25 +67,14 @@ export const getFieldFormatted = ( }; // receive search params -export const exportSearchToCSV = async ( - params: SearchParams, -): Promise => { +export const exportSearchToCSV = async (params: SearchParams): Promise => { const DEFAULT_MAX_SIZE_PER_CALL = 1000; - const { - indexPattern, - filters = [], - query, - sorting, - fields, - pagination, - } = params; + const { indexPattern, filters = [], query, sorting, fields, pagination } = params; // when the pageSize is greater than the default max size per call (10000) // then we need to paginate the search const mustPaginateSearch = pagination?.pageSize && pagination?.pageSize > DEFAULT_MAX_SIZE_PER_CALL; - const pageSize = mustPaginateSearch - ? DEFAULT_MAX_SIZE_PER_CALL - : pagination?.pageSize; + const pageSize = mustPaginateSearch ? DEFAULT_MAX_SIZE_PER_CALL : pagination?.pageSize; const totalHits = pagination?.pageSize || DEFAULT_MAX_SIZE_PER_CALL; let pageIndex = params.pagination?.pageIndex || 0; let hitsCount = 0; @@ -123,13 +105,13 @@ export const exportSearchToCSV = async ( } const resultsFields = fields; - const data = allHits.map(hit => { + const data = allHits.map((hit) => { // check if the field type is a date const dateFields = indexPattern.fields.getByType('date'); - const dateFieldsNames = dateFields.map(field => field.name); + const dateFieldsNames = dateFields.map((field) => field.name); const flattenHit = indexPattern.flattenHit(hit); // replace the date fields with the formatted date - dateFieldsNames.forEach(field => { + dateFieldsNames.forEach((field) => { if (flattenHit[field]) { flattenHit[field] = beautifyDate(flattenHit[field]); } @@ -144,8 +126,8 @@ export const exportSearchToCSV = async ( if (!data || data.length === 0) return; const parsedData = data - .map(row => { - const parsedRow = resultsFields?.map(field => { + .map((row) => { + const parsedRow = resultsFields?.map((field) => { const value = row[field]; if (value === undefined || value === null) { return ''; @@ -185,11 +167,9 @@ export const parseColumns = ( } const columns = fields - .filter(field => field.name !== '_source') - .map(field => { - const defaultColumn = defaultColumns.find( - column => column.id === field.name, - ); + .filter((field) => field.name !== '_source') + .map((field) => { + const defaultColumn = defaultColumns.find((column) => column.id === field.name); return { ...field, id: field.name, @@ -231,12 +211,12 @@ export const parseColumns = ( */ export const getAllCustomRenders = ( customColumns: tDataGridColumn[], - discoverColumns: tDataGridColumn[], + discoverColumns: tDataGridColumn[] ): tDataGridColumn[] => { - const customColumnsWithRender = customColumns.filter(column => column.render); - const allColumns = discoverColumns.map(column => { + const customColumnsWithRender = customColumns.filter((column) => column.render); + const allColumns = discoverColumns.map((column) => { const customColumn = customColumnsWithRender.find( - customColumn => customColumn.id === column.id, + (customColumn) => customColumn.id === column.id ); return customColumn || column; }); From 4390051d75a91104034d59ebd5b7c114ab76f9b8 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 11:36:05 -0300 Subject: [PATCH 12/24] refactor: Update code formatting for better readability --- .../common/data-grid/data-grid-service.ts | 79 +++++++++++++------ .../common/data-grid/get-cell-actions.tsx | 45 +++++++---- .../common/data-grid/use-data-grid.test.tsx | 4 +- .../common/data-grid/use-data-grid.ts | 71 ++++++++++++----- .../common/search-bar/use-search-bar.ts | 30 ++++--- .../common/wazuh-data-grid/wz-data-grid.tsx | 44 ++++++++--- .../common/wazuh-discover/wz-discover.tsx | 78 ++++++++++++------ .../threat-hunting/dashboard/dashboard.tsx | 45 +++++++---- plugins/main/tsconfig.json | 6 +- 9 files changed, 273 insertions(+), 129 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 3808a3a20e..312907de1d 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -2,14 +2,23 @@ import { SearchResponse } from '../../../../../../src/core/server'; import * as FileSaver from '../../../services/file-saver'; import { beautifyDate } from '../../agents/vuls/inventory/lib'; import { SearchParams, search } from '../search-bar/search-bar-service'; -import { Filter, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; +import { + Filter, + IFieldType, + IndexPattern, +} from '../../../../../../src/plugins/data/common'; export const MAX_ENTRIES_PER_QUERY = 10000; import { tDataGridColumn } from './use-data-grid'; import { getCellActions } from './get-cell-actions'; -import { FILTER_OPERATOR, PatternDataSourceFilterManager } from '../data-source'; +import { + FILTER_OPERATOR, + PatternDataSourceFilterManager, +} from '../data-source'; -export const parseData = (resultsHits: SearchResponse['hits']['hits']): any[] => { - const data = resultsHits.map((hit) => { +export const parseData = ( + resultsHits: SearchResponse['hits']['hits'], +): any[] => { + const data = resultsHits.map(hit => { if (!hit) { return {}; } @@ -26,15 +35,20 @@ export const parseData = (resultsHits: SearchResponse['hits']['hits']): any[] => return data; }; -export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => { - const field = indexPattern.fields.find((field) => field.name === columnId); +export const getFieldFormatted = ( + rowIndex, + columnId, + indexPattern, + rowsParsed, +) => { + const field = indexPattern.fields.find(field => field.name === columnId); let fieldValue = null; if (columnId.includes('.')) { // when the column is a nested field. The column could have 2 to n levels // get dinamically the value of the nested field const nestedFields = columnId.split('.'); fieldValue = rowsParsed[rowIndex]; - nestedFields.forEach((field) => { + nestedFields.forEach(field => { if (fieldValue) { fieldValue = fieldValue[field]; } @@ -67,14 +81,25 @@ export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) }; // receive search params -export const exportSearchToCSV = async (params: SearchParams): Promise => { +export const exportSearchToCSV = async ( + params: SearchParams, +): Promise => { const DEFAULT_MAX_SIZE_PER_CALL = 1000; - const { indexPattern, filters = [], query, sorting, fields, pagination } = params; + const { + indexPattern, + filters = [], + query, + sorting, + fields, + pagination, + } = params; // when the pageSize is greater than the default max size per call (10000) // then we need to paginate the search const mustPaginateSearch = pagination?.pageSize && pagination?.pageSize > DEFAULT_MAX_SIZE_PER_CALL; - const pageSize = mustPaginateSearch ? DEFAULT_MAX_SIZE_PER_CALL : pagination?.pageSize; + const pageSize = mustPaginateSearch + ? DEFAULT_MAX_SIZE_PER_CALL + : pagination?.pageSize; const totalHits = pagination?.pageSize || DEFAULT_MAX_SIZE_PER_CALL; let pageIndex = params.pagination?.pageIndex || 0; let hitsCount = 0; @@ -105,13 +130,13 @@ export const exportSearchToCSV = async (params: SearchParams): Promise => } const resultsFields = fields; - const data = allHits.map((hit) => { + const data = allHits.map(hit => { // check if the field type is a date const dateFields = indexPattern.fields.getByType('date'); - const dateFieldsNames = dateFields.map((field) => field.name); + const dateFieldsNames = dateFields.map(field => field.name); const flattenHit = indexPattern.flattenHit(hit); // replace the date fields with the formatted date - dateFieldsNames.forEach((field) => { + dateFieldsNames.forEach(field => { if (flattenHit[field]) { flattenHit[field] = beautifyDate(flattenHit[field]); } @@ -126,8 +151,8 @@ export const exportSearchToCSV = async (params: SearchParams): Promise => if (!data || data.length === 0) return; const parsedData = data - .map((row) => { - const parsedRow = resultsFields?.map((field) => { + .map(row => { + const parsedRow = resultsFields?.map(field => { const value = row[field]; if (value === undefined || value === null) { return ''; @@ -158,7 +183,7 @@ export const parseColumns = ( indexPattern: IndexPattern, rows: any[], filters: Filter[], - setFilters: (filters: Filter[]) => void + setFilters: (filters: Filter[]) => void, ): tDataGridColumn[] => { // remove _source field becuase is a object field and is not supported // merge the properties of the field with the default columns @@ -167,9 +192,11 @@ export const parseColumns = ( } const columns = fields - .filter((field) => field.name !== '_source') - .map((field) => { - const defaultColumn = defaultColumns.find((column) => column.id === field.name); + .filter(field => field.name !== '_source') + .map(field => { + const defaultColumn = defaultColumns.find( + column => column.id === field.name, + ); return { ...field, id: field.name, @@ -186,16 +213,16 @@ export const parseColumns = ( ( columndId: string, value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, ) => { const newFilter = PatternDataSourceFilterManager.createFilter( operation, columndId, value, - indexPattern.id ?? '' + indexPattern.id ?? '', ); setFilters([...filters, newFilter]); - } + }, ), }; }) as tDataGridColumn[]; @@ -211,12 +238,12 @@ export const parseColumns = ( */ export const getAllCustomRenders = ( customColumns: tDataGridColumn[], - discoverColumns: tDataGridColumn[] + discoverColumns: tDataGridColumn[], ): tDataGridColumn[] => { - const customColumnsWithRender = customColumns.filter((column) => column.render); - const allColumns = discoverColumns.map((column) => { + const customColumnsWithRender = customColumns.filter(column => column.render); + const allColumns = discoverColumns.map(column => { const customColumn = customColumnsWithRender.find( - (customColumn) => customColumn.id === column.id + customColumn => customColumn.id === column.id, ); return customColumn || column; }); diff --git a/plugins/main/public/components/common/data-grid/get-cell-actions.tsx b/plugins/main/public/components/common/data-grid/get-cell-actions.tsx index 201d1bcd4a..3081aef8c1 100644 --- a/plugins/main/public/components/common/data-grid/get-cell-actions.tsx +++ b/plugins/main/public/components/common/data-grid/get-cell-actions.tsx @@ -1,7 +1,10 @@ import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import React from 'react'; -import { IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; +import { + IFieldType, + IndexPattern, +} from '../../../../../../src/plugins/data/common'; import { FILTER_OPERATOR } from '../data-source'; // https://github.com/opensearch-project/OpenSearch-Dashboards/blob/2.13.0/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_actions.tsx @@ -12,11 +15,15 @@ export function getCellActions( onFilter: ( columndId: string, value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT - ) => void + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, + ) => void, ) { let cellActions: - | (({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => React.JSX.Element)[] + | (({ + rowIndex, + columnId, + Component, + }: EuiDataGridColumnCellActionProps) => React.JSX.Element)[] | undefined = undefined; if (field.filterable) { cellActions = [ @@ -24,10 +31,13 @@ export function getCellActions( const filterForValueText = i18n.translate('discover.filterForValue', { defaultMessage: 'Filter for value', }); - const filterForValueLabel = i18n.translate('discover.filterForValueLabel', { - defaultMessage: 'Filter for value: {value}', - values: { value: columnId }, - }); + const filterForValueLabel = i18n.translate( + 'discover.filterForValueLabel', + { + defaultMessage: 'Filter for value: {value}', + values: { value: columnId }, + }, + ); return ( {filterForValueText} @@ -51,10 +61,13 @@ export function getCellActions( const filterOutValueText = i18n.translate('discover.filterOutValue', { defaultMessage: 'Filter out value', }); - const filterOutValueLabel = i18n.translate('discover.filterOutValueLabel', { - defaultMessage: 'Filter out value: {value}', - values: { value: columnId }, - }); + const filterOutValueLabel = i18n.translate( + 'discover.filterOutValueLabel', + { + defaultMessage: 'Filter out value: {value}', + values: { value: columnId }, + }, + ); return ( {filterOutValueText} diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx b/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx index 3329a1b90b..8e93598afb 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx +++ b/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx @@ -20,6 +20,8 @@ describe('useDataGrid hook', () => { }; const { result } = renderHook(() => useDataGrid(dataGridProps)); expect(result.current?.pagination?.pageSize).toEqual(15); - expect(result.current?.pagination?.pageSizeOptions).toEqual([15, 25, 50, 100]); + expect(result.current?.pagination?.pageSizeOptions).toEqual([ + 15, 25, 50, 100, + ]); }); }); diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts index 7c7c61b01e..fbc2435838 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.ts +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -7,12 +7,22 @@ import { import React, { useEffect, useMemo, useState } from 'react'; import { SearchResponse } from '@opensearch-project/opensearch/api/types'; // ToDo: check how create this methods -import { parseData, getFieldFormatted, parseColumns } from './data-grid-service'; -import { Filter, IndexPattern } from '../../../../../../src/plugins/data/common'; +import { + parseData, + getFieldFormatted, + parseColumns, +} from './data-grid-service'; +import { + Filter, + IndexPattern, +} from '../../../../../../src/plugins/data/common'; import { EuiDataGridPaginationProps } from '@opensearch-project/oui'; export interface PaginationOptions - extends Pick {} + extends Pick< + EuiDataGridPaginationProps, + 'pageIndex' | 'pageSize' | 'pageSizeOptions' + > {} type SortingColumns = EuiDataGridSorting['columns']; @@ -39,7 +49,9 @@ export type tDataGridProps = { results: SearchResponse; defaultColumns: tDataGridColumn[]; renderColumns?: tDataGridRenderColumn[]; - DocViewInspectButton: ({ rowIndex }: EuiDataGridCellValueElementProps) => React.JSX.Element; + DocViewInspectButton: ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => React.JSX.Element; ariaLabelledBy: string; useDefaultPagination?: boolean; pagination?: typeof DEFAULT_PAGINATION_OPTIONS; @@ -60,15 +72,20 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { setFilters, } = props; /** Columns **/ - const [columns /* , setColumns */] = useState(defaultColumns); - const [columnVisibility, setVisibility] = useState(() => columns.map(({ id }) => id)); + const [columns /* , setColumns */] = + useState(defaultColumns); + const [columnVisibility, setVisibility] = useState(() => + columns.map(({ id }) => id), + ); /** Rows */ const [rows, setRows] = useState([]); const rowCount = results ? (results?.hits?.total as number) : 0; /** Sorting **/ // get default sorting from default columns const getDefaultSorting = () => { - const defaultSort = columns.find((column) => column.isSortable || column.defaultSortDirection); + const defaultSort = columns.find( + column => column.isSortable || column.defaultSortDirection, + ); return defaultSort ? [ { @@ -84,36 +101,44 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { setSortingColumns(sortingColumns); }; /** Pagination **/ - const [pagination, setPagination] = useState( + const [pagination, setPagination] = useState< + typeof DEFAULT_PAGINATION_OPTIONS + >( useDefaultPagination ? DEFAULT_PAGINATION_OPTIONS : { ...DEFAULT_PAGINATION_OPTIONS, ...paginationProps, - } + }, ); const onChangeItemsPerPage = useMemo( () => (pageSize: number) => - setPagination((pagination) => ({ + setPagination(pagination => ({ ...pagination, pageSize, pageIndex: 0, })), - [rows, rowCount] + [rows, rowCount], ); const onChangePage = (pageIndex: number) => - setPagination((pagination) => ({ ...pagination, pageIndex })); + setPagination(pagination => ({ ...pagination, pageIndex })); useEffect(() => { setRows(results?.hits?.hits || []); }, [results, results?.hits, results?.hits?.total]); useEffect(() => { - setPagination((pagination) => ({ ...pagination, pageIndex: 0 })); + setPagination(pagination => ({ ...pagination, pageIndex: 0 })); }, [rowCount]); - const renderCellValue = ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => { + const renderCellValue = ({ + rowIndex, + columnId, + }: { + rowIndex: number; + columnId: string; + }) => { const rowsParsed = parseData(rows); // On the context data always is stored the current page data (pagination) // then the rowIndex is relative to the current page @@ -123,17 +148,22 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { relativeRowIndex, columnId, indexPattern, - rowsParsed + rowsParsed, ); // check if column have render method initialized - const column = columns.find((column) => column.id === columnId); + const column = columns.find(column => column.id === columnId); if (column && column.render) { return column.render(fieldFormatted, rowsParsed[relativeRowIndex]); } // check if column have render method in renderColumns prop - const renderColumn = renderColumns?.find((column) => column.id === columnId); + const renderColumn = renderColumns?.find( + column => column.id === columnId, + ); if (renderColumn) { - return renderColumn.render(fieldFormatted, rowsParsed[relativeRowIndex]); + return renderColumn.render( + fieldFormatted, + rowsParsed[relativeRowIndex], + ); } return fieldFormatted; @@ -164,7 +194,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { indexPattern, rows, filters, - setFilters + setFilters, ), columnVisibility: { visibleColumns: columnVisibility, @@ -172,7 +202,8 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { }, renderCellValue: renderCellValue, leadingControlColumns: leadingControlColumns, - rowCount: rowCount < MAX_ENTRIES_PER_QUERY ? rowCount : MAX_ENTRIES_PER_QUERY, + rowCount: + rowCount < MAX_ENTRIES_PER_QUERY ? rowCount : MAX_ENTRIES_PER_QUERY, sorting: { columns: sortingColumns, onSort }, pagination: { ...pagination, 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 5710333d40..b11c0e5113 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 @@ -18,9 +18,13 @@ type tUseSearchBarCustomInputs = { setTimeFilter?: (timeRange: TimeRange) => void; setQuery?: (query: Query) => void; onFiltersUpdated?: (filters: Filter[]) => void; - onQuerySubmitted?: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; + onQuerySubmitted?: ( + payload: { dateRange: TimeRange; query?: Query }, + isUpdate?: boolean, + ) => void; }; -export type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; +export type tUseSearchBarProps = Partial & + tUseSearchBarCustomInputs; // Output types type tUserSearchBarResponse = { @@ -37,7 +41,9 @@ type tUserSearchBarResponse = { * @param props * @returns */ -const useSearchBarConfiguration = (props: tUseSearchBarProps): tUserSearchBarResponse => { +const useSearchBarConfiguration = ( + props: tUseSearchBarProps, +): tUserSearchBarResponse => { const { indexPattern, filters: defaultFilters, setFilters } = props; // dependencies @@ -55,11 +61,12 @@ const useSearchBarConfiguration = (props: tUseSearchBarProps): tUserSearchBarRes // This absoluteDateRange is used to ensure that the date range is the same when we make the // pagination request with relative dates like "Last 24 hours" const [absoluteDateRange, setAbsoluteDateRange] = useState( - transformDateRange(globalTimeFilter) + transformDateRange(globalTimeFilter), ); // states const [isLoading, setIsLoading] = useState(true); - const [indexPatternSelected, setIndexPatternSelected] = useState(indexPattern); + const [indexPatternSelected, setIndexPatternSelected] = + useState(indexPattern); useEffect(() => { if (indexPattern) { @@ -91,7 +98,8 @@ const useSearchBarConfiguration = (props: tUseSearchBarProps): tUserSearchBarRes * @returns */ const getDefaultIndexPattern = async (): Promise => { - const indexPatternService = getDataPlugin().indexPatterns as IndexPatternsContract; + const indexPatternService = getDataPlugin() + .indexPatterns as IndexPatternsContract; return await indexPatternService.getDefault(); }; @@ -113,17 +121,21 @@ const useSearchBarConfiguration = (props: tUseSearchBarProps): tUserSearchBarRes dateRangeFrom: timeFilter.from, dateRangeTo: timeFilter.to, onFiltersUpdated: (userFilters: Filter[]) => { - setFilters ? setFilters(userFilters) : console.warn('setFilters function is not defined'); + setFilters + ? setFilters(userFilters) + : console.warn('setFilters function is not defined'); props?.onFiltersUpdated && props?.onFiltersUpdated(userFilters); }, onQuerySubmit: ( payload: { dateRange: TimeRange; query?: Query }, - _isUpdate?: boolean + _isUpdate?: boolean, ): void => { const { dateRange, query } = payload; // its necessary execute setter to apply query filters // when the hook receives the dateRange use the setter instead the global setTimeFilter - props?.setTimeFilter ? props?.setTimeFilter(dateRange) : setGlobalTimeFilter(dateRange); + props?.setTimeFilter + ? props?.setTimeFilter(dateRange) + : setGlobalTimeFilter(dateRange); props?.setQuery ? props?.setQuery(query) : setQuery(query); props?.onQuerySubmitted && props?.onQuerySubmitted(payload); setTimeFilter(dateRange); diff --git a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx index 9396ba6c74..c5d0e83cf8 100644 --- a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx +++ b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx @@ -19,9 +19,16 @@ import { PaginationOptions, } from '../data-grid'; import { getWazuhCorePlugin } from '../../../kibana-services'; -import { IndexPattern, SearchResponse } from '../../../../../../src/plugins/data/public'; +import { + IndexPattern, + SearchResponse, +} from '../../../../../../src/plugins/data/public'; import { useDocViewer } from '../doc-viewer'; -import { ErrorHandler, ErrorFactory, HttpError } from '../../../react-services/error-management'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../react-services/error-management'; import { LoadingSpinner } from '../loading-spinner/loading-spinner'; import { DiscoverNoResults } from '../no-results/no-results'; import { DocumentViewTableAndJson } from '../wazuh-discover/components/document-view-table-and-json'; @@ -41,7 +48,10 @@ export type tWazuhDataGridProps = { query: any; exportFilters: tFilter[]; dateRange: TimeRange; - onChangePagination: (pagination: { pageIndex: number; pageSize: number }) => void; + onChangePagination: (pagination: { + pageIndex: number; + pageSize: number; + }) => void; onChangeSorting: (sorting: { columns: any[]; onSort: any }) => void; }; @@ -67,16 +77,18 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => { const rowClicked = results.hits.hits[index]; setInspectedHit(rowClicked); }, - [results] + [results], ); - const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const DocViewInspectButton = ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => { const inspectHintMsg = 'Inspect document details'; return ( onClickInspectDoc(rowIndex)} - iconType="inspect" + iconType='inspect' aria-label={inspectHintMsg} /> @@ -108,7 +120,9 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => { indexPattern: indexPattern as IndexPattern, }); - const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; + const timeField = indexPattern?.timeFieldName + ? indexPattern.timeFieldName + : undefined; const onClickExportResults = async () => { const params = { @@ -143,7 +157,7 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => { ) : null} {!isLoading && results?.hits?.total > 0 ? ( -
+
{
) : null} {inspectedHit && ( - setInspectedHit(undefined)} size="m"> + setInspectedHit(undefined)} size='m'> - + - + 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 b5badfe5de..e8ac8842bd 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -17,15 +17,25 @@ import { IndexPattern } from '../../../../../../src/plugins/data/common'; import { SearchResponse } from '../../../../../../src/core/server'; import { DiscoverNoResults } from '../no-results/no-results'; import { LoadingSpinner } from '../loading-spinner/loading-spinner'; -import { useDataGrid, tDataGridColumn, exportSearchToCSV, getAllCustomRenders } from '../data-grid'; +import { + useDataGrid, + tDataGridColumn, + exportSearchToCSV, + getAllCustomRenders, +} from '../data-grid'; import { DocumentViewTableAndJson } from './components/document-view-table-and-json'; -import { ErrorHandler, ErrorFactory, HttpError } from '../../../react-services/error-management'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../react-services/error-management'; import useSearchBar from '../search-bar/use-search-bar'; import { getPlugins } from '../../../kibana-services'; import { histogramChartInput } from './config/histogram-chart'; import { getWazuhCorePlugin } from '../../../kibana-services'; -const DashboardByRenderer = getPlugins().dashboard.DashboardContainerByValueRenderer; +const DashboardByRenderer = + getPlugins().dashboard.DashboardContainerByValueRenderer; import './discover.scss'; import { withErrorBoundary } from '../hocs'; import { @@ -56,7 +66,9 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const [results, setResults] = useState({} as SearchResponse); const [inspectedHit, setInspectedHit] = useState(undefined); - const [indexPattern, setIndexPattern] = useState(undefined); + const [indexPattern, setIndexPattern] = useState( + undefined, + ); const [isExporting, setIsExporting] = useState(false); const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav(); @@ -80,16 +92,18 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { const rowClicked = results.hits.hits[index]; setInspectedHit(rowClicked); }, - [results] + [results], ); - const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const DocViewInspectButton = ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => { const inspectHintMsg = 'Inspect document details'; return ( onClickInspectDoc(rowIndex)} - iconType="inspect" + iconType='inspect' aria-label={inspectHintMsg} /> @@ -128,8 +142,8 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { sorting, dateRange: absoluteDateRange, }) - .then((results) => setResults(results)) - .catch((error) => { + .then(results => setResults(results)) + .catch(error => { const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching data', @@ -144,7 +158,9 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { JSON.stringify(absoluteDateRange), ]); - const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; + const timeField = indexPattern?.timeFieldName + ? indexPattern.timeFieldName + : undefined; const onClickExportResults = async () => { const params = { @@ -173,20 +189,20 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { }; return ( - + {isDataSourceLoading ? ( ) : ( { ) : null}
0 ? '' : 'wz-no-display' + !isDataSourceLoading && dataSource && results?.hits?.total > 0 + ? '' + : 'wz-no-display' } > - - - + + + { fetchFilters, query, absoluteDateRange.from, - absoluteDateRange.to + absoluteDateRange.to, )} /> @@ -246,19 +269,22 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => { {inspectedHit && ( - setInspectedHit(undefined)} size="m"> + setInspectedHit(undefined)} size='m'> - + - + diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx index cd00ad54a0..2cda96f4fd 100644 --- a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx @@ -20,7 +20,11 @@ import { EuiTitle, EuiButtonEmpty, } from '@elastic/eui'; -import { ErrorFactory, ErrorHandler, HttpError } from '../../../../react-services/error-management'; +import { + ErrorFactory, + ErrorHandler, + HttpError, +} from '../../../../react-services/error-management'; import { MAX_ENTRIES_PER_QUERY, exportSearchToCSV, @@ -91,16 +95,18 @@ const DashboardTH: React.FC = () => { const rowClicked = results.hits.hits[index]; setInspectedHit(rowClicked); }, - [results] + [results], ); - const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const DocViewInspectButton = ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => { const inspectHintMsg = 'Inspect document details'; return ( onClickInspectDoc(rowIndex)} - iconType="inspect" + iconType='inspect' aria-label={inspectHintMsg} /> @@ -126,7 +132,8 @@ const DashboardTH: React.FC = () => { }); const pinnedAgent = - PatternDataSourceFilterManager.getPinnedAgentFilter(dataSource?.id!).length > 0; + PatternDataSourceFilterManager.getPinnedAgentFilter(dataSource?.id!) + .length > 0; useEffect(() => { const currentColumns = !pinnedAgent @@ -154,10 +161,10 @@ const DashboardTH: React.FC = () => { sorting, dateRange: absoluteDateRange, }) - .then((results) => { + .then(results => { setResults(results); }) - .catch((error) => { + .catch(error => { const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching data', @@ -205,7 +212,7 @@ const DashboardTH: React.FC = () => { ) : ( { ) : null}
0 ? '' : 'wz-no-display' + !isDataSourceLoading && dataSource && results?.hits?.total > 0 + ? '' + : 'wz-no-display' }`} > -
+
{ { ) : null}
{inspectedHit && ( - setInspectedHit(undefined)} size="m"> + setInspectedHit(undefined)} size='m'> - + - + diff --git a/plugins/main/tsconfig.json b/plugins/main/tsconfig.json index b7ff5b1390..63e20a2cf0 100644 --- a/plugins/main/tsconfig.json +++ b/plugins/main/tsconfig.json @@ -1,6 +1,4 @@ { "extends": "../../tsconfig.json", - "include": [ - "./**/*" - ] -} \ No newline at end of file + "include": ["./**/*"] +} From ca0da16ff0375f7d5c379889c8b7e411079af898 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 11:56:39 -0300 Subject: [PATCH 13/24] docs: Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b84dd871a..4780a8f07a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the Wazuh app project will be documented in this file. ### Added +- Add feature to filter by field in the events table rows [#6977](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6991) - Support for Wazuh 4.9.1 ### Fixed From 61f9cde398efad27f04919aa8cad56d3127c7b68 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 17:08:05 -0300 Subject: [PATCH 14/24] Add cell filter actions to data grid component --- .../common/data-grid/cell-filter-actions.tsx | 104 ++++++++++++++++++ .../common/data-grid/get-cell-actions.tsx | 93 ---------------- 2 files changed, 104 insertions(+), 93 deletions(-) create mode 100644 plugins/main/public/components/common/data-grid/cell-filter-actions.tsx delete mode 100644 plugins/main/public/components/common/data-grid/get-cell-actions.tsx diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx new file mode 100644 index 0000000000..9376555651 --- /dev/null +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx @@ -0,0 +1,104 @@ +import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React from 'react'; +import { IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; +import { FILTER_OPERATOR } from '../data-source'; + +export const filterIsAction = ( + indexPattern: IndexPattern, + rows: any[], + onFilter: ( + columndId: string, + value: any, + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + ) => void +) => { + return ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { + const filterForValueText = i18n.translate('discover.filterForValue', { + defaultMessage: 'Filter for value', + }); + const filterForValueLabel = i18n.translate('discover.filterForValueLabel', { + defaultMessage: 'Filter for value: {value}', + values: { value: columnId }, + }); + + const handleClick = () => { + const row = rows[rowIndex]; + const flattened = indexPattern.flattenHit(row); + + if (flattened) { + onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS); + } + }; + + return ( + + {filterForValueText} + + ); + }; +}; + +export const filterIsNotAction = + ( + indexPattern: IndexPattern, + rows: any[], + onFilter: ( + columndId: string, + value: any, + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + ) => void + ) => + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { + const filterOutValueText = i18n.translate('discover.filterOutValue', { + defaultMessage: 'Filter out value', + }); + const filterOutValueLabel = i18n.translate('discover.filterOutValueLabel', { + defaultMessage: 'Filter out value: {value}', + values: { value: columnId }, + }); + + const handleClick = () => { + const row = rows[rowIndex]; + const flattened = indexPattern.flattenHit(row); + + if (flattened) { + onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS_NOT); + } + }; + + return ( + + {filterOutValueText} + + ); + }; + +// https://github.com/opensearch-project/OpenSearch-Dashboards/blob/2.13.0/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_actions.tsx +export function cellFilterActions( + field: IFieldType, + indexPattern: IndexPattern, + rows: any[], + onFilter: ( + columndId: string, + value: any, + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + ) => void +) { + if (!field.filterable) return; + + return [ + filterIsAction(indexPattern, rows, onFilter), + filterIsNotAction(indexPattern, rows, onFilter), + ] as EuiDataGridColumn['cellActions']; +} diff --git a/plugins/main/public/components/common/data-grid/get-cell-actions.tsx b/plugins/main/public/components/common/data-grid/get-cell-actions.tsx deleted file mode 100644 index 3081aef8c1..0000000000 --- a/plugins/main/public/components/common/data-grid/get-cell-actions.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; -import { i18n } from '@osd/i18n'; -import React from 'react'; -import { - IFieldType, - IndexPattern, -} from '../../../../../../src/plugins/data/common'; -import { FILTER_OPERATOR } from '../data-source'; - -// https://github.com/opensearch-project/OpenSearch-Dashboards/blob/2.13.0/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_actions.tsx -export function getCellActions( - field: IFieldType, - indexPattern: IndexPattern, - rows: any[], - onFilter: ( - columndId: string, - value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, - ) => void, -) { - let cellActions: - | (({ - rowIndex, - columnId, - Component, - }: EuiDataGridColumnCellActionProps) => React.JSX.Element)[] - | undefined = undefined; - if (field.filterable) { - cellActions = [ - ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { - const filterForValueText = i18n.translate('discover.filterForValue', { - defaultMessage: 'Filter for value', - }); - const filterForValueLabel = i18n.translate( - 'discover.filterForValueLabel', - { - defaultMessage: 'Filter for value: {value}', - values: { value: columnId }, - }, - ); - - return ( - { - const row = rows[rowIndex]; - const flattened = indexPattern.flattenHit(row); - - if (flattened) { - onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS); - } - }} - iconType='plusInCircle' - aria-label={filterForValueLabel} - data-test-subj='filterForValue' - > - {filterForValueText} - - ); - }, - ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { - const filterOutValueText = i18n.translate('discover.filterOutValue', { - defaultMessage: 'Filter out value', - }); - const filterOutValueLabel = i18n.translate( - 'discover.filterOutValueLabel', - { - defaultMessage: 'Filter out value: {value}', - values: { value: columnId }, - }, - ); - - return ( - { - const row = rows[rowIndex]; - const flattened = indexPattern.flattenHit(row); - - if (flattened) { - onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS_NOT); - } - }} - iconType='minusInCircle' - aria-label={filterOutValueLabel} - data-test-subj='filterOutValue' - > - {filterOutValueText} - - ); - }, - ]; - } - return cellActions; -} From dc4e4daaf8dd4d6be6c4059dce69aaaef578b4f3 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 17:08:14 -0300 Subject: [PATCH 15/24] Add tests for cell filter actions functions --- .../data-grid/cell-filter-actions.test.tsx | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx new file mode 100644 index 0000000000..3ee3cb7680 --- /dev/null +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx @@ -0,0 +1,98 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { cellFilterActions, filterIsAction, filterIsNotAction } from './cell-filter-actions'; +import { EuiButtonEmpty } from '@elastic/eui'; + +const indexPattern = { + flattenHit: jest.fn().mockImplementation(() => ({})), +}; + +const onFilter = jest.fn(); + +const TEST_COLUMN_ID = 'test'; + +describe('cell-filter-actions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('cellFilterActions', () => { + it('should be undefined', () => { + // @ts-expect-error Expected 4 arguments, but got 1. + expect(cellFilterActions({ filterable: false })).toBeUndefined(); + }); + }); + + describe('filterIsAction', () => { + it('should call on filter with column id and value', () => { + const TEST_VALUE = 'test-value'; + const ROW = 'row'; + + indexPattern.flattenHit.mockImplementation(() => ({ + [TEST_COLUMN_ID]: TEST_VALUE, + })); + const rows = [ROW]; + + render( + filterIsAction( + // @ts-expect-error Argument of type '{ flattenHit: jest.Mock; }' is not assignable to parameter of type 'IndexPattern' + indexPattern, + rows, + onFilter + )({ + rowIndex: 0, + columnId: TEST_COLUMN_ID, + Component: EuiButtonEmpty, + isExpanded: false, + closePopover: () => {}, + }) + ); + + let component = screen.getByText('Filter for value'); + expect(component).toBeTruthy(); + component = screen.getByLabelText(`Filter for value: ${TEST_COLUMN_ID}`); + expect(component).toBeTruthy(); + + fireEvent.click(component); + + expect(onFilter).toHaveBeenCalledTimes(1); + expect(onFilter).toHaveBeenCalledWith(TEST_COLUMN_ID, TEST_VALUE, 'is'); + }); + }); + + describe('filterIsNotAction', () => { + it('should call on filter with column id and value', () => { + const TEST_VALUE = 'test-value'; + const ROW = 'row'; + + indexPattern.flattenHit.mockImplementation(() => ({ + [TEST_COLUMN_ID]: TEST_VALUE, + })); + const rows = [ROW]; + + render( + filterIsNotAction( + // @ts-expect-error Argument of type '{ flattenHit: jest.Mock; }' is not assignable to parameter of type 'IndexPattern' + indexPattern, + rows, + onFilter + )({ + rowIndex: 0, + columnId: TEST_COLUMN_ID, + Component: EuiButtonEmpty, + isExpanded: false, + closePopover: () => {}, + }) + ); + + let component = screen.getByText('Filter out value'); + expect(component).toBeTruthy(); + component = screen.getByLabelText(`Filter out value: ${TEST_COLUMN_ID}`); + expect(component).toBeTruthy(); + + fireEvent.click(component); + + expect(onFilter).toHaveBeenCalledTimes(1); + expect(onFilter).toHaveBeenCalledWith(TEST_COLUMN_ID, TEST_VALUE, 'is not'); + }); + }); +}); From ca45d7bfcb2b7d6fb076c92fecef06e52a93c671 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 17:11:34 -0300 Subject: [PATCH 16/24] Refactor data grid service to improve column mapping --- .../common/data-grid/data-grid-service.ts | 92 +++++++++++-------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 312907de1d..977f992b2f 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -177,56 +177,68 @@ export const exportSearchToCSV = async ( } }; +const onFilterCellActions = ( + indexPatternId: string, + filters: Filter[], + setFilters: (filters: Filter[]) => void +) => { + return ( + columndId: string, + value: any, + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + ) => { + const newFilter = PatternDataSourceFilterManager.createFilter( + operation, + columndId, + value, + indexPatternId + ); + setFilters([...filters, newFilter]); + }; +}; + +const mapToDataGridColumn = ( + field: IFieldType, + indexPattern: IndexPattern, + rows: any[], + filters: Filter[], + setFilters: (filters: Filter[]) => void, + defaultColumns: tDataGridColumn[] +): tDataGridColumn => { + const defaultColumn = defaultColumns.find((column) => column.id === field.name); + return { + ...field, + id: field.name, + name: field.name, + schema: field.type, + actions: { showHide: true }, + ...defaultColumn, + cellActions: cellFilterActions( + field, + indexPattern, + rows, + onFilterCellActions(indexPattern.id as string, filters, setFilters) + ), + } as tDataGridColumn; +}; + export const parseColumns = ( fields: IFieldType[], defaultColumns: tDataGridColumn[] = [], indexPattern: IndexPattern, rows: any[], filters: Filter[], - setFilters: (filters: Filter[]) => void, + setFilters: (filters: Filter[]) => void ): tDataGridColumn[] => { // remove _source field becuase is a object field and is not supported // merge the properties of the field with the default columns - if (!fields?.length) { - return defaultColumns; - } + if (!fields?.length) return defaultColumns; - const columns = fields - .filter(field => field.name !== '_source') - .map(field => { - const defaultColumn = defaultColumns.find( - column => column.id === field.name, - ); - return { - ...field, - id: field.name, - name: field.name, - schema: field.type, - actions: { - showHide: true, - }, - ...defaultColumn, - cellActions: getCellActions( - field, - indexPattern, - rows, - ( - columndId: string, - value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, - ) => { - const newFilter = PatternDataSourceFilterManager.createFilter( - operation, - columndId, - value, - indexPattern.id ?? '', - ); - setFilters([...filters, newFilter]); - }, - ), - }; - }) as tDataGridColumn[]; - return columns; + return fields + .filter((field) => field.name !== '_source') + .map((field) => + mapToDataGridColumn(field, indexPattern, rows, filters, setFilters, defaultColumns) + ); }; /** From 566af3510d9046496dd3eb741541274f9413d519 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 17:11:44 -0300 Subject: [PATCH 17/24] Refactor data-grid-service.ts for better readability --- .../common/data-grid/data-grid-service.ts | 65 ++++++------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 977f992b2f..8e2ea334d6 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -2,23 +2,14 @@ import { SearchResponse } from '../../../../../../src/core/server'; import * as FileSaver from '../../../services/file-saver'; import { beautifyDate } from '../../agents/vuls/inventory/lib'; import { SearchParams, search } from '../search-bar/search-bar-service'; -import { - Filter, - IFieldType, - IndexPattern, -} from '../../../../../../src/plugins/data/common'; +import { Filter, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; export const MAX_ENTRIES_PER_QUERY = 10000; import { tDataGridColumn } from './use-data-grid'; -import { getCellActions } from './get-cell-actions'; -import { - FILTER_OPERATOR, - PatternDataSourceFilterManager, -} from '../data-source'; +import { cellFilterActions } from './cell-filter-actions'; +import { FILTER_OPERATOR, PatternDataSourceFilterManager } from '../data-source'; -export const parseData = ( - resultsHits: SearchResponse['hits']['hits'], -): any[] => { - const data = resultsHits.map(hit => { +export const parseData = (resultsHits: SearchResponse['hits']['hits']): any[] => { + const data = resultsHits.map((hit) => { if (!hit) { return {}; } @@ -35,20 +26,15 @@ export const parseData = ( return data; }; -export const getFieldFormatted = ( - rowIndex, - columnId, - indexPattern, - rowsParsed, -) => { - const field = indexPattern.fields.find(field => field.name === columnId); +export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => { + const field = indexPattern.fields.find((field) => field.name === columnId); let fieldValue = null; if (columnId.includes('.')) { // when the column is a nested field. The column could have 2 to n levels // get dinamically the value of the nested field const nestedFields = columnId.split('.'); fieldValue = rowsParsed[rowIndex]; - nestedFields.forEach(field => { + nestedFields.forEach((field) => { if (fieldValue) { fieldValue = fieldValue[field]; } @@ -81,25 +67,14 @@ export const getFieldFormatted = ( }; // receive search params -export const exportSearchToCSV = async ( - params: SearchParams, -): Promise => { +export const exportSearchToCSV = async (params: SearchParams): Promise => { const DEFAULT_MAX_SIZE_PER_CALL = 1000; - const { - indexPattern, - filters = [], - query, - sorting, - fields, - pagination, - } = params; + const { indexPattern, filters = [], query, sorting, fields, pagination } = params; // when the pageSize is greater than the default max size per call (10000) // then we need to paginate the search const mustPaginateSearch = pagination?.pageSize && pagination?.pageSize > DEFAULT_MAX_SIZE_PER_CALL; - const pageSize = mustPaginateSearch - ? DEFAULT_MAX_SIZE_PER_CALL - : pagination?.pageSize; + const pageSize = mustPaginateSearch ? DEFAULT_MAX_SIZE_PER_CALL : pagination?.pageSize; const totalHits = pagination?.pageSize || DEFAULT_MAX_SIZE_PER_CALL; let pageIndex = params.pagination?.pageIndex || 0; let hitsCount = 0; @@ -130,13 +105,13 @@ export const exportSearchToCSV = async ( } const resultsFields = fields; - const data = allHits.map(hit => { + const data = allHits.map((hit) => { // check if the field type is a date const dateFields = indexPattern.fields.getByType('date'); - const dateFieldsNames = dateFields.map(field => field.name); + const dateFieldsNames = dateFields.map((field) => field.name); const flattenHit = indexPattern.flattenHit(hit); // replace the date fields with the formatted date - dateFieldsNames.forEach(field => { + dateFieldsNames.forEach((field) => { if (flattenHit[field]) { flattenHit[field] = beautifyDate(flattenHit[field]); } @@ -151,8 +126,8 @@ export const exportSearchToCSV = async ( if (!data || data.length === 0) return; const parsedData = data - .map(row => { - const parsedRow = resultsFields?.map(field => { + .map((row) => { + const parsedRow = resultsFields?.map((field) => { const value = row[field]; if (value === undefined || value === null) { return ''; @@ -250,12 +225,12 @@ export const parseColumns = ( */ export const getAllCustomRenders = ( customColumns: tDataGridColumn[], - discoverColumns: tDataGridColumn[], + discoverColumns: tDataGridColumn[] ): tDataGridColumn[] => { - const customColumnsWithRender = customColumns.filter(column => column.render); - const allColumns = discoverColumns.map(column => { + const customColumnsWithRender = customColumns.filter((column) => column.render); + const allColumns = discoverColumns.map((column) => { const customColumn = customColumnsWithRender.find( - customColumn => customColumn.id === column.id, + (customColumn) => customColumn.id === column.id ); return customColumn || column; }); From 8bd52ead0fd6d9574385f0df66544fbeff9c1055 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 17:51:48 -0300 Subject: [PATCH 18/24] Refactor parseData to handle generic source type --- .../data-grid/data-grid-service.test.ts | 47 +++++++++++++++++++ .../common/data-grid/data-grid-service.ts | 12 ++++- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 plugins/main/public/components/common/data-grid/data-grid-service.test.ts diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.test.ts b/plugins/main/public/components/common/data-grid/data-grid-service.test.ts new file mode 100644 index 0000000000..cfc58ff671 --- /dev/null +++ b/plugins/main/public/components/common/data-grid/data-grid-service.test.ts @@ -0,0 +1,47 @@ +import { parseData } from './data-grid-service'; +import { SearchResponse } from '../../../../../../src/core/server'; + +describe('describe-grid-test', () => { + describe('parseData', () => { + it('should parse data extract source fields correctly', () => { + const resultsHits: SearchResponse['hits']['hits'] = [ + { + _id: 'id-1', + _index: 'index-1', + _type: 'type-1', + _score: 1, + _source: { + test: true, + }, + }, + ]; + + const expectedResult = [ + { + _id: 'id-1', + _index: 'index-1', + _type: 'type-1', + _score: 1, + test: true, + }, + ]; + + expect(parseData(resultsHits)).toEqual(expectedResult); + }); + + it('should parse data handle invalid hits', () => { + const resultsHits: SearchResponse['hits']['hits'] = [ + // @ts-expect-error + undefined, + // @ts-expect-error + null, + // @ts-expect-error + 0, + ]; + + const expectedResult = [{}, {}, {}]; + + expect(parseData(resultsHits)).toEqual(expectedResult); + }); + }); +}); diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 8e2ea334d6..5db28b3856 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -8,12 +8,20 @@ import { tDataGridColumn } from './use-data-grid'; import { cellFilterActions } from './cell-filter-actions'; import { FILTER_OPERATOR, PatternDataSourceFilterManager } from '../data-source'; -export const parseData = (resultsHits: SearchResponse['hits']['hits']): any[] => { +type ParseData = { + source: T, + _id: string, + _index: string, + _type: string, + _score: number, +} | {} + +export const parseData = (resultsHits: SearchResponse['hits']['hits']): ParseData[] => { const data = resultsHits.map((hit) => { if (!hit) { return {}; } - const source = hit._source as object; + const source = hit._source as T; const data = { ...source, _id: hit._id, From db2a1f87fd32858ade5e879ccc37dee8e1f70f62 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Wed, 11 Sep 2024 18:02:58 -0300 Subject: [PATCH 19/24] Update getFieldFormatted function parameters types --- .../public/components/common/data-grid/data-grid-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 5db28b3856..1cd5184383 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -34,7 +34,7 @@ export const parseData = (resultsHits: SearchResponse['hits']['h return data; }; -export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => { +export const getFieldFormatted = (rowIndex: number, columnId: string, indexPattern: IndexPattern, rowsParsed: ParseData[]) => { const field = indexPattern.fields.find((field) => field.name === columnId); let fieldValue = null; if (columnId.includes('.')) { From e90ceabf0206a930db7e3a0f8d8e2ec96a721559 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Thu, 12 Sep 2024 10:16:33 -0300 Subject: [PATCH 20/24] Fix Prettier issues --- .../data-grid/cell-filter-actions.test.tsx | 20 +++- .../common/data-grid/cell-filter-actions.tsx | 38 +++--- .../common/data-grid/data-grid-service.ts | 110 ++++++++++++------ 3 files changed, 110 insertions(+), 58 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx index 3ee3cb7680..b3dcea6cb0 100644 --- a/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx @@ -1,5 +1,9 @@ import { fireEvent, render, screen } from '@testing-library/react'; -import { cellFilterActions, filterIsAction, filterIsNotAction } from './cell-filter-actions'; +import { + cellFilterActions, + filterIsAction, + filterIsNotAction, +} from './cell-filter-actions'; import { EuiButtonEmpty } from '@elastic/eui'; const indexPattern = { @@ -37,14 +41,14 @@ describe('cell-filter-actions', () => { // @ts-expect-error Argument of type '{ flattenHit: jest.Mock; }' is not assignable to parameter of type 'IndexPattern' indexPattern, rows, - onFilter + onFilter, )({ rowIndex: 0, columnId: TEST_COLUMN_ID, Component: EuiButtonEmpty, isExpanded: false, closePopover: () => {}, - }) + }), ); let component = screen.getByText('Filter for value'); @@ -74,14 +78,14 @@ describe('cell-filter-actions', () => { // @ts-expect-error Argument of type '{ flattenHit: jest.Mock; }' is not assignable to parameter of type 'IndexPattern' indexPattern, rows, - onFilter + onFilter, )({ rowIndex: 0, columnId: TEST_COLUMN_ID, Component: EuiButtonEmpty, isExpanded: false, closePopover: () => {}, - }) + }), ); let component = screen.getByText('Filter out value'); @@ -92,7 +96,11 @@ describe('cell-filter-actions', () => { fireEvent.click(component); expect(onFilter).toHaveBeenCalledTimes(1); - expect(onFilter).toHaveBeenCalledWith(TEST_COLUMN_ID, TEST_VALUE, 'is not'); + expect(onFilter).toHaveBeenCalledWith( + TEST_COLUMN_ID, + TEST_VALUE, + 'is not', + ); }); }); }); diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx index 9376555651..fd57493e6c 100644 --- a/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx @@ -1,8 +1,14 @@ -import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { + EuiDataGridColumn, + EuiDataGridColumnCellActionProps, +} from '@elastic/eui'; import { i18n } from '@osd/i18n'; import React from 'react'; -import { IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; -import { FILTER_OPERATOR } from '../data-source'; +import { + IFieldType, + IndexPattern, +} from '../../../../../../src/plugins/data/common'; +import { FILTER_OPERATOR } from '../data-source/pattern/pattern-data-source-filter-manager'; export const filterIsAction = ( indexPattern: IndexPattern, @@ -10,10 +16,14 @@ export const filterIsAction = ( onFilter: ( columndId: string, value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT - ) => void + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, + ) => void, ) => { - return ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { + return ({ + rowIndex, + columnId, + Component, + }: EuiDataGridColumnCellActionProps) => { const filterForValueText = i18n.translate('discover.filterForValue', { defaultMessage: 'Filter for value', }); @@ -34,9 +44,9 @@ export const filterIsAction = ( return ( {filterForValueText} @@ -51,8 +61,8 @@ export const filterIsNotAction = onFilter: ( columndId: string, value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT - ) => void + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, + ) => void, ) => ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { const filterOutValueText = i18n.translate('discover.filterOutValue', { @@ -75,9 +85,9 @@ export const filterIsNotAction = return ( {filterOutValueText} @@ -92,8 +102,8 @@ export function cellFilterActions( onFilter: ( columndId: string, value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT - ) => void + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, + ) => void, ) { if (!field.filterable) return; diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 1cd5184383..7ee7dcb70d 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -2,22 +2,33 @@ import { SearchResponse } from '../../../../../../src/core/server'; import * as FileSaver from '../../../services/file-saver'; import { beautifyDate } from '../../agents/vuls/inventory/lib'; import { SearchParams, search } from '../search-bar/search-bar-service'; -import { Filter, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/common'; +import { + Filter, + IFieldType, + IndexPattern, +} from '../../../../../../src/plugins/data/common'; export const MAX_ENTRIES_PER_QUERY = 10000; import { tDataGridColumn } from './use-data-grid'; import { cellFilterActions } from './cell-filter-actions'; -import { FILTER_OPERATOR, PatternDataSourceFilterManager } from '../data-source'; - -type ParseData = { - source: T, - _id: string, - _index: string, - _type: string, - _score: number, -} | {} - -export const parseData = (resultsHits: SearchResponse['hits']['hits']): ParseData[] => { - const data = resultsHits.map((hit) => { +import { + FILTER_OPERATOR, + PatternDataSourceFilterManager, +} from '../data-source/pattern/pattern-data-source-filter-manager'; + +type ParseData = + | { + source: T; + _id: string; + _index: string; + _type: string; + _score: number; + } + | {}; + +export const parseData = ( + resultsHits: SearchResponse['hits']['hits'], +): ParseData[] => { + const data = resultsHits.map(hit => { if (!hit) { return {}; } @@ -34,15 +45,20 @@ export const parseData = (resultsHits: SearchResponse['hits']['h return data; }; -export const getFieldFormatted = (rowIndex: number, columnId: string, indexPattern: IndexPattern, rowsParsed: ParseData[]) => { - const field = indexPattern.fields.find((field) => field.name === columnId); +export const getFieldFormatted = ( + rowIndex: number, + columnId: string, + indexPattern: IndexPattern, + rowsParsed: ParseData[], +) => { + const field = indexPattern.fields.find(field => field.name === columnId); let fieldValue = null; if (columnId.includes('.')) { // when the column is a nested field. The column could have 2 to n levels // get dinamically the value of the nested field const nestedFields = columnId.split('.'); fieldValue = rowsParsed[rowIndex]; - nestedFields.forEach((field) => { + nestedFields.forEach(field => { if (fieldValue) { fieldValue = fieldValue[field]; } @@ -75,14 +91,25 @@ export const getFieldFormatted = (rowIndex: number, columnId: string, indexPatte }; // receive search params -export const exportSearchToCSV = async (params: SearchParams): Promise => { +export const exportSearchToCSV = async ( + params: SearchParams, +): Promise => { const DEFAULT_MAX_SIZE_PER_CALL = 1000; - const { indexPattern, filters = [], query, sorting, fields, pagination } = params; + const { + indexPattern, + filters = [], + query, + sorting, + fields, + pagination, + } = params; // when the pageSize is greater than the default max size per call (10000) // then we need to paginate the search const mustPaginateSearch = pagination?.pageSize && pagination?.pageSize > DEFAULT_MAX_SIZE_PER_CALL; - const pageSize = mustPaginateSearch ? DEFAULT_MAX_SIZE_PER_CALL : pagination?.pageSize; + const pageSize = mustPaginateSearch + ? DEFAULT_MAX_SIZE_PER_CALL + : pagination?.pageSize; const totalHits = pagination?.pageSize || DEFAULT_MAX_SIZE_PER_CALL; let pageIndex = params.pagination?.pageIndex || 0; let hitsCount = 0; @@ -113,13 +140,13 @@ export const exportSearchToCSV = async (params: SearchParams): Promise => } const resultsFields = fields; - const data = allHits.map((hit) => { + const data = allHits.map(hit => { // check if the field type is a date const dateFields = indexPattern.fields.getByType('date'); - const dateFieldsNames = dateFields.map((field) => field.name); + const dateFieldsNames = dateFields.map(field => field.name); const flattenHit = indexPattern.flattenHit(hit); // replace the date fields with the formatted date - dateFieldsNames.forEach((field) => { + dateFieldsNames.forEach(field => { if (flattenHit[field]) { flattenHit[field] = beautifyDate(flattenHit[field]); } @@ -134,8 +161,8 @@ export const exportSearchToCSV = async (params: SearchParams): Promise => if (!data || data.length === 0) return; const parsedData = data - .map((row) => { - const parsedRow = resultsFields?.map((field) => { + .map(row => { + const parsedRow = resultsFields?.map(field => { const value = row[field]; if (value === undefined || value === null) { return ''; @@ -163,18 +190,18 @@ export const exportSearchToCSV = async (params: SearchParams): Promise => const onFilterCellActions = ( indexPatternId: string, filters: Filter[], - setFilters: (filters: Filter[]) => void + setFilters: (filters: Filter[]) => void, ) => { return ( columndId: string, value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT + operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, ) => { const newFilter = PatternDataSourceFilterManager.createFilter( operation, columndId, value, - indexPatternId + indexPatternId, ); setFilters([...filters, newFilter]); }; @@ -186,9 +213,9 @@ const mapToDataGridColumn = ( rows: any[], filters: Filter[], setFilters: (filters: Filter[]) => void, - defaultColumns: tDataGridColumn[] + defaultColumns: tDataGridColumn[], ): tDataGridColumn => { - const defaultColumn = defaultColumns.find((column) => column.id === field.name); + const defaultColumn = defaultColumns.find(column => column.id === field.name); return { ...field, id: field.name, @@ -200,7 +227,7 @@ const mapToDataGridColumn = ( field, indexPattern, rows, - onFilterCellActions(indexPattern.id as string, filters, setFilters) + onFilterCellActions(indexPattern.id as string, filters, setFilters), ), } as tDataGridColumn; }; @@ -211,16 +238,23 @@ export const parseColumns = ( indexPattern: IndexPattern, rows: any[], filters: Filter[], - setFilters: (filters: Filter[]) => void + setFilters: (filters: Filter[]) => void, ): tDataGridColumn[] => { // remove _source field becuase is a object field and is not supported // merge the properties of the field with the default columns if (!fields?.length) return defaultColumns; return fields - .filter((field) => field.name !== '_source') - .map((field) => - mapToDataGridColumn(field, indexPattern, rows, filters, setFilters, defaultColumns) + .filter(field => field.name !== '_source') + .map(field => + mapToDataGridColumn( + field, + indexPattern, + rows, + filters, + setFilters, + defaultColumns, + ), ); }; @@ -233,12 +267,12 @@ export const parseColumns = ( */ export const getAllCustomRenders = ( customColumns: tDataGridColumn[], - discoverColumns: tDataGridColumn[] + discoverColumns: tDataGridColumn[], ): tDataGridColumn[] => { - const customColumnsWithRender = customColumns.filter((column) => column.render); - const allColumns = discoverColumns.map((column) => { + const customColumnsWithRender = customColumns.filter(column => column.render); + const allColumns = discoverColumns.map(column => { const customColumn = customColumnsWithRender.find( - (customColumn) => customColumn.id === column.id + customColumn => customColumn.id === column.id, ); return customColumn || column; }); From 3ffbb5ff87cf34968805ba6af907494d72820265 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 16 Sep 2024 10:42:40 -0300 Subject: [PATCH 21/24] Upgrade Event-tab column selector and optimize data grid --- CHANGELOG.md | 1 + .../common/data-grid/use-data-grid.ts | 113 +++++++++--------- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4780a8f07a..4daf5e7829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Change the text of the query limit tooltip [#6981](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6981) - Upgraded the `axios` dependency to `1.7.4` [#6919](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6919) - Improved MITRE ATT&CK intelligence flyout details readability [#6954](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6954) +- Upgraded Event-tab column selector showing first the picked columns [#6984](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6984) - Changed vulnerabilities.reference to links in Vulnerability Detection > Inventory columns [#6960](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6960) - Upgraded the `follow-redirects` dependency to `1.15.6` [#6982](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6982) diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts index fbc2435838..32219d853b 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.ts +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -7,22 +7,12 @@ import { import React, { useEffect, useMemo, useState } from 'react'; import { SearchResponse } from '@opensearch-project/opensearch/api/types'; // ToDo: check how create this methods -import { - parseData, - getFieldFormatted, - parseColumns, -} from './data-grid-service'; -import { - Filter, - IndexPattern, -} from '../../../../../../src/plugins/data/common'; +import { parseData, getFieldFormatted, parseColumns } from './data-grid-service'; +import { Filter, IndexPattern } from '../../../../../../src/plugins/data/common'; import { EuiDataGridPaginationProps } from '@opensearch-project/oui'; export interface PaginationOptions - extends Pick< - EuiDataGridPaginationProps, - 'pageIndex' | 'pageSize' | 'pageSizeOptions' - > {} + extends Pick {} type SortingColumns = EuiDataGridSorting['columns']; @@ -49,9 +39,7 @@ export type tDataGridProps = { results: SearchResponse; defaultColumns: tDataGridColumn[]; renderColumns?: tDataGridRenderColumn[]; - DocViewInspectButton: ({ - rowIndex, - }: EuiDataGridCellValueElementProps) => React.JSX.Element; + DocViewInspectButton: ({ rowIndex }: EuiDataGridCellValueElementProps) => React.JSX.Element; ariaLabelledBy: string; useDefaultPagination?: boolean; pagination?: typeof DEFAULT_PAGINATION_OPTIONS; @@ -68,24 +56,19 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { renderColumns, useDefaultPagination = false, pagination: paginationProps = {}, - filters, - setFilters, + filters = [], + setFilters = () => {}, } = props; /** Columns **/ - const [columns /* , setColumns */] = - useState(defaultColumns); - const [columnVisibility, setVisibility] = useState(() => - columns.map(({ id }) => id), - ); + const [columns /* , setColumns */] = useState(defaultColumns); + const [columnVisibility, setVisibility] = useState(() => columns.map(({ id }) => id)); /** Rows */ const [rows, setRows] = useState([]); const rowCount = results ? (results?.hits?.total as number) : 0; /** Sorting **/ // get default sorting from default columns const getDefaultSorting = () => { - const defaultSort = columns.find( - column => column.isSortable || column.defaultSortDirection, - ); + const defaultSort = columns.find((column) => column.isSortable || column.defaultSortDirection); return defaultSort ? [ { @@ -101,44 +84,36 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { setSortingColumns(sortingColumns); }; /** Pagination **/ - const [pagination, setPagination] = useState< - typeof DEFAULT_PAGINATION_OPTIONS - >( + const [pagination, setPagination] = useState( useDefaultPagination ? DEFAULT_PAGINATION_OPTIONS : { ...DEFAULT_PAGINATION_OPTIONS, ...paginationProps, - }, + } ); const onChangeItemsPerPage = useMemo( () => (pageSize: number) => - setPagination(pagination => ({ + setPagination((pagination) => ({ ...pagination, pageSize, pageIndex: 0, })), - [rows, rowCount], + [rows, rowCount] ); const onChangePage = (pageIndex: number) => - setPagination(pagination => ({ ...pagination, pageIndex })); + setPagination((pagination) => ({ ...pagination, pageIndex })); useEffect(() => { setRows(results?.hits?.hits || []); }, [results, results?.hits, results?.hits?.total]); useEffect(() => { - setPagination(pagination => ({ ...pagination, pageIndex: 0 })); + setPagination((pagination) => ({ ...pagination, pageIndex: 0 })); }, [rowCount]); - const renderCellValue = ({ - rowIndex, - columnId, - }: { - rowIndex: number; - columnId: string; - }) => { + const renderCellValue = ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => { const rowsParsed = parseData(rows); // On the context data always is stored the current page data (pagination) // then the rowIndex is relative to the current page @@ -148,22 +123,17 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { relativeRowIndex, columnId, indexPattern, - rowsParsed, + rowsParsed ); // check if column have render method initialized - const column = columns.find(column => column.id === columnId); + const column = columns.find((column) => column.id === columnId); if (column && column.render) { return column.render(fieldFormatted, rowsParsed[relativeRowIndex]); } // check if column have render method in renderColumns prop - const renderColumn = renderColumns?.find( - column => column.id === columnId, - ); + const renderColumn = renderColumns?.find((column) => column.id === columnId); if (renderColumn) { - return renderColumn.render( - fieldFormatted, - rowsParsed[relativeRowIndex], - ); + return renderColumn.render(fieldFormatted, rowsParsed[relativeRowIndex]); } return fieldFormatted; @@ -186,24 +156,51 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { ]; }, [results]); - return { - 'aria-labelledby': props.ariaLabelledBy, - columns: parseColumns( + const filterColumns = () => { + const allColumns = parseColumns( indexPattern?.fields || [], defaultColumns, indexPattern, rows, filters, - setFilters, - ), + setFilters + ); + const columnMatch = []; + const columnNonMatch = []; + + for (const item of allColumns) { + if (columnVisibility.includes(item.name)) { + columnMatch.push(item); + } else { + columnNonMatch.push(item); + } + } + + return [...columnMatch, ...columnNonMatch]; + }; + + const defaultColumnsPosition = (columnsVisibility, defaultColumns) => { + const defaults = defaultColumns + .map((item) => item.id) + .filter((id) => columnsVisibility.includes(id)); + + const nonDefaults = columnsVisibility.filter( + (item) => !defaultColumns.map((item) => item.id).includes(item) + ); + + return [...defaults, ...nonDefaults]; + }; + + return { + 'aria-labelledby': props.ariaLabelledBy, + columns: filterColumns(), columnVisibility: { - visibleColumns: columnVisibility, + visibleColumns: defaultColumnsPosition(columnVisibility, defaultColumns), setVisibleColumns: setVisibility, }, renderCellValue: renderCellValue, leadingControlColumns: leadingControlColumns, - rowCount: - rowCount < MAX_ENTRIES_PER_QUERY ? rowCount : MAX_ENTRIES_PER_QUERY, + rowCount: rowCount < MAX_ENTRIES_PER_QUERY ? rowCount : MAX_ENTRIES_PER_QUERY, sorting: { columns: sortingColumns, onSort }, pagination: { ...pagination, From 21202e431f5d2e23d67ac400674c682226d15655 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 16 Sep 2024 11:05:55 -0300 Subject: [PATCH 22/24] Refactor default columns assignment in useDataGrid --- .../components/common/data-grid/use-data-grid.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts index cadbf1ae42..bce4f05c81 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.ts +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -64,16 +64,13 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { indexPattern, DocViewInspectButton, results, - defaultColumns, + defaultColumns: columns, renderColumns, useDefaultPagination = false, pagination: paginationProps = {}, filters = [], setFilters = () => {}, } = props; - /** Columns **/ - const [columns /* , setColumns */] = - useState(defaultColumns); const [columnVisibility, setVisibility] = useState(() => columns.map(({ id }) => id), ); @@ -189,7 +186,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { const filterColumns = () => { const allColumns = parseColumns( indexPattern?.fields || [], - defaultColumns, + columns, indexPattern, rows, filters, @@ -224,10 +221,8 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { return { 'aria-labelledby': props.ariaLabelledBy, columns: filterColumns(), - columns: filterColumns(), columnVisibility: { - visibleColumns: defaultColumnsPosition(columnVisibility, defaultColumns), - visibleColumns: defaultColumnsPosition(columnVisibility, defaultColumns), + visibleColumns: defaultColumnsPosition(columnVisibility, columns), setVisibleColumns: setVisibility, }, renderCellValue: renderCellValue, From c0da92df58ada3cc92d2044ec42a34b060e6cefe Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 16 Sep 2024 14:55:54 -0300 Subject: [PATCH 23/24] Refactor cell filter actions and data grid service logic --- .../common/data-grid/cell-filter-actions.tsx | 11 +++++++---- .../components/common/data-grid/data-grid-service.ts | 4 ++++ .../components/common/data-grid/use-data-grid.ts | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx index fd57493e6c..6f9007dffc 100644 --- a/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx @@ -13,6 +13,7 @@ import { FILTER_OPERATOR } from '../data-source/pattern/pattern-data-source-filt export const filterIsAction = ( indexPattern: IndexPattern, rows: any[], + pageSize: number, onFilter: ( columndId: string, value: any, @@ -33,7 +34,7 @@ export const filterIsAction = ( }); const handleClick = () => { - const row = rows[rowIndex]; + const row = rows[rowIndex % pageSize]; const flattened = indexPattern.flattenHit(row); if (flattened) { @@ -58,6 +59,7 @@ export const filterIsNotAction = ( indexPattern: IndexPattern, rows: any[], + pageSize: number, onFilter: ( columndId: string, value: any, @@ -74,7 +76,7 @@ export const filterIsNotAction = }); const handleClick = () => { - const row = rows[rowIndex]; + const row = rows[rowIndex % pageSize]; const flattened = indexPattern.flattenHit(row); if (flattened) { @@ -99,6 +101,7 @@ export function cellFilterActions( field: IFieldType, indexPattern: IndexPattern, rows: any[], + pageSize: number, onFilter: ( columndId: string, value: any, @@ -108,7 +111,7 @@ export function cellFilterActions( if (!field.filterable) return; return [ - filterIsAction(indexPattern, rows, onFilter), - filterIsNotAction(indexPattern, rows, onFilter), + filterIsAction(indexPattern, rows, pageSize, onFilter), + filterIsNotAction(indexPattern, rows, pageSize, onFilter), ] as EuiDataGridColumn['cellActions']; } diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index 7ee7dcb70d..35151a2f2d 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -211,6 +211,7 @@ const mapToDataGridColumn = ( field: IFieldType, indexPattern: IndexPattern, rows: any[], + pageSize: number, filters: Filter[], setFilters: (filters: Filter[]) => void, defaultColumns: tDataGridColumn[], @@ -227,6 +228,7 @@ const mapToDataGridColumn = ( field, indexPattern, rows, + pageSize, onFilterCellActions(indexPattern.id as string, filters, setFilters), ), } as tDataGridColumn; @@ -237,6 +239,7 @@ export const parseColumns = ( defaultColumns: tDataGridColumn[] = [], indexPattern: IndexPattern, rows: any[], + pageSize: number, filters: Filter[], setFilters: (filters: Filter[]) => void, ): tDataGridColumn[] => { @@ -251,6 +254,7 @@ export const parseColumns = ( field, indexPattern, rows, + pageSize, filters, setFilters, defaultColumns, diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts index bce4f05c81..a330fc5ad1 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.ts +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -189,6 +189,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { columns, indexPattern, rows, + pagination.pageSize, filters, setFilters, ); From 2f6ae36c8599a438508b2b740c189d416eb437f7 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 16 Sep 2024 15:40:01 -0300 Subject: [PATCH 24/24] Add page size to cell filter actions tests --- .../components/common/data-grid/cell-filter-actions.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx index b3dcea6cb0..8eb829a886 100644 --- a/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx @@ -35,12 +35,14 @@ describe('cell-filter-actions', () => { [TEST_COLUMN_ID]: TEST_VALUE, })); const rows = [ROW]; + const pageSize = 15; render( filterIsAction( // @ts-expect-error Argument of type '{ flattenHit: jest.Mock; }' is not assignable to parameter of type 'IndexPattern' indexPattern, rows, + pageSize, onFilter, )({ rowIndex: 0, @@ -72,12 +74,14 @@ describe('cell-filter-actions', () => { [TEST_COLUMN_ID]: TEST_VALUE, })); const rows = [ROW]; + const pageSize = 15; render( filterIsNotAction( // @ts-expect-error Argument of type '{ flattenHit: jest.Mock; }' is not assignable to parameter of type 'IndexPattern' indexPattern, rows, + pageSize, onFilter, )({ rowIndex: 0,