2 Commits

Author SHA1 Message Date
Jordan Ramos
5126ccc6ae Fix History tab crash: coerce Ivanti note fields to strings before rendering
PostgreSQL + Ivanti API enrichment can return non-string values
(objects/arrays) for currentStateUserNotes and similar fields.
React crashes silently (blank page, no console error) when trying
to render non-string values as children. Same root cause pattern
as Bug 3 in ivanti-panel-bugs-2026-05-12.

Added safeText() wrapper that coerces any non-string truthy value
to a JSON string before rendering in the History tab notes section.

Also fixed flaky property test: fc.date() could generate invalid
dates causing RangeError on .toISOString(). Added .filter() guard
and explicit UTC date bounds.
2026-05-13 14:24:16 -06:00
Jordan Ramos
870c0e247a Fix History tab crash: coerce Ivanti note fields to strings before rendering
PostgreSQL + Ivanti API enrichment can return non-string values
(objects/arrays) for currentStateUserNotes and similar fields.
React crashes silently (blank page, no console error) when trying
to render non-string values as children. Same root cause pattern
as Bug 3 in ivanti-panel-bugs-2026-05-12.

Added safeText() wrapper that coerces any non-string truthy value
to a JSON string before rendering in the History tab notes section.
2026-05-13 12:01:52 -06:00
2 changed files with 12 additions and 5 deletions

View File

@@ -31,7 +31,9 @@ const lifecycleStatusArb = fc.constantFrom('submitted', 'approved', 'rejected',
const dismissedAtArb = fc.oneof( const dismissedAtArb = fc.oneof(
fc.constant(null), fc.constant(null),
fc.date({ min: new Date('2020-01-01'), max: new Date('2030-12-31') }).map(d => d.toISOString()) fc.date({ min: new Date('2020-01-01T00:00:00.000Z'), max: new Date('2030-12-31T00:00:00.000Z') })
.filter(d => !isNaN(d.getTime()))
.map(d => d.toISOString())
); );
const submissionArb = fc.record({ const submissionArb = fc.record({

View File

@@ -4087,11 +4087,16 @@ function FpEditModal({ open, onClose, submission, queueItems, onSuccess }) {
<div> <div>
{/* Ivanti reviewer notes (rework/approval/previous state feedback) */} {/* Ivanti reviewer notes (rework/approval/previous state feedback) */}
{(() => { {(() => {
const safeText = (val) => {
if (!val) return null;
if (typeof val === 'string') return val;
try { return JSON.stringify(val); } catch { return String(val); }
};
const notes = [ const notes = [
submission.ivanti_rework_note && { label: 'Rework Note', text: submission.ivanti_rework_note }, safeText(submission.ivanti_rework_note) && { label: 'Rework Note', text: safeText(submission.ivanti_rework_note) },
submission.ivanti_approval_note && { label: 'Approval Note', text: submission.ivanti_approval_note }, safeText(submission.ivanti_approval_note) && { label: 'Approval Note', text: safeText(submission.ivanti_approval_note) },
submission.ivanti_current_state_notes && { label: 'Current State Notes', text: submission.ivanti_current_state_notes }, safeText(submission.ivanti_current_state_notes) && { label: 'Current State Notes', text: safeText(submission.ivanti_current_state_notes) },
submission.ivanti_previous_state_notes && { label: 'Previous State Notes', text: submission.ivanti_previous_state_notes }, safeText(submission.ivanti_previous_state_notes) && { label: 'Previous State Notes', text: safeText(submission.ivanti_previous_state_notes) },
].filter(Boolean); ].filter(Boolean);
return notes.length > 0 ? notes.map((note, idx) => ( return notes.length > 0 ? notes.map((note, idx) => (
<div key={idx} style={{ <div key={idx} style={{