All files / src/features/ChangeLog/utils normalizeFieldChanges.ts

100% Statements 10/10
100% Branches 6/6
100% Functions 3/3
100% Lines 8/8

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36                                118x 88x             118x     37x   36x 38x 38x 38x      
import { ChangeItem, ChangelogData } from "@services/api/changelog/changelog";
 
/**
 * `fieldChanges` is a free-form JSON column on the BE, and not every writer
 * stores the same shape. Almost all of them store a list of field changes
 * (`[{ operation, fieldName, oldValue, newValue }]`), but a few audit-only
 * rows store a flat `{ key: value }` map instead — e.g. a blocked owner
 * reassignment attempt, which records
 * `{ owner_reassignment_blocked: "person_declined", attempted_email: "..." }`.
 *
 * The changelog UI iterates `fieldChanges`, so a single map-shaped row used to
 * throw "fieldChanges.map is not a function" and take the whole side panel
 * down with the "Technical Difficulties" fallback (GB-21601).
 *
 * @returns The value itself when it is already a list, an empty list otherwise
 */
export const toFieldChangeList = (fieldChanges: unknown): ChangeItem[] =>
  Array.isArray(fieldChanges) ? (fieldChanges as ChangeItem[]) : [];
 
/**
 * Coerces every row's `fieldChanges` to a list and drops the rows left with
 * nothing to render, so an audit row the UI has no description for is skipped
 * instead of rendering as an empty timeline entry.
 */
export const normalizeChangelogData = (
  data: ChangelogData[],
): ChangelogData[] => {
  if (!Array.isArray(data)) return [];
 
  return data.reduce<ChangelogData[]>((normalized, item) => {
    const fieldChanges = toFieldChangeList(item?.fieldChanges);
    if (fieldChanges.length) normalized.push({ ...item, fieldChanges });
    return normalized;
  }, []);
};