Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added limit to FIM csv export #7182

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e1b4d28
Added tip on fim
JuanGarriuz Nov 29, 2024
f09523e
Added Changelog
JuanGarriuz Nov 29, 2024
2bd0fb9
Snaps updated
JuanGarriuz Nov 29, 2024
6379576
Update alert message
JuanGarriuz Dec 2, 2024
61ebfe0
Merge branch '4.10.2' into enhacement/7068-add-alert-message-about-to…
guidomodarelli Dec 2, 2024
33276b3
Add limit of 10000 rows
Tostti Dec 5, 2024
36e1a16
Add warning message and sorting filter
Tostti Dec 5, 2024
91304dd
Update changelog
Tostti Dec 5, 2024
dd336ae
Add constant to set the limit
Tostti Dec 6, 2024
0445538
Add constant to server side
Tostti Dec 6, 2024
44e2948
Remove duplicated SVG elements in inventory and agents table snapshot…
guidomodarelli Dec 9, 2024
ac24959
Merge branch '4.10.2' into enhacement/7068-add-alert-message-about-to…
asteriscos Dec 10, 2024
0d603d7
Add constant to wazuh-core
Tostti Dec 10, 2024
e0bd7b1
Fix sort state
Tostti Dec 11, 2024
c026070
Merge branch '4.10.2' into enhacement/7068-add-alert-message-about-to…
asteriscos Dec 11, 2024
4151978
Add option to set the max rows of csv reports
Tostti Dec 12, 2024
053c1be
Add logic to read the max rows from configuration in frontend
Tostti Dec 12, 2024
4abe944
Add logic to read rows limit from configuration in backend
Tostti Dec 12, 2024
89ee03f
Update changelog
Tostti Dec 12, 2024
ee6729a
Remove unused constants
Tostti Dec 12, 2024
0b3c3d4
Update test
Tostti Dec 12, 2024
813f208
Update tests
Tostti Dec 12, 2024
5df89d1
Remove unnecessary values
Tostti Dec 13, 2024
249ca5f
Remove console logs
Tostti Dec 13, 2024
bd7f5e0
Replace icon
Tostti Dec 13, 2024
be67522
Fix spacing at button sides
Tostti Dec 13, 2024
dd28d16
Fix formatting
Tostti Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to the Wazuh app project will be documented in this file.
### Added

- Support for Wazuh 4.10.2
- Add setting to limit the number of rows in csv reports [#7182](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7182)

### Changed

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import React from 'react';

Check failure on line 1 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

Definition for rule 'import/no-unused-modules' was not found
import { render } from 'enzyme';
import { SyscollectorInventory } from './inventory';
import { AgentTabs } from '../../endpoints-summary/agent/agent-tabs';
import { queryDataTestAttr } from '../../../../test/public/query-attr';

jest.mock('../../common/hooks/use-app-config', () => ({
useAppConfig: () => ({
isReady: true,
isLoading: false,
data: {
'reports.csv.maxRows': 10000,
},
}),
}));

const TABLE_ID = '__table_7d62db31-1cd0-11ee-8e0c-33242698a3b9';
const SOFTWARE_PACKAGES = 'Packages';
const SOFTWARE_WINDOWS_UPDATES = 'Windows updates';
Expand Down Expand Up @@ -196,13 +206,13 @@
);
};

function findAgentInfo(wrapper: cheerio.Cheerio): any {

Check failure on line 209 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

Unexpected any. Specify a different type
return wrapper.find(queryDataTestAttr('agent-info')).html();
}

describe('Inventory data', () => {
describe('Agent info', () => {
it("A Linux agent shouldn't render agent info", () => {

Check failure on line 215 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

Strings must use singlequote
let wrapper = render(
<SyscollectorInventory
agent={AGENT.DEBIAN}
Expand Down Expand Up @@ -237,7 +247,7 @@
expect(agentInfo).toBeFalsy();
});

it("A Windows agent shouldn't render agent info", () => {

Check failure on line 250 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

Strings must use singlequote
let wrapper = render(
<SyscollectorInventory
agent={AGENT.WINDOWS}
Expand Down Expand Up @@ -272,7 +282,7 @@
expect(agentInfo).toBeFalsy();
});

it("A Apple agent shouldn't render agent info", () => {

Check failure on line 285 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

Strings must use singlequote
let wrapper = render(
<SyscollectorInventory
agent={AGENT.DARWIN}
Expand Down Expand Up @@ -427,7 +437,7 @@
});

describe(NETWORK_SETTINGS + ' table', () => {
it('A Linux agent should render network settings table with correct columns and title.', () => {

Check failure on line 440 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

This line has a length of 102. Maximum allowed is 100
const columns = [
NETWORK_SETTINGS_COLUMNS.INTERFACE,
NETWORK_SETTINGS_COLUMNS.ADDRESS,
Expand All @@ -441,7 +451,7 @@
columns,
);
});
it('A Windows agent should render network settings table with correct columns and title.', () => {

Check failure on line 454 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

This line has a length of 104. Maximum allowed is 100
const columns = [
NETWORK_SETTINGS_COLUMNS.INTERFACE,
NETWORK_SETTINGS_COLUMNS.ADDRESS,
Expand All @@ -455,7 +465,7 @@
columns,
);
});
it('A Apple agent should render network settings table with correct columns and title.', () => {

Check failure on line 468 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

This line has a length of 102. Maximum allowed is 100
const columns = [
NETWORK_SETTINGS_COLUMNS.INTERFACE,
NETWORK_SETTINGS_COLUMNS.ADDRESS,
Expand All @@ -471,7 +481,7 @@
});
});
describe(NETWORK_INTERFACES + ' table', () => {
it('A Linux agent should render network interfaces table with correct columns and title.', () => {

Check failure on line 484 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

This line has a length of 104. Maximum allowed is 100
const columns = [
NETWORK_INTERFACES_COLUMNS.NAME,
NETWORK_INTERFACES_COLUMNS.MAC,
Expand All @@ -485,7 +495,7 @@
columns,
);
});
it('A Windows agent should render network interfaces table with correct columns and title.', () => {

Check failure on line 498 in plugins/main/public/components/agents/syscollector/inventory.test.tsx

View workflow job for this annotation

GitHub Actions / Ensure the code format on the changed files

This line has a length of 106. Maximum allowed is 100
const columns = [
NETWORK_INTERFACES_COLUMNS.NAME,
NETWORK_INTERFACES_COLUMNS.MAC,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@
*/

import React from 'react';
import {
EuiFlexItem,
EuiButtonEmpty
} from '@elastic/eui';
import { EuiFlexItem, EuiButtonEmpty, EuiIconTip } from '@elastic/eui';
import exportCsv from '../../../../react-services/wz-csv';
import { getToasts } from '../../../../kibana-services';
import { getToasts } from '../../../../kibana-services';
import { UI_ERROR_SEVERITIES } from '../../../../react-services/error-orchestrator/types';
import { UI_LOGGER_LEVELS } from '../../../../../common/constants';
import { getErrorOrchestrator } from '../../../../react-services/common-services';

export function ExportTableCsv({ endpoint, totalItems, filters, title }) {

export function ExportTableCsv({
endpoint,
totalItems,
filters,
title,
maxRows,
}) {
const showToast = (color, title, time) => {
getToasts().add({
color: color,
Expand All @@ -33,15 +35,12 @@ export function ExportTableCsv({ endpoint, totalItems, filters, title }) {

const downloadCsv = async () => {
try {
const formatedFilters = Object.entries(filters).map(([name, value]) => ({name, value}));
const formatedFilters = Object.entries(filters).map(([name, value]) => ({
name,
value,
}));
showToast('success', 'Your download should begin automatically...', 3000);
await exportCsv(
endpoint,
[
...formatedFilters
],
`${(title).toLowerCase()}`
);
await exportCsv(endpoint, [...formatedFilters], `${title.toLowerCase()}`);
} catch (error) {
const options = {
context: `${ExportTableCsv.name}.downloadCsv`,
Expand All @@ -55,19 +54,36 @@ export function ExportTableCsv({ endpoint, totalItems, filters, title }) {
};
getErrorOrchestrator().handleError(options);
}
}

return <EuiFlexItem grow={false}>
<EuiButtonEmpty isDisabled={(totalItems == 0)} iconType="importAction" onClick={() => downloadCsv()}>
Export formatted
</EuiButtonEmpty>
};

return (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
isDisabled={totalItems == 0}
iconType='importAction'
onClick={() => downloadCsv()}
>
Export formatted
{totalItems > maxRows && (
<>
{' '}
<EuiIconTip
content={`The exported CSV will be limited to the first ${maxRows} lines. You can change this limit in Dashboard management > App Settings`}
size='m'
color='primary'
type='iInCircle'
/>
</>
)}
</EuiButtonEmpty>
</EuiFlexItem>
);
}

// Set default props
ExportTableCsv.defaultProps = {
endpoint:'/',
totalItems:0,
filters: [],
title:""
};
endpoint: '/',
totalItems: 0,
filters: [],
title: '',
};
13 changes: 13 additions & 0 deletions plugins/main/public/components/common/tables/table-wz-api.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
import React from 'react';
import { mount } from 'enzyme';
import { TableWzAPI } from './table-wz-api';
import { useAppConfig, useStateStorage } from '../hooks';

jest.mock('../hooks', () => ({
useAppConfig: jest.fn(),
useStateStorage: jest.fn(),
}));

jest.mock('../../../kibana-services', () => ({
getHttp: () => ({
Expand Down Expand Up @@ -64,6 +70,13 @@ const columns = [

describe('Table WZ API component', () => {
it('renders correctly to match the snapshot', () => {
(useAppConfig as jest.Mock).mockReturnValue({
data: {
'reports.csv.maxRows': 10000,
},
});
(useStateStorage as jest.Mock).mockReturnValue([[], jest.fn()]);

const wrapper = mount(
<TableWzAPI
title='Table'
Expand Down
69 changes: 43 additions & 26 deletions plugins/main/public/components/common/tables/table-wz-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ import { TableWithSearchBar } from './table-with-search-bar';
import { TableDefault } from './table-default';
import { WzRequest } from '../../../react-services/wz-request';
import { ExportTableCsv } from './components/export-table-csv';
import { useStateStorage } from '../hooks';

import { useStateStorage, useAppConfig } from '../hooks';
/**
* Search input custom filter button
*/
Expand All @@ -37,11 +36,22 @@ interface CustomFilterButton {
value: string;
}

const getFilters = filters => {
interface Filters {
[key: string]: string;
}

const getFilters = (filters: Filters) => {
const { default: defaultFilters, ...restFilters } = filters;
return Object.keys(restFilters).length ? restFilters : defaultFilters;
};

const formatSorting = sorting => {
if (!sorting.field || !sorting.direction) {
return '';
}
return `${sorting.direction === 'asc' ? '+' : '-'}${sorting.field}`;
guidomodarelli marked this conversation as resolved.
Show resolved Hide resolved
};

export function TableWzAPI({
actionButtons,
postActionButtons,
Expand All @@ -53,12 +63,11 @@ export function TableWzAPI({
actionButtons?:
| ReactNode
| ReactNode[]
| (({ filters }: { filters }) => ReactNode);
| (({ filters }: { filters: Filters }) => ReactNode);
postActionButtons?:
| ReactNode
| ReactNode[]
| (({ filters }: { filters }) => ReactNode);

| (({ filters }: { filters: Filters }) => ReactNode);
title?: string;
addOnTitle?: ReactNode;
description?: string;
Expand All @@ -67,18 +76,18 @@ export function TableWzAPI({
searchTable?: boolean;
endpoint: string;
buttonOptions?: CustomFilterButton[];
onFiltersChange?: Function;
onFiltersChange?: (filters: Filters) => void;
showReload?: boolean;
searchBarProps?: any;
reload?: boolean;
onDataChange?: Function;
setReload?: (newValue: number) => void;
}) {
const [totalItems, setTotalItems] = useState(0);
const [filters, setFilters] = useState({});
const [filters, setFilters] = useState<Filters>({});
const [isLoading, setIsLoading] = useState(false);

const onFiltersChange = filters =>
const [sort, setSort] = useState({});
const onFiltersChange = (filters: Filters) =>
typeof rest.onFiltersChange === 'function'
? rest.onFiltersChange(filters)
: null;
Expand All @@ -101,26 +110,26 @@ export function TableWzAPI({
: undefined,
);
const [isOpenFieldSelector, setIsOpenFieldSelector] = useState(false);

const appConfig = useAppConfig();
const maxRows = appConfig.data['reports.csv.maxRows'];
const onSearch = useCallback(async function (
endpoint,
filters,
filters: Filters,
pagination,
sorting,
) {
try {
const { pageIndex, pageSize } = pagination;
const { field, direction } = sorting.sort;
setSort(sorting.sort);
setIsLoading(true);
setFilters(filters);
onFiltersChange(filters);
const params = {
...getFilters(filters),
offset: pageIndex * pageSize,
limit: pageSize,
sort: `${direction === 'asc' ? '+' : '-'}${field}`,
sort: formatSorting(sorting.sort),
};

const response = await WzRequest.apiReq('GET', endpoint, { params });

const { affected_items: items, total_affected_items: totalItems } = (
Expand Down Expand Up @@ -182,7 +191,9 @@ export function TableWzAPI({
};

useEffect(() => {
if (rest.reload) triggerReload();
if (rest.reload) {
triggerReload();
}
}, [rest.reload]);

const ReloadButton = (
Expand Down Expand Up @@ -227,16 +238,22 @@ export function TableWzAPI({
{rest.showReload && ReloadButton}
{/* Render optional export to CSV button */}
{rest.downloadCsv && (
<ExportTableCsv
endpoint={rest.endpoint}
totalItems={totalItems}
filters={getFilters(filters)}
title={
typeof rest.downloadCsv === 'string'
? rest.downloadCsv
: rest.title
}
/>
<>
<ExportTableCsv
endpoint={rest.endpoint}
totalItems={totalItems}
filters={getFilters({
...filters,
sort: formatSorting(sort),
})}
title={
typeof rest.downloadCsv === 'string'
? rest.downloadCsv
: rest.title
}
maxRows={maxRows}
/>
</>
)}
{/* Render optional post custom action button */}
{renderActionButtons(postActionButtons, filters)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import { WzRequest } from '../../../react-services/wz-request';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';

jest.mock('../../common/hooks/use-app-config', () => ({
useAppConfig: () => ({
isReady: true,
isLoading: false,
data: {
'reports.csv.maxRows': 10000,
},
}),
}));

const data = [
{
id: '001',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ import { ModuleMitreAttackIntelligence } from './intelligence';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';

jest.mock('../../../common/hooks/use-app-config', () => ({
useAppConfig: () => ({
isReady: true,
isLoading: false,
data: {
'reports.csv.maxRows': 10000,
},
}),
}));
jest.mock(
'../../../../../../../node_modules/@elastic/eui/lib/services/accessibility/html_id_generator',
() => ({
Expand Down
19 changes: 15 additions & 4 deletions plugins/main/server/controllers/wazuh-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,8 @@ export class WazuhApiCtrl {
request: OpenSearchDashboardsRequest,
response: OpenSearchDashboardsResponseFactory,
) {
const appConfig = await context.wazuh_core.configuration.get();
const reportMaxRows = appConfig['reports.csv.maxRows'];
try {
if (!request.body || !request.body.path)
throw new Error('Field path is required');
Expand Down Expand Up @@ -782,16 +784,25 @@ export class WazuhApiCtrl {

if (totalItems && !isList) {
params.offset = 0;
itemsArray.push(...output.data.data.affected_items);
while (itemsArray.length < totalItems && params.offset < totalItems) {
params.offset += params.limit;
while (
itemsArray.length < Math.min(totalItems, reportMaxRows) &&
params.offset < Math.min(totalItems, reportMaxRows)
) {
const tmpData = await context.wazuh.api.client.asCurrentUser.request(
'GET',
`/${tmpPath}`,
{ params: params },
{ apiHostID: request.body.id },
);
itemsArray.push(...tmpData.data.data.affected_items);

const affectedItems = tmpData.data.data.affected_items;
const remainingItems = reportMaxRows - itemsArray.length;
if (itemsArray.length + affectedItems.length > reportMaxRows) {
itemsArray.push(...affectedItems.slice(0, remainingItems));
break;
}
itemsArray.push(...affectedItems);
params.offset += params.limit;
}
}

Expand Down
Loading
Loading