Skip to content

Commit

Permalink
Merge branch '4.9.0' into enhancement/6201-replace-plugins-configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
asteriscos authored Mar 8, 2024
2 parents 9513e19 + 9a0a9a5 commit 31d5ab7
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- 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 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)

### Changed

Expand Down
68 changes: 68 additions & 0 deletions plugins/main/public/components/common/hooks/use-service.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { renderHook } from '@testing-library/react-hooks';
import { useService } from './use-service';

const successfulService = async (_params?: any) => {
return Promise.resolve('test data');
};
const failingService = async (_params?: any) => {
return Promise.reject('Error occurred');
};
const successfulRefreshService = async (_params?: any) => {
return Promise.resolve(Date.now());
};

const successfulServiceWithParams = jest.fn().mockResolvedValue('test data');

describe('useService hook', () => {
it('should return data and success state when service call is successful', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useService(successfulService),
);

await waitForNextUpdate();

expect(result.current.data).toEqual('test data');
expect(result.current.isLoading).toEqual(false);
expect(result.current.isSuccess).toEqual(true);
expect(result.current.isError).toEqual(false);
expect(result.current.error).toBeUndefined();
});

it('should return error state when service call fails', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useService(failingService),
);

await waitForNextUpdate();

expect(result.current.data).toBeUndefined();
expect(result.current.isLoading).toEqual(false);
expect(result.current.isSuccess).toEqual(false);
expect(result.current.isError).toEqual(true);
expect(result.current.error).toEqual('Error occurred');
});

it('should fetch data again with new data when refresh value changes', async () => {
const { result, rerender, waitForNextUpdate } = renderHook(
({ refresh }) => useService(successfulRefreshService, undefined, refresh),
{ initialProps: { refresh: 0 } },
);

await waitForNextUpdate();

const data = result.current.data;

rerender({ refresh: 1 });
await waitForNextUpdate();

expect(result.current.data).not.toEqual(data);
});

it('should call the service with provided parameters', async () => {
const params = { id: 123456 };

renderHook(() => useService(successfulServiceWithParams, params));

expect(successfulServiceWithParams).toHaveBeenCalledWith(params);
});
});
40 changes: 40 additions & 0 deletions plugins/main/public/components/common/hooks/use-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState, useEffect } from 'react';

interface useServiceResponse<T> {
data: T | undefined;
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
error: any;
}

export function useService<T>(
service: (params?: any | undefined) => Promise<T>,
params?: any | undefined,
refresh?: number | undefined,
): useServiceResponse<T> {
const [data, setData] = useState<T | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const [isError, setIsError] = useState<boolean>(false);
const [error, setError] = useState<any>(undefined);
useEffect(() => {
const handleService = async () => {
setIsLoading(true);
try {
const response = await service(params);
setData(response);
setIsSuccess(true);
} catch (error) {
setIsError(true);
setError(error);
} finally {
setIsLoading(false);
}
};

handleService();
}, [refresh]);

return { data, isLoading, isSuccess, isError, error };
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function TableWzAPI({
searchBarProps?: any;
reload?: boolean;
onDataChange?: Function;
setReload?: (newValue: number) => void;
}) {
const [totalItems, setTotalItems] = useState(0);
const [filters, setFilters] = useState({});
Expand Down Expand Up @@ -161,10 +162,13 @@ export function TableWzAPI({
};

/**
* Generate a new reload footprint
* Generate a new reload footprint and set reload to propagate refresh
*/
const triggerReload = () => {
setReloadFootprint(Date.now());
if (rest.setReload) {
rest.setReload(Date.now());
}
};

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class ResizeObserver {
}
global.ResizeObserver = ResizeObserver;

jest.mock('../../../common/hooks/useApiService', () => ({
jest.mock('../../../common/hooks/use-service', () => ({
__esModule: true,
useApiService: jest.fn(),
useService: jest.fn(),
}));

describe('DonutCard', () => {
Expand Down Expand Up @@ -45,12 +45,12 @@ describe('DonutCard', () => {
},
];
const mockGetInfo = jest.fn().mockResolvedValue(mockData);
const useApiServiceMock = jest.fn(() => [mockLoading, mockData]);
const useServiceMock = jest.fn(() => ({data: mockData, isLoading: mockLoading}));
const mockGetInfoNoData = jest.fn().mockResolvedValue([]);
const useApiServiceMockNoData = jest.fn(() => [mockLoading, []]);
const useServiceMockNoData = jest.fn(() => ({data: [], isLoading: mockLoading}));

it('renders with data', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMock;
require('../../../common/hooks/use-service').useService = useServiceMock;

await act(async () => {
const { getByText } = render(
Expand All @@ -67,7 +67,7 @@ describe('DonutCard', () => {
});

it('handles click on data', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMock;
require('../../../common/hooks/use-service').useService = useServiceMock;

const handleClick = jest.fn();
const firstMockData = mockData[0];
Expand All @@ -86,7 +86,7 @@ describe('DonutCard', () => {
});

it('show noDataTitle and noDataMessage when no data', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMockNoData;
require('../../../common/hooks/use-service').useService = useServiceMockNoData;

await act(async () => {
const { getByText } = render(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiCard } from '@elastic/eui';
import { useApiService } from '../../../common/hooks/useApiService';
import { VisualizationBasic } from '../../../common/charts/visualizations/basic';
import { useService } from '../../../common/hooks/use-service';

interface AgentsByStatusCardProps {
title?: string;
description?: string;
betaBadgeLabel?: string;
noDataTitle?: string;
noDataMessage?: string;
reload?: number;
getInfo: () => Promise<any[]>;
onClickLabel?: (status: any) => void;
[key: string]: any;
Expand All @@ -20,11 +21,12 @@ const DonutCard = ({
betaBadgeLabel,
noDataTitle = 'No results',
noDataMessage = 'No results were found',
reload,
getInfo,
onClickLabel,
...props
}: AgentsByStatusCardProps) => {
const [loading, data] = useApiService<any>(getInfo, undefined);
const { data, isLoading } = useService<any>(getInfo, undefined, reload);

const handleClick = (item: any) => {
if (onClickLabel) {
Expand All @@ -41,7 +43,7 @@ const DonutCard = ({
<EuiFlexGroup>
<EuiFlexItem className='align-items-center'>
<VisualizationBasic
isLoading={loading}
isLoading={isLoading}
type='donut'
size={{ width: '100%', height: '150px' }}
showLegend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { mount } from 'enzyme';
import { EuiButtonEmpty, EuiLink } from '@elastic/eui';
import { webDocumentationLink } from '../../../../../common/services/web_documentation';

jest.mock('../../../common/hooks/useApiService', () => ({
jest.mock('../../../common/hooks/use-service', () => ({
__esModule: true,
useApiService: jest.fn(),
useService: jest.fn(),
}));

describe('OutdatedAgentsCard', () => {
Expand All @@ -21,7 +21,7 @@ describe('OutdatedAgentsCard', () => {

const mockLoading = false;
const mockDataNoOutdatedAgents = [];
const useApiServiceMockNoOutdatedAgent = jest.fn(() => [mockLoading, mockDataNoOutdatedAgents]);
const useServiceMockNoOutdatedAgent = jest.fn(() => ({data: mockDataNoOutdatedAgents, isLoading: mockLoading}));
const mockDataOutdatedAgents = [
{
version: "Wazuh v3.0.0",
Expand All @@ -34,12 +34,12 @@ describe('OutdatedAgentsCard', () => {
name: "dmz002"
}
];
const useApiServiceMockOutdatedAgent = jest.fn(() => [mockLoading, mockDataOutdatedAgents]);
const useServiceMockOutdatedAgent = jest.fn(() => ({data: mockDataOutdatedAgents, isLoading: mockLoading}));

const handleClick = jest.fn();

it('renders with not outdated agents', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMockNoOutdatedAgent;
require('../../../common/hooks/use-service').useService = useServiceMockNoOutdatedAgent;

await act(async () => {
const { getByTestId } = render(
Expand All @@ -53,7 +53,7 @@ describe('OutdatedAgentsCard', () => {
});

it('renders with outdated agents', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMockOutdatedAgent;
require('../../../common/hooks/use-service').useService = useServiceMockOutdatedAgent;

await act(async () => {
const { getByTestId } = render(
Expand All @@ -67,7 +67,7 @@ describe('OutdatedAgentsCard', () => {
});

it('renders popover on click with outdated agents', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMockOutdatedAgent;
require('../../../common/hooks/use-service').useService = useServiceMockOutdatedAgent;

const wrapper = await mount(
<OutdatedAgentsCard onClick={handleClick} />,
Expand All @@ -83,7 +83,7 @@ describe('OutdatedAgentsCard', () => {
});

it('handles click with correct data', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMockOutdatedAgent;
require('../../../common/hooks/use-service').useService = useServiceMockOutdatedAgent;

const wrapper = await mount(
<OutdatedAgentsCard onClick={handleClick} />,
Expand All @@ -103,7 +103,7 @@ describe('OutdatedAgentsCard', () => {
});

it('EuiButtonEmpty filter must be disabled when no data', async () => {
require('../../../common/hooks/useApiService').useApiService = useApiServiceMockNoOutdatedAgent;
require('../../../common/hooks/use-service').useService = useServiceMockNoOutdatedAgent;

const wrapper = await mount(
<OutdatedAgentsCard onClick={handleClick} />,
Expand All @@ -123,7 +123,7 @@ describe('OutdatedAgentsCard', () => {
const documentationLink = webDocumentationLink(
'upgrade-guide/wazuh-agent/index.html',
);
require('../../../common/hooks/useApiService').useApiService = useApiServiceMockNoOutdatedAgent;
require('../../../common/hooks/use-service').useService = useServiceMockNoOutdatedAgent;

const wrapper = await mount(
<OutdatedAgentsCard onClick={handleClick} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,25 @@ import {
} from '@elastic/eui';
import './outdated-agents-card.scss';
import { getOutdatedAgents } from '../../services/get-outdated-agents';
import { useApiService } from '../../../common/hooks/useApiService';
import { webDocumentationLink } from '../../../../../common/services/web_documentation';
import { useService } from '../../../common/hooks/use-service';

interface OutdatedAgentsCardProps {
onClick?: (status: any) => void;
reload?: number;
[key: string]: any;
}

const OutdatedAgentsCard = ({ onClick, ...props }: OutdatedAgentsCardProps) => {
const [loading, data] = useApiService<any>(getOutdatedAgents, undefined);
const OutdatedAgentsCard = ({
onClick,
reload,
...props
}: OutdatedAgentsCardProps) => {
const { data, isLoading } = useService<any>(
getOutdatedAgents,
undefined,
reload,
);
const outdatedAgents = data?.length;
const contentType = outdatedAgents > 0 ? 'warning' : 'success';
const contentIcon = outdatedAgents > 0 ? 'alert' : 'check';
Expand Down Expand Up @@ -65,7 +74,7 @@ const OutdatedAgentsCard = ({ onClick, ...props }: OutdatedAgentsCardProps) => {
</EuiTextColor>
}
titleColor='danger'
isLoading={loading}
isLoading={isLoading}
titleSize='l'
textAlign='center'
reverse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,40 @@ interface EndpointsSummaryDashboardProps {
filterAgentByOS: (data: any) => void;
filterAgentByGroup: (data: any) => void;
filterByOutdatedAgent: (data: any) => void;
reloadDashboard?: number;
}

export const EndpointsSummaryDashboard: FC<EndpointsSummaryDashboardProps> = ({
filterAgentByStatus,
filterAgentByOS,
filterAgentByGroup,
filterByOutdatedAgent,
reloadDashboard,
}) => {
return (
<div className='endpoints-summary-container-indicators'>
<DonutCard
betaBadgeLabel='Agents by Status'
onClickLabel={filterAgentByStatus}
getInfo={getSummaryAgentsStatus}
reload={reloadDashboard}
/>
<DonutCard
betaBadgeLabel='Top 5 agents by OS'
onClickLabel={filterAgentByOS}
getInfo={getAgentsByOs}
reload={reloadDashboard}
/>
<DonutCard
betaBadgeLabel='Top 5 agents by Group'
onClickLabel={filterAgentByGroup}
getInfo={getAgentsByGroup}
reload={reloadDashboard}
/>
<OutdatedAgentsCard
onClick={filterByOutdatedAgent}
reload={reloadDashboard}
/>
<OutdatedAgentsCard onClick={filterByOutdatedAgent} />
</div>
);
};
Loading

0 comments on commit 31d5ab7

Please sign in to comment.