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 {CvesCreatedLoader} from 'web/pages/cves/dashboard/Loaders';
import {CvesCreatedLoader} from 'web/pages/cves/dashboard/CveLoaders';
import Theme from 'web/utils/theme';

export const CvesCreatedDisplay = createDisplay({
loaderComponent: CvesCreatedLoader,
displayComponent: CreatedDisplay,
title: () => _('CVEs by Creation Time'),
yAxisLabel: _l('# of created CVEs'),
y2AxisLabel: _l('Total CVEs'),
xAxisLabel: _l('Time'),
yLine: {
color: Theme.darkGreenTransparent,
label: _l('Created CVEs'),
},
y2Line: {
color: Theme.darkGreenTransparent,
dashArray: '3, 2',
label: _l('Total CVEs'),
},
displayComponent: props => (
<CreatedDisplay
{...props}
title={() => _('CVEs by Creation Time')}
xAxisLabel={_('Time')}
y2AxisLabel={_('Total CVEs')}
y2Line={{
color: Theme.darkGreenTransparent,
dashArray: '3, 2',
label: _('Total CVEs'),
}}
yAxisLabel={_('# of created CVEs')}
yLine={{
color: Theme.darkGreenTransparent,
label: _('Created CVEs'),
}}
/>
),
displayId: 'cve-by-created',
displayName: 'CveCreatedDisplay',
filtersFilter: CVES_FILTER_FILTER,
});

export const CvesCreatedTableDisplay = createDisplay({
loaderComponent: CvesCreatedLoader,
displayComponent: DataTableDisplay,
title: () => _('CVEs by Creation Time'),
dataTitles: [_l('Creation Time'), _l('# of CVEs'), _l('Total CVEs')],
dataRow: row => [row.label, row.y, row.y2],
dataTransform: transformCreated,
displayComponent: props => (
<DataTableDisplay
{...props}
dataRow={row => [row.label ?? '', row.y, row.y2]}
dataTitles={[_l('Creation Time'), _l('# of CVEs'), _l('Total CVEs')]}
dataTransform={transformCreated}
title={() => _('CVEs by Creation Time')}
/>
),
displayId: 'cve-by-created-table',
displayName: 'CveCreatedTableDisplay',
filtersFilter: CVES_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 {CvesSeverityLoader} from 'web/pages/cves/dashboard/Loaders';
import {CvesSeverityLoader} from 'web/pages/cves/dashboard/CveLoaders';

export const CvesCvssDisplay = createDisplay({
loaderComponent: CvesSeverityLoader,
displayComponent: CvssDisplay,
yLabel: _l('# of CVEs'),
title: ({data: tdata}) =>
_('CVEs by CVSS (Total: {{count}})', {count: tdata.total}),
displayComponent: props => (
<CvssDisplay
{...props}
title={({data}) =>
_('CVEs by CVSS (Total: {{count}})', {count: data.total})
}
yLabel={_('# of CVEs')}
/>
),
filtersFilter: CVES_FILTER_FILTER,
displayId: 'cve-by-cvss',
displayName: 'CvesCvssDisplay',
});

export const CvesCvssTableDisplay = createDisplay({
loaderComponent: CvesSeverityLoader,
displayComponent: CvssTableDisplay,
dataTitles: [_l('Severity'), _l('# of CVEs')],
title: ({data: tdata}) =>
_('CVEs by CVSS (Total: {{count}})', {count: tdata.total}),
displayComponent: props => (
<CvssTableDisplay
{...props}
dataTitles={[_('Severity'), _('# of CVEs')]}
title={({data}) =>
_('CVEs by CVSS (Total: {{count}})', {count: data.total})
}
/>
),
filtersFilter: CVES_FILTER_FILTER,
displayId: 'cve-by-cvss-table',
displayName: 'CvesCvssTableDisplay',
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 CVES_SEVERITY = 'cves-severity';
export const CVES_CREATED = 'cves-created';

export const cveCreatedLoadFunc = createLoadFunc(
const cveCreatedLoadFunc = createLoadFunc(
({gmp, filter}) => gmp.cves.getCreatedAggregates({filter}).then(r => r.data),
CVES_CREATED,
);

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

export const cveSeverityLoadFunc = createLoadFunc(
const cveSeverityLoadFunc = createLoadFunc(
({gmp, filter}) => gmp.cves.getSeverityAggregates({filter}).then(r => r.data),
CVES_SEVERITY,
);

export const CvesSeverityLoader = ({filter, children}) => (
export const CvesSeverityLoader = ({
filter,
children,
}: DisplayLoaderProps<SeverityData>) => (
<Loader
dataId={CVES_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 {CvesSeverityLoader} from 'web/pages/cves/dashboard/Loaders';
import {CvesSeverityLoader} from 'web/pages/cves/dashboard/CveLoaders';

export const CvesSeverityClassDisplay = createDisplay({
loaderComponent: CvesSeverityLoader,
displayComponent: SeverityClassDisplay,
title: ({data: tdata}) =>
_('CVEs by Severity Class (Total: {{count}})', {count: tdata.total}),
displayComponent: props => (
<SeverityClassDisplay
{...props}
title={({data}) =>
_('CVEs by Severity Class (Total: {{count}})', {count: data.total})
}
/>
),
displayId: 'cve-by-severity-class',
displayName: 'CvesSeverityClassDisplay',
filtersFilter: CVES_FILTER_FILTER,
});

export const CvesSeverityClassTableDisplay = createDisplay({
loaderComponent: CvesSeverityLoader,
displayComponent: SeverityClassTableDisplay,
title: ({data: tdata}) =>
_('CVEs by Severity Class (Total: {{count}})', {count: tdata.total}),
dataTitles: [_l('Severity Class'), _l('# of CVEs')],
displayComponent: props => (
<SeverityClassTableDisplay
{...props}
dataTitles={[_('Severity Class'), _('# of CVEs')]}
title={({data}) =>
_('CVEs by Severity Class (Total: {{count}})', {count: data.total})
}
/>
),
displayId: 'cve-by-severity-table',
displayName: 'CvesSeverityClassTableDisplay',
filtersFilter: CVES_FILTER_FILTER,
Expand Down
138 changes: 138 additions & 0 deletions src/web/pages/cves/dashboard/__tests__/CveCreatedDisplay.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 {
CvesCreatedDisplay,
CvesCreatedTableDisplay,
} from 'web/pages/cves/dashboard/CveCreatedDisplay';

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 = () => ({
cves: {
getCreatedAggregates: testing.fn().mockResolvedValue({data: loaderData}),
},
filters: {
get: testing.fn().mockResolvedValue({
data: [],
meta: {filter: 'type=cve', 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('CvesCreatedDisplay', () => {
test('should export a valid component with the correct configuration', () => {
expect(CvesCreatedDisplay).toBeDefined();
expect(typeof CvesCreatedDisplay).toBe('function');
expect(CvesCreatedDisplay.displayId).toBe('cve-by-created');
expect(CvesCreatedDisplay.displayName).toBe('CveCreatedDisplay');
});

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

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

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

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

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

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

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

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

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