Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,45 @@ import CreatedDisplay from 'web/components/dashboard/display/created/CreatedDisp
import createDisplay from 'web/components/dashboard/display/createDisplay';
import DataTableDisplay from 'web/components/dashboard/display/DataTableDisplay';
import {registerDisplay} from 'web/components/dashboard/registry';
import {CpesCreatedLoader} from 'web/pages/cpes/dashboard/Loaders';
import {CpesCreatedLoader} from 'web/pages/cpes/dashboard/CpeLoaders';
import Theme from 'web/utils/theme';

export const CpesCreatedDisplay = createDisplay({
loaderComponent: CpesCreatedLoader,
displayComponent: CreatedDisplay,
title: () => _('CPEs by Creation Time'),
yAxisLabel: _l('# of created CPEs'),
y2AxisLabel: _l('Total CPEs'),
xAxisLabel: _l('Time'),
yLine: {
color: Theme.darkGreenTransparent,
label: _l('Created CPEs'),
},
y2Line: {
color: Theme.darkGreenTransparent,
dashArray: '3, 2',
label: _l('Total CPEs'),
},
displayComponent: props => (
<CreatedDisplay
{...props}
title={() => _('CPEs by Creation Time')}
xAxisLabel={_('Time')}
y2AxisLabel={_('Total CPEs')}
y2Line={{
color: Theme.darkGreenTransparent,
dashArray: '3, 2',
label: _('Total CPEs'),
}}
yAxisLabel={_('# of created CPEs')}
yLine={{
color: Theme.darkGreenTransparent,
label: _('Created CPEs'),
}}
/>
),
displayId: 'cpe-by-created',
displayName: 'CpeCreatedDisplay',
filtersFilter: CPES_FILTER_FILTER,
});

export const CpesCreatedTableDisplay = createDisplay({
loaderComponent: CpesCreatedLoader,
displayComponent: DataTableDisplay,
title: () => _('CPEs by Creation Time'),
dataTitles: [_l('Creation Time'), _l('# of CPEs'), _l('Total CPEs')],
dataRow: row => [row.label, row.y, row.y2],
dataTransform: transformCreated,
displayComponent: props => (
<DataTableDisplay
{...props}
dataRow={row => [row.label ?? '', row.y, row.y2]}
dataTitles={[_('Creation Time'), _('# of CPEs'), _('Total CPEs')]}
dataTransform={transformCreated}
title={() => _('CPEs by Creation Time')}
/>
),
displayId: 'cpe-by-created-table',
displayName: 'CpeCreatedTableDisplay',
filtersFilter: CPES_FILTER_FILTER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,35 @@ import createDisplay from 'web/components/dashboard/display/createDisplay';
import CvssDisplay from 'web/components/dashboard/display/cvss/CvssDisplay';
import CvssTableDisplay from 'web/components/dashboard/display/cvss/CvssTableDisplay';
import {registerDisplay} from 'web/components/dashboard/registry';
import {CpesSeverityLoader} from 'web/pages/cpes/dashboard/Loaders';
import {CpesSeverityLoader} from 'web/pages/cpes/dashboard/CpeLoaders';

export const CpesCvssDisplay = createDisplay({
loaderComponent: CpesSeverityLoader,
displayComponent: CvssDisplay,
yLabel: _l('# of CPEs'),
title: ({data: tdata}) =>
_('CPEs by CVSS (Total: {{count}})', {count: tdata.total}),
displayComponent: props => (
<CvssDisplay
{...props}
title={({data}) =>
_('CPEs by CVSS (Total: {{count}})', {count: data.total})
}
yLabel={_('# of CPEs')}
/>
),
filtersFilter: CPES_FILTER_FILTER,
displayId: 'cpe-by-cvss',
displayName: 'CpesCvssDisplay',
});

export const CpesCvssTableDisplay = createDisplay({
loaderComponent: CpesSeverityLoader,
displayComponent: CvssTableDisplay,
dataTitles: [_l('Severity'), _l('# of CPEs')],
title: ({data: tdata}) =>
_('CPEs by CVSS (Total: {{count}})', {count: tdata.total}),
displayComponent: props => (
<CvssTableDisplay
{...props}
dataTitles={[_('Severity'), _('# of CPEs')]}
title={({data}) =>
_('CPEs by CVSS (Total: {{count}})', {count: data.total})
}
/>
),
filtersFilter: CPES_FILTER_FILTER,
displayId: 'cpe-by-cvss-table',
displayName: 'CpesCvssTableDisplay',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import Loader, {createLoadFunc} from 'web/components/dashboard/display/Loader';
import type {CreatedData} from 'web/components/dashboard/display/created/created-transform';
import Loader, {
createLoadFunc,
type DisplayLoaderProps,
} from 'web/components/dashboard/display/Loader';
import type {SeverityData} from 'web/components/dashboard/display/severity/severity-class-transform';

export const CPES_CREATED = 'cpes-created';
export const CPES_SEVERITY = 'cpes-severity';

export const cpeCreatedLoadFunc = createLoadFunc(
const cpeCreatedLoadFunc = createLoadFunc(
({gmp, filter}) => gmp.cpes.getCreatedAggregates({filter}).then(r => r.data),
CPES_CREATED,
);

export const CpesCreatedLoader = ({filter, children}) => (
export const CpesCreatedLoader = ({
filter,
children,
}: DisplayLoaderProps<CreatedData>) => (
<Loader
dataId={CPES_CREATED}
filter={filter}
Expand All @@ -24,12 +32,15 @@ export const CpesCreatedLoader = ({filter, children}) => (
</Loader>
);

export const cpeSeverityLoadFunc = createLoadFunc(
const cpeSeverityLoadFunc = createLoadFunc(
({gmp, filter}) => gmp.cpes.getSeverityAggregates({filter}).then(r => r.data),
CPES_SEVERITY,
);

export const CpesSeverityLoader = ({filter, children}) => (
export const CpesSeverityLoader = ({
filter,
children,
}: DisplayLoaderProps<SeverityData>) => (
<Loader
dataId={CPES_SEVERITY}
filter={filter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ import createDisplay from 'web/components/dashboard/display/createDisplay';
import SeverityClassDisplay from 'web/components/dashboard/display/severity/SeverityClassDisplay';
import SeverityClassTableDisplay from 'web/components/dashboard/display/severity/SeverityClassTableDisplay';
import {registerDisplay} from 'web/components/dashboard/registry';
import {CpesSeverityLoader} from 'web/pages/cpes/dashboard/Loaders';
import {CpesSeverityLoader} from 'web/pages/cpes/dashboard/CpeLoaders';

export const CpesSeverityClassDisplay = createDisplay({
loaderComponent: CpesSeverityLoader,
displayComponent: SeverityClassDisplay,
title: ({data: tdata}) =>
_('CPEs by Severity Class (Total: {{count}})', {count: tdata.total}),
displayComponent: props => (
<SeverityClassDisplay
{...props}
title={({data}) =>
_('CPEs by Severity Class (Total: {{count}})', {count: data.total})
}
/>
),
displayId: 'cpe-by-severity-class',
displayName: 'CpesSeverityClassDisplay',
filtersFilter: CPES_FILTER_FILTER,
});

export const CpesSeverityClassTableDisplay = createDisplay({
loaderComponent: CpesSeverityLoader,
displayComponent: SeverityClassTableDisplay,
title: ({data: tdata}) =>
_('CPEs by Severity Class (Total: {{count}})', {count: tdata.total}),
dataTitles: [_l('Severity Class'), _l('# of CPEs')],
displayComponent: props => (
<SeverityClassTableDisplay
{...props}
dataTitles={[_('Severity Class'), _('# of CPEs')]}
title={({data}) =>
_('CPEs by Severity Class (Total: {{count}})', {count: data.total})
}
/>
),
displayId: 'cpe-by-severity-table',
displayName: 'CpesSeverityClassTableDisplay',
filtersFilter: CPES_FILTER_FILTER,
Expand Down
138 changes: 138 additions & 0 deletions src/web/pages/cpes/dashboard/__tests__/CpeCreatedDisplay.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* SPDX-FileCopyrightText: 2026 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type {ReactElement} from 'react';
import {describe, expect, test, testing} from '@gsa/testing';
import {rendererWith, screen, waitFor} from 'web/testing';
import {
SubscriptionContext,
type SubscribeFunc,
} from 'web/components/provider/SubscriptionProvider';
import {getDisplay} from 'web/components/dashboard/registry';
import {
CpesCreatedDisplay,
CpesCreatedTableDisplay,
} from 'web/pages/cpes/dashboard/CpeCreatedDisplay';

const loaderData = {
groups: [{value: '2026-01-01', count: '5', c_count: '10'}],
};

vi.mock('web/components/dashboard/display/created/CreatedDisplay', () => ({
default: ({title, xAxisLabel, yAxisLabel, y2AxisLabel}) => (
<div data-testid="mock-created-display">
<span data-testid="title">{title?.()}</span>
<span data-testid="x-axis-label">{xAxisLabel}</span>
<span data-testid="y-axis-label">{yAxisLabel}</span>
<span data-testid="y2-axis-label">{y2AxisLabel}</span>
</div>
),
}));

vi.mock('web/components/dashboard/display/DataTableDisplay', () => ({
default: ({data, dataRow, dataTitles, dataTransform, title}) => {
const transformedData = dataTransform ? dataTransform(data) : data;

return (
<div data-testid="mock-data-table-display">
<span data-testid="title">{title?.()}</span>
<span data-testid="data-titles">{dataTitles?.join('|')}</span>
{transformedData.map((row, index) => (
<span key={index} data-testid={`data-row-${index}`}>
{dataRow(row).join('|')}
</span>
))}
</div>
);
},
}));

const createGmp = () => ({
cpes: {
getCreatedAggregates: testing.fn().mockResolvedValue({data: loaderData}),
},
filters: {
get: testing.fn().mockResolvedValue({
data: [],
meta: {filter: 'type=cpe', counts: {}},
}),
},
});

const renderDisplay = (component: ReactElement) => {
const subscribe: SubscribeFunc = testing.fn().mockReturnValue(testing.fn());
const {render} = rendererWith({gmp: createGmp()});

return render(
<SubscriptionContext.Provider value={subscribe}>
{component}
</SubscriptionContext.Provider>,
);
};

describe('CpesCreatedDisplay', () => {
test('should export a valid component with the correct configuration', () => {
expect(CpesCreatedDisplay).toBeDefined();
expect(typeof CpesCreatedDisplay).toBe('function');
expect(CpesCreatedDisplay.displayId).toBe('cpe-by-created');
expect(CpesCreatedDisplay.displayName).toBe('CpeCreatedDisplay');
});

test('should be registered with the correct title', () => {
const registered = getDisplay(CpesCreatedDisplay.displayId);

expect(registered?.component).toBe(CpesCreatedDisplay);
expect(String(registered?.title)).toBe('Chart: CPEs by Creation Time');
});

test('should render the configured chart labels', async () => {
renderDisplay(<CpesCreatedDisplay height={200} width={200} />);

await waitFor(() => {
expect(screen.getByTestId('title')).toHaveTextContent(
'CPEs by Creation Time',
);
expect(screen.getByTestId('x-axis-label')).toHaveTextContent('Time');
expect(screen.getByTestId('y-axis-label')).toHaveTextContent(
'# of created CPEs',
);
expect(screen.getByTestId('y2-axis-label')).toHaveTextContent(
'Total CPEs',
);
});
});
});

describe('CpesCreatedTableDisplay', () => {
test('should export a valid component with the correct configuration', () => {
expect(CpesCreatedTableDisplay).toBeDefined();
expect(typeof CpesCreatedTableDisplay).toBe('function');
expect(CpesCreatedTableDisplay.displayId).toBe('cpe-by-created-table');
expect(CpesCreatedTableDisplay.displayName).toBe('CpeCreatedTableDisplay');
});

test('should be registered with the correct title', () => {
const registered = getDisplay(CpesCreatedTableDisplay.displayId);

expect(registered?.component).toBe(CpesCreatedTableDisplay);
expect(String(registered?.title)).toBe('Table: CPEs by Creation Time');
});

test('should render the configured table data', async () => {
renderDisplay(<CpesCreatedTableDisplay height={200} width={200} />);

await waitFor(() => {
expect(screen.getByTestId('title')).toHaveTextContent(
'CPEs by Creation Time',
);
expect(screen.getByTestId('data-titles')).toHaveTextContent(
'Creation Time|# of CPEs|Total CPEs',
);
expect(screen.getByTestId('data-row-0')).toHaveTextContent(
'01/01/2026|5|10',
);
});
});
});
Loading