Skip to content

Commit

Permalink
Add feature to filter by field in the events table rows (#6991)
Browse files Browse the repository at this point in the history
* refactor: Update default pagination options and sorting columns

* feat: Add tsconfig.json for main plugin

* style: Remove unnecessary white-space and formatting

* style: Simplify function parameters and formatting

* style: Remove unnecessary whitespace and fix formatting

* style: Update quotes and remove unnecessary line breaks

* style: Update pagination to use default setting

* feat: Add function to get cell actions in data grid

* feat: Add filtering functionality to data grid

* feat: Add filters and setFilters to data grid props

* refactor: Simplify arrow function parameters and formatting

* refactor: Update code formatting for better readability

* docs: Update CHANGELOG.md

* Add cell filter actions to data grid component

* Add tests for cell filter actions functions

* Refactor data grid service to improve column mapping

* Refactor data-grid-service.ts for better readability

* Refactor parseData to handle generic source type

* Update getFieldFormatted function parameters types

* Fix Prettier issues

* Upgrade Event-tab column selector and optimize data grid

* Refactor default columns assignment in useDataGrid

* Refactor cell filter actions and data grid service logic

* Add page size to cell filter actions tests

---------

Co-authored-by: Federico Rodriguez <[email protected]>
  • Loading branch information
guidomodarelli and asteriscos authored Sep 17, 2024
1 parent 952bf49 commit 500def5
Show file tree
Hide file tree
Showing 13 changed files with 484 additions and 99 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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];
const pageSize = 15;

render(
filterIsAction(
// @ts-expect-error Argument of type '{ flattenHit: jest.Mock<any, any>; }' is not assignable to parameter of type 'IndexPattern'
indexPattern,
rows,
pageSize,
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];
const pageSize = 15;

render(
filterIsNotAction(
// @ts-expect-error Argument of type '{ flattenHit: jest.Mock<any, any>; }' is not assignable to parameter of type 'IndexPattern'
indexPattern,
rows,
pageSize,
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',
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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/pattern/pattern-data-source-filter-manager';

export const filterIsAction = (
indexPattern: IndexPattern,
rows: any[],
pageSize: number,
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 % pageSize];
const flattened = indexPattern.flattenHit(row);

if (flattened) {
onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS);
}
};

return (
<Component
onClick={handleClick}
iconType='plusInCircle'
aria-label={filterForValueLabel}
data-test-subj='filterForValue'
>
{filterForValueText}
</Component>
);
};
};

export const filterIsNotAction =
(
indexPattern: IndexPattern,
rows: any[],
pageSize: number,
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 % pageSize];
const flattened = indexPattern.flattenHit(row);

if (flattened) {
onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS_NOT);
}
};

return (
<Component
onClick={handleClick}
iconType='minusInCircle'
aria-label={filterOutValueLabel}
data-test-subj='filterOutValue'
>
{filterOutValueText}
</Component>
);
};

// 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[],
pageSize: number,
onFilter: (
columndId: string,
value: any,
operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT,
) => void,
) {
if (!field.filterable) return;

return [
filterIsAction(indexPattern, rows, pageSize, onFilter),
filterIsNotAction(indexPattern, rows, pageSize, onFilter),
] as EuiDataGridColumn['cellActions'];
}
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
Loading

0 comments on commit 500def5

Please sign in to comment.