diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index dde01266e3b8..63f16b127aab 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -565,8 +565,8 @@ export type AuditLogItem = { export type AuditLogDetail = AuditLogItem & { change_details: { field: string - old: FlagsmithValue - new: FlagsmithValue + old: FlagsmithValue | object + new: FlagsmithValue | object }[] } export type PaymentMethod = 'CHARGEBEE' | 'XERO' | 'AWS_MARKETPLACE' diff --git a/frontend/common/utils/__tests__/diffValue.test.ts b/frontend/common/utils/__tests__/diffValue.test.ts new file mode 100644 index 000000000000..1d8b317838c2 --- /dev/null +++ b/frontend/common/utils/__tests__/diffValue.test.ts @@ -0,0 +1,27 @@ +import { stringifyDiffValue } from 'common/utils/diffValue' + +describe('stringifyDiffValue', () => { + it('returns an empty string for null and undefined', () => { + expect(stringifyDiffValue(null)).toBe('') + expect(stringifyDiffValue(undefined)).toBe('') + }) + + it('quotes the empty string', () => { + expect(stringifyDiffValue('')).toBe('""') + }) + + it('stringifies primitives', () => { + expect(stringifyDiffValue('abc')).toBe('abc') + expect(stringifyDiffValue(3)).toBe('3') + expect(stringifyDiffValue(false)).toBe('false') + }) + + it('pretty-prints arrays of objects instead of [object Object]', () => { + const rules = [ + { conditions: [{ operator: 'EQUAL', property: 'a', value: '1' }] }, + ] + const result = stringifyDiffValue(rules) + expect(result).not.toContain('[object Object]') + expect(JSON.parse(result)).toEqual(rules) + }) +}) diff --git a/frontend/common/utils/diffValue.ts b/frontend/common/utils/diffValue.ts new file mode 100644 index 000000000000..ee3af0842cd4 --- /dev/null +++ b/frontend/common/utils/diffValue.ts @@ -0,0 +1,17 @@ +/** + * Turns an audit-log / diff value into the string shown in the diff viewer. + * Objects and arrays (e.g. a segment's `rules_data`) are pretty-printed as JSON, + * otherwise template interpolation would render them as `[object Object]`. + */ +export const stringifyDiffValue = (value: unknown): string => { + if (value === undefined || value === null) { + return '' + } + if (value === '') { + return '""' + } + if (typeof value === 'object') { + return JSON.stringify(value, null, 2) + } + return `${value}` +} diff --git a/frontend/web/components/diff/DiffString.tsx b/frontend/web/components/diff/DiffString.tsx index 0042b10d60d6..d083b2ea398f 100644 --- a/frontend/web/components/diff/DiffString.tsx +++ b/frontend/web/components/diff/DiffString.tsx @@ -3,23 +3,15 @@ import Diff, { DiffMethod } from 'react-diff-viewer-continued' import Prism from 'prismjs' import 'prismjs/components/prism-json' import { FlagsmithValue } from 'common/types/responses' +import { stringifyDiffValue } from 'common/utils/diffValue' import DiffRow from './DiffRow' type DiffType = { - oldValue: FlagsmithValue - newValue: FlagsmithValue + oldValue: FlagsmithValue | object + newValue: FlagsmithValue | object compareMethod?: DiffMethod } -const sanitiseDiffString = (value: FlagsmithValue) => { - if (value === undefined || value === null) { - return '' - } - if (value === '') { - return '""' - } - return `${value}` -} const DiffString: FC = ({ compareMethod = DiffMethod.WORDS_WITH_SPACE, newValue, @@ -34,7 +26,7 @@ const DiffString: FC = ({ ) } - if (oldValue === newValue) { + if (stringifyDiffValue(oldValue) === stringifyDiffValue(newValue)) { if (oldValue === null || oldValue === '') { return
No Value.
} @@ -44,7 +36,7 @@ const DiffString: FC = ({ className='d-inline mb-0' dangerouslySetInnerHTML={{ __html: Prism.highlight( - sanitiseDiffString(newValue), + stringifyDiffValue(newValue), Prism.languages.json, 'json', ), @@ -55,11 +47,14 @@ const DiffString: FC = ({ } return ( (