diff --git a/frontend/src/components/ConsolidationModal.js b/frontend/src/components/ConsolidationModal.js index fd2cf28..6012225 100644 --- a/frontend/src/components/ConsolidationModal.js +++ b/frontend/src/components/ConsolidationModal.js @@ -5,6 +5,7 @@ import { generateConsolidatedDescription, extractFirstCve, extractCommonVendor, + appendRemediationNotes, } from '../utils/jiraConsolidation'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; @@ -285,9 +286,31 @@ export default function ConsolidationModal({ items, onClose, onSuccess }) { useEffect(() => { if (items.length >= 2) { setSummary(generateConsolidatedSummary(items)); - setDescription(generateConsolidatedDescription(items)); setCveId(extractFirstCve(items)); setVendor(extractCommonVendor(items)); + + // Build description, appending remediation notes for Remediate items + const baseDescription = generateConsolidatedDescription(items); + const remediateItems = items.filter(i => i.workflow_type === 'Remediate'); + if (remediateItems.length > 0) { + Promise.all( + remediateItems.map(item => + fetch(`${API_BASE}/ivanti/todo-queue/${item.id}/notes`, { credentials: 'include' }) + .then(r => r.ok ? r.json() : []) + .catch(() => []) + ) + ).then(results => { + const notesMap = {}; + remediateItems.forEach((item, idx) => { + if (results[idx] && results[idx].length > 0) { + notesMap[item.id] = results[idx]; + } + }); + setDescription(appendRemediationNotes(baseDescription, notesMap)); + }); + } else { + setDescription(baseDescription); + } } }, [items]); diff --git a/frontend/src/components/pages/ReportingPage.js b/frontend/src/components/pages/ReportingPage.js index 1a9c0b3..c2cbd7e 100644 --- a/frontend/src/components/pages/ReportingPage.js +++ b/frontend/src/components/pages/ReportingPage.js @@ -1759,7 +1759,7 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on }; // Open Create Jira modal pre-populated from a queue item - const openCreateJiraFromQueue = (item) => { + const openCreateJiraFromQueue = async (item) => { // Parse cves_json — it may be a JSON string or already an array let cves = []; if (item.cves_json) { @@ -1769,12 +1769,32 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on } const firstCve = (Array.isArray(cves) && cves.length > 0) ? cves[0] : ''; const summary = (item.finding_title || '').slice(0, 255); + + // Build description — include remediation notes for Remediate items + let description = ''; + if (item.workflow_type === 'Remediate') { + try { + const notesRes = await fetch(`${API_BASE}/ivanti/todo-queue/${item.id}/notes`, { credentials: 'include' }); + if (notesRes.ok) { + const notes = await notesRes.json(); + if (notes.length > 0) { + const sorted = [...notes].sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); + description = '== Remediation Notes ==\n'; + for (const note of sorted) { + const date = note.created_at ? note.created_at.slice(0, 10) : 'Unknown'; + description += `[${date}] ${note.username}: ${note.note_text}\n`; + } + } + } + } catch (_) { /* best-effort */ } + } + setCreateJiraForm({ summary, cve_id: firstCve, vendor: item.vendor || '', source_context: 'ivanti_queue', - description: '', + description, project_key: '', issue_type: '', });