From adac82278f15e41fbbbafa667b6b163a9ad65ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dugovi=C4=8D?= Date: Fri, 21 Aug 2026 10:51:37 +0200 Subject: [PATCH 1/3] fix: fix table column widths HMS-11119 --- .../TableView/TableViewAssets.js | 114 +++++------------- .../__snapshots__/SystemDetail.test.js.snap | 28 ++--- .../Systems/SystemsListAssets.js | 109 ++++------------- 3 files changed, 68 insertions(+), 183 deletions(-) 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 +62,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 +87,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 +122,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 +143,28 @@ export const PACKAGE_SYSTEMS_COLUMNS = [ { key: 'display_name', title: 'Name', - isShown: true, - isShownByDefault: true, - isUntoggleable: true, + transforms: [wrappable], }, { 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 +175,22 @@ 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], }, { key: 'available_evra', title: 'Latest version', - props: { - width: 15, - }, - isShown: true, - isShownByDefault: true, + transforms: [wrappable], }, { key: 'update_status', title: 'Status', - props: { - width: 20, - }, + transforms: [wrappable], renderFunc: (value) => createUpgradableColumn(value), - isShown: true, - isShownByDefault: true, }, ]; From a94791570ece09fffe8f38b8e44f6a5d49cf56be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dugovi=C4=8D?= Date: Fri, 21 Aug 2026 14:45:05 +0200 Subject: [PATCH 2/3] feat: add wrappableEVRA utility HMS-11119 Claude authored just the tests. Co-Authored-By: Claude --- src/Utilities/Helpers.js | 23 ++++ src/Utilities/Helpers.test.js | 60 +++++++++ .../__snapshots__/Helpers.test.js.snap | 116 ++++++++++++++++++ 3 files changed, 199 insertions(+) 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__/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 + +
+`; From d0815c262f061124be45b18d4ae281c9d2b27649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dugovi=C4=8D?= Date: Fri, 21 Aug 2026 14:45:05 +0200 Subject: [PATCH 3/3] feat: apply wrappableEVRA HMS-11119 --- .../Systems/SystemsListAssets.js | 6 ++++ src/Utilities/DataMappers.js | 7 ++-- src/Utilities/DataMappers.test.js | 32 ++++++++++++++--- .../__snapshots__/DataMappers.test.js.snap | 36 +++++++++++++++++-- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/SmartComponents/Systems/SystemsListAssets.js b/src/SmartComponents/Systems/SystemsListAssets.js index aeeeeb23c..a09a9a891 100644 --- a/src/SmartComponents/Systems/SystemsListAssets.js +++ b/src/SmartComponents/Systems/SystemsListAssets.js @@ -7,6 +7,7 @@ import { remediationProvider, createOSColumn, createPackagesColumn, + wrappableEVRA, } from '../../Utilities/Helpers'; import { sortable, wrappable, nowrap } from '@patternfly/react-table'; import { InsightsLink } from '@redhat-cloud-services/frontend-components/InsightsLink'; @@ -144,6 +145,9 @@ export const PACKAGE_SYSTEMS_COLUMNS = [ key: 'display_name', title: 'Name', transforms: [wrappable], + renderFunc: (displayName, id) => ( + {displayName} + ), }, { key: 'groups', @@ -180,11 +184,13 @@ export const PACKAGE_SYSTEMS_COLUMNS = [ key: 'installed_evra', title: 'Installed version', transforms: [wrappable], + renderFunc: (value) => wrappableEVRA(value), }, { key: 'available_evra', title: 'Latest version', transforms: [wrappable], + renderFunc: (value) => wrappableEVRA(value), }, { key: 'update_status', 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/__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",