Append remediation notes to Jira ticket description in QueuePanel
- Single-item: openCreateJiraFromQueue fetches notes for Remediate items and pre-fills the description with a Remediation Notes section - Multi-item: ConsolidationModal fetches notes for all Remediate items and appends them via appendRemediationNotes utility Previously notes were only integrated in IvantiTodoQueuePage.js but the actual Jira creation flow users interact with is in ReportingPage.js QueuePanel and ConsolidationModal.
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
generateConsolidatedDescription,
|
generateConsolidatedDescription,
|
||||||
extractFirstCve,
|
extractFirstCve,
|
||||||
extractCommonVendor,
|
extractCommonVendor,
|
||||||
|
appendRemediationNotes,
|
||||||
} from '../utils/jiraConsolidation';
|
} from '../utils/jiraConsolidation';
|
||||||
|
|
||||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
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(() => {
|
useEffect(() => {
|
||||||
if (items.length >= 2) {
|
if (items.length >= 2) {
|
||||||
setSummary(generateConsolidatedSummary(items));
|
setSummary(generateConsolidatedSummary(items));
|
||||||
setDescription(generateConsolidatedDescription(items));
|
|
||||||
setCveId(extractFirstCve(items));
|
setCveId(extractFirstCve(items));
|
||||||
setVendor(extractCommonVendor(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]);
|
}, [items]);
|
||||||
|
|
||||||
|
|||||||
@@ -1759,7 +1759,7 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Open Create Jira modal pre-populated from a queue item
|
// 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
|
// Parse cves_json — it may be a JSON string or already an array
|
||||||
let cves = [];
|
let cves = [];
|
||||||
if (item.cves_json) {
|
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 firstCve = (Array.isArray(cves) && cves.length > 0) ? cves[0] : '';
|
||||||
const summary = (item.finding_title || '').slice(0, 255);
|
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({
|
setCreateJiraForm({
|
||||||
summary,
|
summary,
|
||||||
cve_id: firstCve,
|
cve_id: firstCve,
|
||||||
vendor: item.vendor || '',
|
vendor: item.vendor || '',
|
||||||
source_context: 'ivanti_queue',
|
source_context: 'ivanti_queue',
|
||||||
description: '',
|
description,
|
||||||
project_key: '',
|
project_key: '',
|
||||||
issue_type: '',
|
issue_type: '',
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user