Skip to content

Commit

Permalink
Added pinned agent mechanic to inventory data, stats, and configurati…
Browse files Browse the repository at this point in the history
…on for consistent functionality (#7135)

* feat: integrate ButtonExploreAgent component into MainModuleAgent for enhanced agent interaction and updated layout consistency

* feat: add pinned agent mechanic across all pages for improved consistency in functionality and user experience

* feat: enhance ButtonPinnedAgent with clsx for dynamic class management and improved background styling

* refactor: clean up agent selector styles by removing unnecessary focus and hover states for better maintainability

* refactor: update WzButtonProps type to include EUI component properties for better flexibility and maintainability

* refactor: improve ButtonPinnedAgent structure by adding data-test attribute and simplifying JSX for cleaner code readability

* test: add tests for explore agent button rendering in various agent tabs for improved coverage and verification of UI functionality

* test: add tests for explore agent button rendering in various agent tabs for improved coverage and verification of UI functionality

refactor: enhance MainModuleAgent layout by adding margin and padding styles to EuiFlexItems for improved UI consistency

* refactor: add newline in ButtonPinnedAgent for improved readability and code consistency in explore agent button component

* chore: update CHANGELOG to clarify pinned agent mechanic in inventory, stats, and configuration for consistency across pages

* refactor: add unPinAgent functionality to agent components for enhanced user interaction and UI consistency across agent views

* refactor: remove unused EuiButtonEmpty import for cleaner code in configuration overview component

* refactor: update useGenericRequest hook for improved type safety and cleaner data handling in API requests

* refactor: enhance type safety in InventoryMetrics and streamline API request handling with updated useGenericRequest usage

* refactor: rename ErrorOrchestratorService to ErrorService for improved clarity and type safety in error handling logic

* refactor: add JSDoc comments to GenericRequest for improved type safety and better documentation of request parameters

* refactor: improve cluster handling logic and error management in WzConfigurationSwitch for better state management and readability

* refactor: streamline AgentStats component with improved type safety and error handling for enhanced clarity and maintainability

* Fix Prettier issue

* refactor: update test descriptions in MainModuleAgent tests to clarify the "Pinned Agent" button functionality across tabs

* refactor: remove deprecated Route for syscollector in AgentView to clean up component structure and improve readability

* refactor: rename PartialRecordMock to DeepPartialRecordMock for better clarity and maintainability in type definitions

* feat: add mock agent data for Debian, Windows, and Darwin to enhance testing and provide a comprehensive environment setup

* refactor: replace inline AGENT mock with import from test mocks for improved test organization and maintainability

* test: add API call verification in AgentStats tests to ensure correct fetching of agent stats during component rendering

* test: enhance AgentStats tests to verify API calls with correct agent IDs and endpoints on component updates

* test: refine AgentStats test description to clarify API call behavior when switching agents

* test: improve AgentStats tests to ensure correct column structure, titles, and CSV filename updates on rendering changes

* refactor: update breadcrumb types in useGlobalBreadcrumb for improved type safety and readability in context management

* refactor: enhance withGlobalBreadcrumb HOC for better type handling and maintainability in breadcrumb logic

* refactor: replace hardcoded path in agent stats breadcrumb with SECTIONS constant for improved maintainability and readability

* refactor: replace hardcoded agents-preview path with SECTIONS constant for improved maintainability and readability in configuration-main.js

* refactor: improve agent handling in withGlobalBreadcrumb for better maintainability and readability in configuration-main.js

* refactor: add global breadcrumb support in AgentView for better navigation and maintainability in index.tsx

* refactor: update unPinAgent to navigate with new URL structure for improved navigation in index.tsx

* refactor: add withGlobalBreadcrumb HOC to enhance navigation structure in agent index.tsx

* refactor: enhance AgentStats tests with clearer descriptions for column structure and title checks amidst agent changes

* refactor: replace jQuery with native DOM method for setting title attribute in breadcrumb configuration component

* refactor: improve syscollector metrics tests by adding agent ID handling and verifying data fetching for different agents

* refactor: add unit test for SoftwareTab rendering WindowsUpdatesTable on Windows platform in syscollector agent component

* refactor: add unit test for WindowsUpdatesTable to verify correct hotfixes endpoint when changing agents in syscollector component

* refactor: fix endpoint string in WindowsUpdatesTable test for accurate hotfixes URL when changing agents in syscollector component

* refactor: enhance WindowsUpdatesTable test to validate API requests for different agent IDs in syscollector component

* refactor: handle potential undefined values in packages-table component for safer access to sorting fields when rendering table

* test: add test suite for PackagesTable to validate API requests and table rendering for different agent IDs in syscollector component

* refactor: safeguard against undefined values for sorting fields in multiple syscollector table components during rendering

* test: add test suite for NetworkInterfacesTable to verify API requests and rendering behavior for changing agent IDs

* test: update test descriptions in PackagesTable and WindowsUpdatesTable for clarity on data being fetched for given agent IDs

* test: add tests for NetworkPortsTable to verify correct API requests and rendering behavior for different agent IDs

* test: rename test suite to NetworkPortsTable for clarity and proper context in related API request tests

* test: add NetworkSettingsTable tests to ensure correct rendering and API requests for changing agent IDs

* test: add tests for ProcessesTable to validate rendering and API requests when switching between agent IDs

* fix: improve optional chaining to direct access for initial sorting fields in network tables for better readability

* test: introduce reusable functions for validating agent API requests and endpoint rendering in various system collector tables

* refactor(useGenericRequest): rename response state to data and update related logic for clarity

---------

Co-authored-by: Federico Rodriguez <[email protected]>
  • Loading branch information
guidomodarelli and asteriscos authored Nov 5, 2024
1 parent 17f798c commit a20c60a
Show file tree
Hide file tree
Showing 33 changed files with 1,139 additions and 292 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Added ability to filter from File Integrity Monitoring registry inventory [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119)
- Added new field columns and ability to select the visible fields in the File Integrity Monitoring Files and Registry tables [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119)
- Added filter by value to document details fields [#7081](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7081)
- Added pinned agent mechanic to inventory data, stats, and configuration for consistent functionality [#7135](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7135)

### Changed

Expand Down
195 changes: 194 additions & 1 deletion plugins/main/public/components/agents/stats/agent-stats.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,60 @@
import React from 'react';
import { render, act } from '@testing-library/react';
import { render, act, RenderResult } from '@testing-library/react';
import { AgentStats } from './agent-stats';
import { queryDataTestAttr } from '../../../../test/public/query-attr';
import { CSS } from '../../../../test/utils/CSS';
import { WzRequest } from '../../../react-services';
import { AgentStatTable } from './table';

const agent000 = '000';
const agent001 = '001';

const apiReqMock = WzRequest.apiReq as jest.Mock;
const AgentStatTableMock = AgentStatTable as jest.Mock;

jest.mock('../../../react-services', () => ({
WzRequest: {
apiReq: jest.fn().mockResolvedValue(undefined),
},
}));

jest.mock('redux', () => ({
compose: () => (Component: React.JSX.Element) => Component,
__esModule: true,
}));

jest.mock('../../common/hocs', () => ({
withGlobalBreadcrumb: () => () => <></>,
withGuard: () => () => <></>,
withUserAuthorizationPrompt: () => () => <></>,
withErrorBoundary: () => () => <></>,
__esModule: true,
}));

jest.mock('../prompts', () => ({
PromptNoActiveAgentWithoutSelect: () => <></>,
PromptAgentFeatureVersion: () => <></>,
__esModule: true,
}));

jest.mock('../../../utils/applications', () => ({
endpointsSummary: {
id: 'endpoints-summary',
breadcrumbLabel: 'Endpoints',
},
}));

jest.mock('../../../react-services/navigation-service', () => ({
getInstance: () => ({
getUrlForApp: jest.fn().mockReturnValue('http://url'),
__esModule: true,
}),
}));

jest.mock('./table', () => ({
AgentStatTable: jest.fn(() => <></>),
}));

describe('AgentStats', () => {
it('should not render agent info ribbon', async () => {
await act(async () => {
Expand Down Expand Up @@ -65,4 +110,152 @@ describe('AgentStats', () => {
).toHaveLength(7);
});
});

it('should call api with correct agent ids and endpoints when changing agent', async () => {
apiReqMock.mockClear();

let rerender: RenderResult['rerender'];

await act(async () => {
({ rerender } = render(<AgentStats agent={{ id: agent000 }} />));
});

expect(apiReqMock).toHaveBeenCalledTimes(2);
expect(apiReqMock.mock.calls[0]).toEqual([
'GET',
`/agents/${agent000}/stats/logcollector`,
{},
]);
expect(apiReqMock.mock.calls[1]).toEqual([
'GET',
`/agents/${agent000}/stats/agent`,
{},
]);

apiReqMock.mockClear();

await act(async () => {
rerender(<AgentStats agent={{ id: agent001 }} />);
});

expect(apiReqMock).toHaveBeenCalledTimes(2);
expect(apiReqMock.mock.calls[0]).toEqual([
'GET',
`/agents/${agent001}/stats/logcollector`,
{},
]);
expect(apiReqMock.mock.calls[1]).toEqual([
'GET',
`/agents/${agent001}/stats/agent`,
{},
]);
});

it('should maintain column structure across multiple renders, either when changing agent or not', async () => {
AgentStatTableMock.mockClear();

const mockColumns = [
{
field: 'location',
name: 'Location',
sortable: true,
},
{
field: 'events',
name: 'Events',
sortable: true,
},
{
field: 'bytes',
name: 'Bytes',
sortable: true,
},
];

let rerender: RenderResult['rerender'];

await act(async () => {
({ rerender } = render(<AgentStats agent={{ id: agent000 }} />));
});

expect(AgentStatTableMock.mock.calls[0][0].columns).toEqual(mockColumns);
expect(AgentStatTableMock.mock.calls[1][0].columns).toEqual(mockColumns);

AgentStatTableMock.mockClear();

await act(async () => {
rerender(<AgentStats agent={{ id: agent001 }} />);
});

expect(AgentStatTableMock.mock.calls[0][0].columns).toEqual(mockColumns);
expect(AgentStatTableMock.mock.calls[1][0].columns).toEqual(mockColumns);
});

it('should apply correct titles after render and rerender, either when changing agent or not', async () => {
AgentStatTableMock.mockClear();

const mockDataStatLogcollectorTitle = 'Global';
const mockDataStatAgentTitle = 'Interval';

let rerender: RenderResult['rerender'];

await act(async () => {
({ rerender } = render(<AgentStats agent={{ id: agent000 }} />));
});

expect(AgentStatTableMock.mock.calls[0][0].title).toEqual(
mockDataStatLogcollectorTitle,
);
expect(AgentStatTableMock.mock.calls[1][0].title).toEqual(
mockDataStatAgentTitle,
);

AgentStatTableMock.mockClear();

await act(async () => {
rerender(<AgentStats agent={{ id: agent001 }} />);
});

expect(AgentStatTableMock.mock.calls[0][0].title).toEqual(
mockDataStatLogcollectorTitle,
);
expect(AgentStatTableMock.mock.calls[1][0].title).toEqual(
mockDataStatAgentTitle,
);
});

it('should update export csv filename correctly when changing agent', async () => {
AgentStatTableMock.mockClear();

const mockExportCSVFilename = (
agent000: string,
suffix: 'global' | 'interval',
) => `agent-stats-${agent000}-logcollector-${suffix}`;

let rerender: RenderResult['rerender'];

await act(async () => {
({ rerender } = render(<AgentStats agent={{ id: agent000 }} />));
});

expect(AgentStatTableMock.mock.calls[0][0].exportCSVFilename).toEqual(
mockExportCSVFilename(agent000, 'global'),
);
expect(AgentStatTableMock.mock.calls[1][0].exportCSVFilename).toEqual(
mockExportCSVFilename(agent000, 'interval'),
);

AgentStatTableMock.mockClear();

await act(async () => {
rerender(<AgentStats agent={{ id: agent001 }} />);
});

expect(AgentStatTableMock.mock.calls[0][0].exportCSVFilename).toEqual(
mockExportCSVFilename(agent001, 'global'),
);
expect(AgentStatTableMock.mock.calls[1][0].exportCSVFilename).toEqual(
mockExportCSVFilename(agent001, 'interval'),
);
});
});
93 changes: 49 additions & 44 deletions plugins/main/public/components/agents/stats/agent-stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@ import React, { useState, useEffect } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPanel,
EuiPage,
EuiPageBody,
EuiSpacer,
EuiText,
} from '@elastic/eui';

import {
withGlobalBreadcrumb,
withGuard,
Expand Down Expand Up @@ -48,6 +44,8 @@ import { getErrorOrchestrator } from '../../../react-services/common-services';
import { endpointSummary } from '../../../utils/applications';
import NavigationService from '../../../react-services/navigation-service';
import WzRibbon from '../../common/ribbon/ribbon';
import { Agent } from '../../endpoints-summary/types';
import { SECTIONS } from '../../../sections';

const tableColumns = [
{
Expand All @@ -67,48 +65,51 @@ const tableColumns = [
},
];

const statsAgents: { title: string; field: string; render?: (value) => any }[] =
[
{
title: 'Status',
field: 'status',
},
{
title: 'Buffer',
field: 'buffer_enabled',
render: value => (value ? 'enabled' : 'disabled'),
},
{
title: 'Message buffer',
field: 'msg_buffer',
},
{
title: 'Messages count',
field: 'msg_count',
},
{
title: 'Messages sent',
field: 'msg_sent',
},
{
title: 'Last ack',
field: 'last_ack',
render: formatUIDate,
},
{
title: 'Last keep alive',
field: 'last_keepalive',
render: formatUIDate,
},
];
const statsAgents: {
title: string;
field: string;
render?: (value: any) => any;
}[] = [
{
title: 'Status',
field: 'status',
},
{
title: 'Buffer',
field: 'buffer_enabled',
render: value => (value ? 'enabled' : 'disabled'),
},
{
title: 'Message buffer',
field: 'msg_buffer',
},
{
title: 'Messages count',
field: 'msg_count',
},
{
title: 'Messages sent',
field: 'msg_sent',
},
{
title: 'Last ack',
field: 'last_ack',
render: formatUIDate,
},
{
title: 'Last keep alive',
field: 'last_keepalive',
render: formatUIDate,
},
];

export const MainAgentStats = compose(
withErrorBoundary,
withGlobalBreadcrumb(({ agent }) => [
{
text: endpointSummary.breadcrumbLabel,
href: NavigationService.getInstance().getUrlForApp(endpointSummary.id, {
path: `#/agents-preview`,
path: `#/${SECTIONS.AGENTS_PREVIEW}`,
}),
},
{ agent },
Expand Down Expand Up @@ -143,9 +144,13 @@ export const MainAgentStats = compose(
),
)(AgentStats);

export function AgentStats(props) {
interface AgentStatsProps {
agent: Agent;
}

export function AgentStats(props: AgentStatsProps) {
const { agent } = props;
const [loading, setLoading] = useState();
const [loading, setLoading] = useState(false);
const [dataStatLogcollector, setDataStatLogcollector] = useState({});
const [dataStatAgent, setDataStatAgent] = useState();
useEffect(() => {
Expand Down Expand Up @@ -175,16 +180,16 @@ export function AgentStats(props) {
severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity,
error: {
error: error,
message: error.message || error,
title: error.name || error,
message: (error as Error).message || (error as string),
title: (error as Error).name || (error as string),
},
};
getErrorOrchestrator().handleError(options);
} finally {
setLoading(false);
}
})();
}, []);
}, [agent.id]);
return (
<EuiPage>
<EuiPageBody>
Expand Down
Loading

0 comments on commit a20c60a

Please sign in to comment.