From c10ea05f34008db8414e546ce21590815579747c Mon Sep 17 00:00:00 2001 From: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:19:52 -0700 Subject: [PATCH 1/2] ACM-36697 Preserve newlines in CSV export to match console rendering Backport of #6081 to release-2.13. 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 220999ea356..1b43a9b894b 100644 --- a/frontend/src/resources/utils/utils.ts +++ b/frontend/src/resources/utils/utils.ts @@ -54,9 +54,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 3b0cb224dfc..8865a8b57db 100644 --- a/frontend/src/ui-components/AcmTable/AcmTable.test.tsx +++ b/frontend/src/ui-components/AcmTable/AcmTable.test.tsx @@ -1108,10 +1108,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 53bbc77aa4b57712e6b99b1b53ab46d1ef6c6581 Mon Sep 17 00:00:00 2001 From: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:35:36 -0700 Subject: [PATCH 2/2] test(ACM-32500): Add CSV export tests for release-2.13 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 709cb8ab87a..ff290bb47f3 100644 --- a/frontend/src/routes/Governance/common/util.test.tsx +++ b/frontend/src/routes/Governance/common/util.test.tsx @@ -2,6 +2,7 @@ 'use strict' import { hasInformOnlyPolicies, getPolicyRemediation, resolveExternalStatus, parseStringMap } from './util' +import { returnCSVSafeString } from '../../../resources/utils' import { PolicyTableItem } from '../policies/Policies' import { Policy, PolicyTemplate, REMEDIATION_ACTION } from '../../../resources' import { cloneDeep } from 'lodash' @@ -901,3 +902,32 @@ describe('Test parseStringValue', () => { ) }) }) + +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('"-"') + }) +})