From 3668b69d88b601dba93ac6df620572759c252d3a Mon Sep 17 00:00:00 2001 From: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:23:55 -0700 Subject: [PATCH 1/2] ACM-36695 Preserve newlines in CSV export to match console rendering Backport of #6081 to release-2.15. Fixes returnCSVSafeString to preserve newlines instead of replacing them with spaces, so the exported CSV matches the UI rendering of policy descriptions. Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 --- frontend/src/resources/utils/utils.ts | 11 ++++++++--- frontend/src/ui-components/AcmTable/AcmTable.test.tsx | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/resources/utils/utils.ts b/frontend/src/resources/utils/utils.ts index 338f55939cd..bbdee7652a4 100644 --- a/frontend/src/resources/utils/utils.ts +++ b/frontend/src/resources/utils/utils.ts @@ -51,9 +51,14 @@ export function exportObjectString(object: Record) { return keyValueMap.toString() } -export function returnCSVSafeString(exportValue: string | ReactNode) { - // extract newlines - return `"${typeof exportValue === 'string' ? exportValue.split('\n').join(' ').replace(/"/g, '""') : exportValue}"` +export function returnCSVSafeString(exportValue: string | number | ReactNode) { + if (typeof exportValue === 'number') { + return `"${exportValue}"` + } + if (typeof exportValue !== 'string') { + return '"-"' + } + return `"${exportValue.replaceAll('"', '""')}"` } export const getISOStringTimestamp = (timestamp: string) => { diff --git a/frontend/src/ui-components/AcmTable/AcmTable.test.tsx b/frontend/src/ui-components/AcmTable/AcmTable.test.tsx index 09af9165979..01fa1903801 100644 --- a/frontend/src/ui-components/AcmTable/AcmTable.test.tsx +++ b/frontend/src/ui-components/AcmTable/AcmTable.test.tsx @@ -1146,10 +1146,10 @@ describe('AcmTable', () => { }) describe('returnCSVSafeString', () => { - test('returns a csv safe string replacing new line with space', () => { + test('returns a csv safe string preserving newlines', () => { const content = 'testing for multiline\ndescription' const exportContent = returnCSVSafeString(content) - expect(exportContent).toEqual('"testing for multiline description"') + expect(exportContent).toEqual('"testing for multiline\ndescription"') }) test('returns a csv safe string escaping double quotes', () => { From fea3c61a27c64b36646ef8885be17a30585c9b4c Mon Sep 17 00:00:00 2001 From: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:03:43 -0700 Subject: [PATCH 2/2] test(ACM-32500): Add CSV export tests for release-2.15 Add tests validating returnCSVSafeString behavior: newline preservation, quote escaping, number handling, and ReactNode fallback. Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-Authored-By: Claude Haiku 4.5 --- .../routes/Governance/common/util.test.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/frontend/src/routes/Governance/common/util.test.tsx b/frontend/src/routes/Governance/common/util.test.tsx index 6cb555cad62..e1b964bd6a8 100644 --- a/frontend/src/routes/Governance/common/util.test.tsx +++ b/frontend/src/routes/Governance/common/util.test.tsx @@ -8,6 +8,7 @@ import { parseStringMap, policyHasDeletePruneBehavior, } from './util' +import { returnCSVSafeString } from '../../../resources/utils' import { PolicyTableItem } from '../policies/Policies' import { Policy, PolicyTemplate, REMEDIATION_ACTION } from '../../../resources' import { cloneDeep } from 'lodash' @@ -1028,3 +1029,32 @@ describe('Test policyHasDeletePruneBehavior', () => { expect(policyHasDeletePruneBehavior(policy)).toBe(false) }) }) + +describe('ACM-32500: returnCSVSafeString preserves newlines in CSV export', () => { + const descriptionFromApi = + 'Policy is placed on hub or managed clusters with label acm-virt-config=acm-dr-virt-config-file-name.\nCreates a velero Schedule for all virtualmachines.kubevirt.io resources with a cluster.open-cluster-management.io/backup-vm label.' + + test('returnCSVSafeString preserves newlines', () => { + const csvRaw = returnCSVSafeString(descriptionFromApi) + const unquoted = csvRaw.slice(1, -1).replace(/""/g, '"') + + expect(unquoted).toContain('\n') + expect(unquoted).toEqual(descriptionFromApi) + }) + + test('returnCSVSafeString escapes double quotes', () => { + const stringWithQuotes = 'Description with "quoted" text' + const result = returnCSVSafeString(stringWithQuotes) + + expect(result).toBe('"Description with ""quoted"" text"') + }) + + test('returnCSVSafeString handles number input', () => { + expect(returnCSVSafeString(42)).toBe('"42"') + }) + + test('returnCSVSafeString handles non-string/non-number ReactNode', () => { + expect(returnCSVSafeString(null)).toBe('"-"') + expect(returnCSVSafeString(undefined)).toBe('"-"') + }) +})