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

Allow upgrade agent from actions column #6476

Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Added a migration task to setup the configuration using a configuration file [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337)
- Added the ability to manage the API hosts from the Server APIs [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337)
- Added edit groups action to Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250)
- Added upgrade agent action to Endpoints Summary [#6476](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6476)
- Added global actions add agents to groups and remove agents from groups to Endpoints Summary [#6274](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6274)
- Added propagation of updates from the table to dashboard visualizations in Endpoints summary [#6460](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6460)

Expand Down
10 changes: 10 additions & 0 deletions docker/imposter/tasks/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"data": {
"affected_items": [],
"total_affected_items": 0,
"total_failed_items": 0,
"failed_items": []
},
"message": "All specified task's status were returned",
"error": 0
}
47 changes: 47 additions & 0 deletions docker/imposter/tasks/status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
var storeWazuh = stores.open('storeWazuh');
var attemptRestart = storeWazuh.load('attempt');

var taskStatus = context.request.queryParams.status;

if (!taskStatus) {
respond().withStatusCode(200).withFile('tasks/status_in_progress_2.json');
}

if (attemptRestart < 5) {
storeWazuh.save('attempt', attemptRestart + 1);

if (taskStatus === 'In progress') {
respond().withStatusCode(200).withFile('tasks/status_in_progress_2.json');
}

if (taskStatus === 'Done' || taskStatus === 'Failed') {
respond().withStatusCode(200).withFile('tasks/empty.json');
}
} else if (attemptRestart < 10) {
storeWazuh.save('attempt', attemptRestart + 1);

if (taskStatus === 'In progress') {
respond().withStatusCode(200).withFile('tasks/status_in_progress_1.json');
}

if (taskStatus === 'Done') {
respond().withStatusCode(200).withFile('tasks/status_done.json');
}

if (taskStatus === 'Failed') {
respond().withStatusCode(200).withFile('tasks/empty.json');
}
} else {
if (taskStatus === 'In progress') {
respond().withStatusCode(200).withFile('tasks/empty.json');
}

if (taskStatus === 'Done') {
respond().withStatusCode(200).withFile('tasks/status_done.json');
}

if (taskStatus === 'Failed') {
storeWazuh.save('attempt', 0);
respond().withStatusCode(200).withFile('tasks/status_failed.json');
}
}
22 changes: 22 additions & 0 deletions docker/imposter/tasks/status_done.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"data": {
"affected_items": [
{
"message": "Success",
"agent": "001",
"task_id": 1,
"node": "worker2",
"module": "upgrade_module",
"command": "upgrade",
"status": "Done",
"create_time": "2024-03-11T11:55:33.000Z",
"last_update_time": "2020-03-11T12:05:10.000Z"
}
],
"total_affected_items": 1,
"total_failed_items": 0,
"failed_items": []
},
"message": "All specified task's status were returned",
"error": 0
}
22 changes: 22 additions & 0 deletions docker/imposter/tasks/status_failed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"data": {
"affected_items": [
{
"message": "Success",
"agent": "002",
"task_id": 2,
"node": "worker2",
"module": "upgrade_module",
"command": "upgrade",
"status": "Failed",
"create_time": "2024-03-11T11:57:44.000Z",
"last_update_time": "2020-03-11T12:11:32.000Z"
}
],
"total_affected_items": 1,
"total_failed_items": 0,
"failed_items": []
},
"message": "All specified task's status were returned",
"error": 0
}
22 changes: 22 additions & 0 deletions docker/imposter/tasks/status_in_progress_1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"data": {
"affected_items": [
{
"message": "Success",
"agent": "002",
"task_id": 2,
"node": "worker2",
"module": "upgrade_module",
"command": "upgrade",
"status": "In progress",
"create_time": "2024-03-11T11:57:44.000Z",
"last_update_time": "2020-03-11T11:57:46.000Z"
}
],
"total_affected_items": 1,
"total_failed_items": 0,
"failed_items": []
},
"message": "All specified task's status were returned",
"error": 0
}
33 changes: 33 additions & 0 deletions docker/imposter/tasks/status_in_progress_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"data": {
"affected_items": [
{
"message": "Success",
"agent": "001",
"task_id": 1,
"node": "worker2",
"module": "upgrade_module",
"command": "upgrade",
"status": "In progress",
"create_time": "2024-03-11T11:55:33.000Z",
"last_update_time": "2020-03-11T11:55:36.000Z"
},
{
"message": "Success",
"agent": "002",
"task_id": 2,
"node": "worker2",
"module": "upgrade_module",
"command": "upgrade",
"status": "In progress",
"create_time": "2024-03-11T11:57:44.000Z",
"last_update_time": "2020-03-11T11:57:46.000Z"
}
],
"total_affected_items": 2,
"total_failed_items": 0,
"failed_items": []
},
"message": "All specified task's status were returned",
"error": 0
}
3 changes: 3 additions & 0 deletions docker/imposter/wazuh-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,9 @@ resources:
# List tasks
- method: GET
path: /tasks/status
response:
statusCode: 200
scriptFile: tasks/status.js

# ===================================================== #
# VULNERABILITY
Expand Down
12 changes: 12 additions & 0 deletions plugins/main/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ export const AGENT_STATUS_CODE = [
},
];

export const API_NAME_TASK_STATUS = {
DONE: 'Done',
IN_PROGRESS: 'In progress',
FAILED: 'Failed',
} as const;

export const UI_TASK_STATUS = [
API_NAME_TASK_STATUS.DONE,
API_NAME_TASK_STATUS.IN_PROGRESS,
API_NAME_TASK_STATUS.FAILED,
];

// Documentation
export const DOCUMENTATION_WEB_BASE_URL = 'https://documentation.wazuh.com';

Expand Down
13 changes: 9 additions & 4 deletions plugins/main/public/components/common/tables/table-wz-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ const getFilters = filters => {

export function TableWzAPI({
actionButtons,
addOnTitle,
extra,
setReload,
...rest
}: {
actionButtons?:
Expand All @@ -53,6 +56,7 @@ export function TableWzAPI({
title?: string;
addOnTitle?: ReactNode;
description?: string;
extra?: ReactNode;
downloadCsv?: boolean | string;
searchTable?: boolean;
endpoint: string;
Expand Down Expand Up @@ -166,8 +170,8 @@ export function TableWzAPI({
*/
const triggerReload = () => {
setReloadFootprint(Date.now());
if (rest.setReload) {
rest.setReload(Date.now());
if (setReload) {
setReload(Date.now());
}
};

Expand Down Expand Up @@ -202,9 +206,9 @@ export function TableWzAPI({
</EuiTitle>
)}
</EuiFlexItem>
{rest.addOnTitle ? (
{addOnTitle ? (
<EuiFlexItem className='wz-flex-basis-auto' grow={false}>
{rest.addOnTitle}
{addOnTitle}
</EuiFlexItem>
) : null}
</EuiFlexGroup>
Expand Down Expand Up @@ -297,6 +301,7 @@ export function TableWzAPI({
<EuiText color='subdued'>{rest.description}</EuiText>
</EuiFlexItem>
)}
{extra ? <EuiFlexItem>{extra}</EuiFlexItem> : null}
<EuiFlexItem>{table}</EuiFlexItem>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export const EndpointsSummary = compose(
<WzReduxProvider>
<AgentsTable
filters={this.state.agentTableFilters}
externalReload={this.state.reload}
setExternalReload={this.setReload}
/>
</WzReduxProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react-hooks';
import { useGetTotalAgents } from './agents';
import { getAgentsService } from '../services';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { useGetTotalAgents } from './agents';
export { useGetGroups } from './groups';
export { useGetUpgradeTasks } from './upgrade-tasks';
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { renderHook } from '@testing-library/react-hooks';
import { getTasks } from '../services';
import { useGetUpgradeTasks } from './upgrade-tasks';
import { API_NAME_TASK_STATUS } from '../../../../common/constants';

jest.mock('../services', () => ({
getTasks: jest.fn(),
}));

jest.useFakeTimers();
jest.spyOn(global, 'clearInterval');

describe('useGetUpgradeTasks hook', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should fetch initial data without any error', async () => {
const mockGetTasks = jest.requireMock('../services').getTasks;
mockGetTasks.mockImplementation(async ({ status }) => {
if (status === API_NAME_TASK_STATUS.IN_PROGRESS) {
return { total_affected_items: 5 };
}
if (status === API_NAME_TASK_STATUS.DONE) {
return { total_affected_items: 3 };
}
return { total_affected_items: 2 };
});

const { result, waitForNextUpdate } = renderHook(() =>
useGetUpgradeTasks(false),
);

expect(result.current.getInProgressIsLoading).toBe(true);
expect(result.current.totalInProgressTasks).toBe(0);
expect(result.current.getInProgressError).toBeUndefined();

expect(result.current.getSuccessIsLoading).toBe(true);
expect(result.current.totalSuccessTasks).toBe(0);
expect(result.current.getSuccessError).toBeUndefined();

expect(result.current.getErrorIsLoading).toBe(true);
expect(result.current.totalErrorUpgradeTasks).toBe(0);
expect(result.current.getErrorTasksError).toBeUndefined();

await waitForNextUpdate();
jest.advanceTimersByTime(500);

expect(result.current.getInProgressIsLoading).toBe(false);
expect(result.current.totalInProgressTasks).toBe(5);
expect(result.current.getInProgressError).toBeUndefined();

jest.advanceTimersByTime(500);

expect(result.current.getSuccessIsLoading).toBe(false);
expect(result.current.totalSuccessTasks).toBe(3);
expect(result.current.getSuccessError).toBeUndefined();

jest.advanceTimersByTime(500);

expect(result.current.getErrorIsLoading).toBe(false);
expect(result.current.totalErrorUpgradeTasks).toBe(2);
expect(result.current.getErrorTasksError).toBeUndefined();
});

it('should clear interval when totalInProgressTasks is 0', async () => {
const mockGetTasks = jest.requireMock('../services').getTasks;
mockGetTasks.mockResolvedValue({ total_affected_items: 0 });

const { waitForNextUpdate } = renderHook(() => useGetUpgradeTasks(false));

await waitForNextUpdate();
jest.advanceTimersByTime(500);

expect(clearInterval).toHaveBeenCalledTimes(1);
});

it('should handle error while fetching data', async () => {
const mockErrorMessage = 'Some error occurred';
(getTasks as jest.Mock).mockRejectedValue(mockErrorMessage);

const { result, waitForNextUpdate } = renderHook(() =>
useGetUpgradeTasks(0),
);

expect(result.current.getInProgressIsLoading).toBeTruthy();
await waitForNextUpdate();
expect(result.current.getInProgressError).toBe(mockErrorMessage);
expect(result.current.getInProgressIsLoading).toBeFalsy();
});
});
Loading
Loading