feat: show finding IDs in history, display Ivanti reviewer notes (rework/approval feedback) in history tab
This commit is contained in:
@@ -560,6 +560,43 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
|
|||||||
for (const sub of submissions) {
|
for (const sub of submissions) {
|
||||||
sub.history = historyMap[sub.id] || [];
|
sub.history = historyMap[sub.id] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enrich submissions with Ivanti reviewer notes (rework/approval feedback)
|
||||||
|
const apiKey = process.env.IVANTI_API_KEY;
|
||||||
|
const clientId = process.env.IVANTI_CLIENT_ID || '1550';
|
||||||
|
const skipTls = process.env.IVANTI_SKIP_TLS === 'true';
|
||||||
|
|
||||||
|
if (apiKey) {
|
||||||
|
try {
|
||||||
|
// Batch search by all workflow names
|
||||||
|
for (const sub of submissions) {
|
||||||
|
if (!sub.workflow_name) continue;
|
||||||
|
const searchUrl = `/client/${encodeURIComponent(clientId)}/workflowBatch/search`;
|
||||||
|
const searchBody = {
|
||||||
|
filters: [{ field: 'name', exclusive: false, operator: 'EXACT', value: sub.workflow_name }],
|
||||||
|
projection: 'internal',
|
||||||
|
sort: [{ field: 'created', direction: 'DESC' }],
|
||||||
|
page: 0, size: 1
|
||||||
|
};
|
||||||
|
const result = await ivantiPost(searchUrl, searchBody, apiKey, skipTls);
|
||||||
|
if (result.status === 200) {
|
||||||
|
const data = JSON.parse(result.body);
|
||||||
|
const batches = data._embedded?.workflowBatches || data._embedded?.workflowBatch || [];
|
||||||
|
const batch = batches[0];
|
||||||
|
if (batch) {
|
||||||
|
sub.ivanti_rework_note = batch.reworkNote || null;
|
||||||
|
sub.ivanti_approval_note = batch.approvalNote || null;
|
||||||
|
sub.ivanti_current_state_notes = batch.currentStateUserNotes || null;
|
||||||
|
sub.ivanti_previous_state_notes = batch.previousStateUserNotes || null;
|
||||||
|
sub.ivanti_current_state = batch.currentState || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error enriching submissions with Ivanti notes:', e.message);
|
||||||
|
// Don't fail the response — notes are supplementary
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// No submissions, nothing to do
|
// No submissions, nothing to do
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2707,6 +2707,21 @@ function FpEditModal({ open, onClose, submission, queueItems, onSuccess }) {
|
|||||||
{/* History tab */}
|
{/* History tab */}
|
||||||
{activeTab === 'history' && (
|
{activeTab === 'history' && (
|
||||||
<div>
|
<div>
|
||||||
|
{/* Ivanti reviewer notes (rework/approval feedback) */}
|
||||||
|
{(submission.ivanti_rework_note || submission.ivanti_current_state_notes) && (
|
||||||
|
<div style={{
|
||||||
|
padding: '0.625rem 0.75rem', marginBottom: '0.75rem',
|
||||||
|
borderRadius: '0.375rem',
|
||||||
|
background: 'rgba(245,158,11,0.06)', border: '1px solid rgba(245,158,11,0.2)',
|
||||||
|
}}>
|
||||||
|
<div style={{ fontFamily: 'monospace', fontSize: '0.68rem', fontWeight: '600', color: '#F59E0B', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '0.35rem' }}>
|
||||||
|
Ivanti Reviewer Notes
|
||||||
|
</div>
|
||||||
|
<div style={{ fontFamily: 'monospace', fontSize: '0.72rem', color: '#CBD5E1', whiteSpace: 'pre-wrap', lineHeight: 1.5 }}>
|
||||||
|
{submission.ivanti_rework_note || submission.ivanti_current_state_notes}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{history.length === 0 ? (
|
{history.length === 0 ? (
|
||||||
<div style={{ fontFamily: 'monospace', fontSize: '0.72rem', color: '#475569', textAlign: 'center', padding: '2rem 0' }}>
|
<div style={{ fontFamily: 'monospace', fontSize: '0.72rem', color: '#475569', textAlign: 'center', padding: '2rem 0' }}>
|
||||||
No history entries.
|
No history entries.
|
||||||
@@ -2742,13 +2757,26 @@ function FpEditModal({ open, onClose, submission, queueItems, onSuccess }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{entry.change_type === 'findings_added' && details.addedFindingIds && (
|
{entry.change_type === 'findings_added' && details.addedFindingIds && (
|
||||||
<div style={{ fontFamily: 'monospace', fontSize: '0.65rem', color: '#94A3B8', marginTop: '0.15rem' }}>
|
<div style={{ marginTop: '0.15rem' }}>
|
||||||
+{details.addedFindingIds.length} finding(s)
|
<div style={{ fontFamily: 'monospace', fontSize: '0.65rem', color: '#94A3B8' }}>
|
||||||
|
+{details.addedFindingIds.length} finding(s):
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.2rem', marginTop: '0.2rem' }}>
|
||||||
|
{details.addedFindingIds.map(fid => (
|
||||||
|
<span key={fid} style={{
|
||||||
|
padding: '0.05rem 0.3rem', borderRadius: '0.15rem',
|
||||||
|
background: 'rgba(14,165,233,0.08)', border: '1px solid rgba(14,165,233,0.2)',
|
||||||
|
fontFamily: 'monospace', fontSize: '0.6rem', color: '#0EA5E9',
|
||||||
|
}}>
|
||||||
|
{fid}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{entry.change_type === 'attachments_added' && details.files && (
|
{entry.change_type === 'attachments_added' && details.files && (
|
||||||
<div style={{ fontFamily: 'monospace', fontSize: '0.65rem', color: '#94A3B8', marginTop: '0.15rem' }}>
|
<div style={{ fontFamily: 'monospace', fontSize: '0.65rem', color: '#94A3B8', marginTop: '0.15rem' }}>
|
||||||
{details.files.length} file(s) uploaded
|
{details.files.filter(f => f.success).length} of {details.files.length} file(s) uploaded
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user