diff --git a/src/PresentationalComponents/TableView/TableViewAssets.js b/src/PresentationalComponents/TableView/TableViewAssets.js
index 2208e6d15..e6fff88ad 100644
--- a/src/PresentationalComponents/TableView/TableViewAssets.js
+++ b/src/PresentationalComponents/TableView/TableViewAssets.js
@@ -1,4 +1,4 @@
-import { cellWidth, expandable, sortable } from '@patternfly/react-table';
+import { expandable, sortable, wrappable, nowrap } from '@patternfly/react-table';
import messages from '../../Messages';
import { intl } from '../../Utilities/IntlProvider';
@@ -6,53 +6,38 @@ export const advisoriesColumns = [
{
title: intl.formatMessage(messages.labelsColumnsName),
cellFormatters: [expandable],
- transforms: [sortable, cellWidth(15)],
+ transforms: [sortable, wrappable],
key: 'id',
- isShown: true,
- isShownByDefault: true,
- isUntoggleable: true,
},
{
title: intl.formatMessage(messages.labelsColumnsSynopsis),
- transforms: [sortable],
+ transforms: [sortable, nowrap],
key: 'synopsis',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsType),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'advisory_type_name',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsSeverity),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'severity',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsAffectedSystems),
- transforms: [sortable, cellWidth(15)],
+ transforms: [sortable, wrappable],
key: 'applicable_systems',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsReboot),
- transforms: [sortable],
+ transforms: [sortable, wrappable],
key: 'reboot_required',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsPublishDate),
- transforms: [sortable, cellWidth(15)],
+ transforms: [sortable, wrappable],
key: 'public_date',
- isShown: true,
- isShownByDefault: true,
},
];
@@ -60,99 +45,71 @@ export const systemAdvisoriesColumns = [
{
title: intl.formatMessage(messages.labelsColumnsName),
cellFormatters: [expandable],
- transforms: [sortable, cellWidth(15)],
+ transforms: [sortable, wrappable],
key: 'id',
- isShown: true,
- isShownByDefault: true,
- isUntoggleable: true,
},
{
- title: 'Synopsis',
- transforms: [sortable, cellWidth(35)],
+ title: intl.formatMessage(messages.labelsColumnsSynopsis),
+ transforms: [sortable, nowrap],
key: 'synopsis',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsStatus),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'status',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsType),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'advisory_type_name',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsSeverity),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'severity',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsReboot),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'reboot_required',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsPublishDate),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'public_date',
- isShown: true,
- isShownByDefault: true,
},
];
export const systemPackagesColumns = [
{
title: intl.formatMessage(messages.labelsColumnsName),
- transforms: [sortable, cellWidth(20)],
+ transforms: [sortable, wrappable],
key: 'name',
- isShown: true,
- isShownByDefault: true,
- isUntoggleable: true,
},
{
title: intl.formatMessage(messages.labelsColumnsInstalledVersion),
- transforms: [sortable, cellWidth(15)],
+ transforms: [sortable, wrappable],
key: 'evra',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsLatestInstallableVersion),
- transforms: [cellWidth(15)],
+ transforms: [wrappable],
key: 'latest_installable_version',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsLatestApplicableVersion),
- transforms: [cellWidth(15)],
+ transforms: [wrappable],
key: 'latest_applicable_version',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsStatus),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
key: 'update_status',
- isShown: true,
- isShownByDefault: true,
},
{
title: intl.formatMessage(messages.labelsColumnsSummary),
- transforms: [sortable, cellWidth(40)],
+ transforms: [sortable, wrappable],
key: 'summary',
- isShown: true,
- isShownByDefault: true,
},
];
@@ -160,63 +117,52 @@ export const packagesColumns = [
{
key: 'name',
title: intl.formatMessage(messages.labelsColumnsName),
- transforms: [sortable, cellWidth(25)],
- isShown: true,
- isShownByDefault: true,
- isUntoggleable: true,
+ transforms: [sortable, wrappable],
},
{
key: 'systems_installed',
title: intl.formatMessage(messages.labelsColumnsInstalledSystems),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
props: {
width: 10,
},
- isShown: true,
- isShownByDefault: true,
},
{
key: 'systems_applicable',
title: intl.formatMessage(messages.labelsColumnsApplicableSystems),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
props: {
width: 10,
},
- isShown: true,
- isShownByDefault: true,
},
{
key: 'systems_installable',
title: intl.formatMessage(messages.labelsColumnsInstallableSystems),
- transforms: [sortable, cellWidth(10)],
+ transforms: [sortable, wrappable],
props: {
width: 10,
},
- isShown: true,
- isShownByDefault: true,
},
{
key: 'summary',
title: intl.formatMessage(messages.labelsColumnsSummary),
- transforms: [sortable, cellWidth(40)],
+ transforms: [sortable, wrappable],
props: {
width: 30,
},
- isShown: true,
- isShownByDefault: true,
},
];
export const cvesTableColumns = [
{
title: intl.formatMessage(messages.labelsColumnsCveID),
- transforms: [sortable, cellWidth(40)],
+ transforms: [sortable, nowrap],
key: 'synopsis',
},
{
key: 'impact',
title: intl.formatMessage(messages.labelsColumnsSeverity),
- transforms: [sortable, cellWidth(30)],
+ transforms: [sortable],
props: {
width: 30,
},
@@ -224,7 +170,7 @@ export const cvesTableColumns = [
{
key: 'cvss',
title: intl.formatMessage(messages.labelsColumnsCVSS),
- transforms: [sortable, cellWidth(30)],
+ transforms: [sortable],
props: {
width: 30,
},
diff --git a/src/SmartComponents/SystemDetail/__snapshots__/SystemDetail.test.js.snap b/src/SmartComponents/SystemDetail/__snapshots__/SystemDetail.test.js.snap
index 062dc59de..7ed7f219b 100644
--- a/src/SmartComponents/SystemDetail/__snapshots__/SystemDetail.test.js.snap
+++ b/src/SmartComponents/SystemDetail/__snapshots__/SystemDetail.test.js.snap
@@ -636,7 +636,7 @@ exports[`SystemDetail.js Should match the snapshot when Package tab is active by
tabindex="-1"
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
(
{displayName}
),
- isShown: true,
- isShownByDefault: true,
- isUntoggleable: true,
},
{
key: 'groups',
title: 'Workspace',
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
},
{
key: 'tags',
title: 'Tags',
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
},
{
key: 'operating_system',
title: 'OS',
+ transforms: [wrappable],
renderFunc: (value) => createOSColumn(value),
- props: {
- width: 5,
- },
- isShown: true,
- isShownByDefault: true,
},
{
key: 'template_name',
title: 'Template',
+ transforms: [wrappable],
renderFunc: (value, _, row) =>
row.satellite_managed ? (
@@ -69,38 +63,24 @@ export const SYSTEMS_LIST_COLUMNS = [
) : (
'No template'
),
- props: {
- width: 5,
- },
- isShown: true,
- isShownByDefault: true,
},
{
key: 'applicable_advisories',
title: 'Installable advisories',
- props: {
- width: 15,
- },
+ transforms: [wrappable],
renderFunc: (value) => createAdvisoriesIcons(value, 'installable'),
- isShown: true,
- isShownByDefault: true,
},
{
key: 'packages_installed',
title: 'Installed packages',
+ transforms: [wrappable],
renderFunc: (packageCount, systemID) => createPackagesColumn(packageCount, systemID),
- props: {
- width: 10,
- },
- isShown: true,
- isShownByDefault: true,
},
{
inventoryKey: 'updated',
key: 'last_upload',
+ transforms: [nowrap],
sortKey: 'last_upload',
- isShown: true,
- isShownByDefault: true,
},
];
@@ -108,38 +88,31 @@ export const ADVISORY_SYSTEMS_COLUMNS = [
{
key: 'display_name',
title: 'Name',
+ transforms: [wrappable],
renderFunc: (displayName, id) => (
{displayName}
),
- isShown: true,
- isShownByDefault: true,
- isUntoggleable: true,
},
{
key: 'groups',
title: 'Workspace',
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
},
{
key: 'tags',
title: 'Tags',
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
},
{
key: 'os',
title: 'OS',
+ transforms: [wrappable],
renderFunc: (value) => createOSColumn(value),
- props: {
- width: 5,
- },
- isShown: true,
- isShownByDefault: true,
},
{
key: 'template_name',
title: 'Template',
+ transforms: [wrappable],
renderFunc: (value, _, row) =>
row.satellite_managed ? (
@@ -150,29 +123,20 @@ export const ADVISORY_SYSTEMS_COLUMNS = [
) : (
'No template'
),
- props: {
- width: 5,
- },
- isShown: true,
- isShownByDefault: true,
},
{
key: 'status',
title: 'Status',
props: {
- width: 5,
isStatic: true,
},
- transforms: [sortable],
- isShown: true,
- isShownByDefault: true,
+ transforms: [sortable, wrappable],
},
{
inventoryKey: 'updated',
key: 'last_upload',
+ transforms: [nowrap],
sortKey: 'last_upload',
- isShown: true,
- isShownByDefault: true,
},
];
@@ -180,35 +144,31 @@ export const PACKAGE_SYSTEMS_COLUMNS = [
{
key: 'display_name',
title: 'Name',
- isShown: true,
- isShownByDefault: true,
- isUntoggleable: true,
+ transforms: [wrappable],
+ renderFunc: (displayName, id) => (
+ {displayName}
+ ),
},
{
key: 'groups',
title: 'Workspace',
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
},
{
key: 'tags',
title: 'Tags',
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
},
{
key: 'os',
title: 'OS',
+ transforms: [wrappable],
renderFunc: (value) => createOSColumn(value),
- props: {
- width: 5,
- },
- isShown: true,
- isShownByDefault: true,
},
{
key: 'template_name',
title: 'Template',
+ transforms: [wrappable],
renderFunc: (value, _, row) =>
row.satellite_managed ? (
@@ -219,39 +179,24 @@ export const PACKAGE_SYSTEMS_COLUMNS = [
) : (
'No template'
),
- props: {
- width: 5,
- },
- isShown: true,
- isShownByDefault: true,
},
{
key: 'installed_evra',
title: 'Installed version',
- props: {
- width: 15,
- },
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
+ renderFunc: (value) => wrappableEVRA(value),
},
{
key: 'available_evra',
title: 'Latest version',
- props: {
- width: 15,
- },
- isShown: true,
- isShownByDefault: true,
+ transforms: [wrappable],
+ renderFunc: (value) => wrappableEVRA(value),
},
{
key: 'update_status',
title: 'Status',
- props: {
- width: 20,
- },
+ transforms: [wrappable],
renderFunc: (value) => createUpgradableColumn(value),
- isShown: true,
- isShownByDefault: true,
},
];
diff --git a/src/Utilities/DataMappers.js b/src/Utilities/DataMappers.js
index 57f8fc75b..1f5b6cce0 100644
--- a/src/Utilities/DataMappers.js
+++ b/src/Utilities/DataMappers.js
@@ -16,6 +16,7 @@ import {
getSeverityByCveImpact,
handleLongSynopsis,
handlePatchLink,
+ wrappableEVRA,
} from './Helpers';
import { intl } from './IntlProvider';
import AdvisorySeverity from '../PresentationalComponents/AdvisorySeverity/AdvisorySeverity';
@@ -251,9 +252,9 @@ export const createSystemPackagesRows = (rows, selectedRows = {}) => {
disableSelection: !pkg.updatable,
cells: [
{ title: handlePatchLink(entityTypes.packages, pkg.name) },
- { title: pkg.evra },
- { title: latestInstallable?.evra ?? pkg.evra },
- { title: latestApplicable?.evra ?? pkg.evra },
+ { title: wrappableEVRA(pkg.evra) },
+ { title: wrappableEVRA(latestInstallable?.evra ?? pkg.evra) },
+ { title: wrappableEVRA(latestApplicable?.evra ?? pkg.evra) },
{ title: createUpgradableColumn(pkg.update_status) },
{ title: pkg.summary },
],
diff --git a/src/Utilities/DataMappers.test.js b/src/Utilities/DataMappers.test.js
index 10194530f..97004f0aa 100644
--- a/src/Utilities/DataMappers.test.js
+++ b/src/Utilities/DataMappers.test.js
@@ -128,8 +128,20 @@ describe('DataMappers', () => {
disableSelection: true,
cells: [
{ title: expect.anything() }, // FIXME!
- { title: 'test-evra' },
- { title: 'test-evra' },
+ {
+ title: expect.objectContaining({
+ props: expect.objectContaining({
+ children: expect.arrayContaining(['test\u200B', '.evra']),
+ }),
+ }),
+ },
+ {
+ title: expect.objectContaining({
+ props: expect.objectContaining({
+ children: expect.arrayContaining(['test\u200B', '.evra']),
+ }),
+ }),
+ },
{ title: expect.anything() },
{ title: expect.anything() },
{ title: 'Access control list utilities' },
@@ -156,8 +168,20 @@ describe('DataMappers', () => {
disableSelection: false,
cells: [
{ title: expect.anything() }, // FIXME!
- { title: 'test-evra' },
- { title: 'testEvra' },
+ {
+ title: expect.objectContaining({
+ props: expect.objectContaining({
+ children: expect.arrayContaining(['test\u200B', '.evra']),
+ }),
+ }),
+ },
+ {
+ title: expect.objectContaining({
+ props: expect.objectContaining({
+ children: expect.arrayContaining(['testEvra\u200B']),
+ }),
+ }),
+ },
{ title: expect.anything() },
{ title: expect.anything() },
{ title: 'Access control list utilities' },
diff --git a/src/Utilities/Helpers.js b/src/Utilities/Helpers.js
index b3f7419e5..367b5fb38 100644
--- a/src/Utilities/Helpers.js
+++ b/src/Utilities/Helpers.js
@@ -177,6 +177,29 @@ export function createUpgradableColumn(updatableStatus) {
}
}
+/**
+ * Inserts zero-width breakable spaces to correctly break EVRA.
+ */
+export const wrappableEVRA = (evra = '') => {
+ const [ev, ra] = evra.split('-');
+ let [e, v] = ev.split(':');
+ if (v === undefined) {
+ v = e;
+ e = undefined;
+ }
+ const raParts = (ra ?? '').split('.');
+ const a = raParts.pop();
+ const r = raParts.join('.');
+ return (
+
+ {e && `${e}:\u200B`}
+ {v + '\u200B'}
+ {r && `-${r}\u200B`}
+ {a && `.${a}`}
+
+ );
+};
+
export const getSeverityByCveImpact = (impact) => {
switch (impact) {
case 'None':
diff --git a/src/Utilities/Helpers.test.js b/src/Utilities/Helpers.test.js
index 98ff0ccfa..d3572b8a3 100644
--- a/src/Utilities/Helpers.test.js
+++ b/src/Utilities/Helpers.test.js
@@ -27,6 +27,7 @@ import {
persistantParams,
remediationProvider,
transformPairs,
+ wrappableEVRA,
} from './Helpers';
import { render } from '@testing-library/react';
@@ -593,3 +594,62 @@ describe('buildApiFilters', () => {
expect(buildApiFilters(patchFilters, inventoryFilters).os).toEqual('RHEL 8.8,RHEL 8.9');
});
});
+
+describe('wrappableEVRA', () => {
+ it.each`
+ evra | description
+ ${'1:2.3.4-5.el8.x86_64'} | ${'full EVRA with epoch'}
+ ${'2.3.4-5.el8.x86_64'} | ${'EVRA without epoch'}
+ ${'0:1.0-1.el9.noarch'} | ${'EVRA with epoch 0'}
+ ${'10:3.14.159-2.el8.aarch64'} | ${'EVRA with multi-digit epoch'}
+ ${'1.2.3-4.5.6.el8.x86_64'} | ${'EVRA with complex version and release'}
+ ${''} | ${'empty string'}
+ ${'1.0'} | ${'minimal version only'}
+ ${'1.0-1'} | ${'version and release without architecture'}
+ ${'2024.1'} | ${'unconventional versioning'}
+ ${'1.0-1.fc40'} | ${'release without architecture'}
+ `('should render wrappable EVRA for $description', ({ evra }) => {
+ const { container } = render(wrappableEVRA(evra));
+ expect(container).toMatchSnapshot();
+ });
+
+ it('should insert zero-width spaces at correct positions for full EVRA', () => {
+ const { container } = render(wrappableEVRA('1:2.3.4-5.el8.x86_64'));
+ const span = container.querySelector('span');
+ expect(span).not.toBeNull();
+ expect(span.style.overflowWrap).toBe('anywhere');
+ expect(span.textContent).toEqual('1:\u200B2.3.4\u200B-5.el8\u200B.x86_64');
+ });
+
+ it('should handle EVRA without epoch correctly', () => {
+ const { container } = render(wrappableEVRA('2.3.4-5.el8.x86_64'));
+ const span = container.querySelector('span');
+ expect(span.textContent).toEqual('2.3.4\u200B-5.el8\u200B.x86_64');
+ });
+
+ it('should handle empty string with empty element', () => {
+ const { container } = render(wrappableEVRA(''));
+ const span = container.querySelector('span');
+ expect(span).not.toBeNull();
+ expect(span.textContent).toEqual('\u200B');
+ });
+
+ it('should use default empty string when no parameter provided', () => {
+ const { container } = render(wrappableEVRA());
+ const span = container.querySelector('span');
+ expect(span).not.toBeNull();
+ expect(span.textContent).toEqual('\u200B');
+ });
+
+ it('should handle version only (no release or architecture)', () => {
+ const { container } = render(wrappableEVRA('1.0'));
+ const span = container.querySelector('span');
+ expect(span.textContent).toEqual('1.0\u200B');
+ });
+
+ it('should handle unconventional versioning schemes', () => {
+ const { container } = render(wrappableEVRA('2024.1-1.el8.x86_64'));
+ const span = container.querySelector('span');
+ expect(span.textContent).toEqual('2024.1\u200B-1.el8\u200B.x86_64');
+ });
+});
diff --git a/src/Utilities/__snapshots__/DataMappers.test.js.snap b/src/Utilities/__snapshots__/DataMappers.test.js.snap
index 4b23b8cda..ea75b8a8d 100644
--- a/src/Utilities/__snapshots__/DataMappers.test.js.snap
+++ b/src/Utilities/__snapshots__/DataMappers.test.js.snap
@@ -11,13 +11,43 @@ exports[`DataMappers Should create package list 1`] = `
,
},
{
- "title": "test-evra",
+ "title":
+ test
+
+ .evra
+ ,
},
{
- "title": "test-evra",
+ "title":
+ test
+
+ .evra
+ ,
},
{
- "title": "test-evra",
+ "title":
+ test
+
+ .evra
+ ,
},
{
"title": "Up to applicable version",
diff --git a/src/Utilities/__snapshots__/Helpers.test.js.snap b/src/Utilities/__snapshots__/Helpers.test.js.snap
index d2736f8f5..dcb763152 100644
--- a/src/Utilities/__snapshots__/Helpers.test.js.snap
+++ b/src/Utilities/__snapshots__/Helpers.test.js.snap
@@ -820,3 +820,119 @@ exports[`Helpers tests createAdvisoriesIcons: Should match advisory icons snapsh
`;
+
+exports[`wrappableEVRA should render wrappable EVRA for EVRA with complex version and release 1`] = `
+
+
+ 1.2.3
+ -4.5.6.el8
+ .x86_64
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for EVRA with epoch 0 1`] = `
+
+
+ 0:
+ 1.0
+ -1.el9
+ .noarch
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for EVRA with multi-digit epoch 1`] = `
+
+
+ 10:
+ 3.14.159
+ -2.el8
+ .aarch64
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for EVRA without epoch 1`] = `
+
+
+ 2.3.4
+ -5.el8
+ .x86_64
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for empty string 1`] = `
+
+
+
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for full EVRA with epoch 1`] = `
+
+
+ 1:
+ 2.3.4
+ -5.el8
+ .x86_64
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for minimal version only 1`] = `
+
+
+ 1.0
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for release without architecture 1`] = `
+
+
+ 1.0
+ -1
+ .fc40
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for unconventional versioning 1`] = `
+
+
+ 2024.1
+
+
+`;
+
+exports[`wrappableEVRA should render wrappable EVRA for version and release without architecture 1`] = `
+
+
+ 1.0
+ .1
+
+
+`;
|