import { buildChangeApproval, buildRequestApproval } from './approvalCards.js'; export function createJiraProcessor({ config, requests, jiraService, webexService, log }) { const { logger, logWarn, logError } = log; const approvalRoomId = config.rooms.approval; function handleApprovalNotifications(issueKey, jiraTicket, buildApprovalFn) { buildApprovalFn(jiraTicket, log) .then(approvalCard => jiraService.buildApprovers(issueKey).then(approvers => ({ approvalCard, approvers }))) .then(({ approvalCard, approvers }) => { const cardPayload = { text: approvalCard.message, attachments: [{ contentType: 'application/vnd.microsoft.card.adaptive', content: approvalCard.card }], }; for (const approver of approvers) { logger('processJiraTicket', `Notifying ${issueKey} --> ${approver}`); webexService.sendTicketCard(issueKey, { toPersonEmail: approver, ...cardPayload }); } if (approvers.length > 0) { webexService.sendTicketCard(issueKey, { roomId: approvalRoomId, ...cardPayload }); } else { logWarn('processJiraTicket', `${issueKey} no pending approvers found after Jira lookup — no Webex messages sent`); } }) .catch(error => logError('processJiraTicket', `Failed approval notifications for ${issueKey}`, error)); } function processJiraTicket(jiraTicket) { if (!jiraTicket?.issue?.key) { logError('processJiraTicket', 'Missing issue key in webhook payload'); return; } const issueKey = jiraTicket.issue.key; const projectKey = jiraTicket.issue.fields?.project?.key; const requestTypeField = jiraTicket.issue.fields?.customfield_10010; const requestType = requestTypeField?.requestType?.id != null ? String(requestTypeField.requestType.id) : undefined; const transitionId = jiraTicket.transition?.transitionId != null ? String(jiraTicket.transition.transitionId) : undefined; const webhookEvent = jiraTicket.webhookEvent || 'unknown'; logger( 'processJiraTicket', `${issueKey} event=${webhookEvent} project=${projectKey || '?'} requestType=${requestType || '?'} transition=${transitionId || 'none'}` ); if (projectKey !== 'CHANGE' && projectKey !== 'REQUEST') { logWarn('processJiraTicket', `${issueKey} skipped: project ${projectKey} is not CHANGE or REQUEST`); return; } if (!requestType) { logWarn('processJiraTicket', `${issueKey} skipped: missing request type in customfield_10010`); return; } if (!requests[requestType]) { logWarn( 'processJiraTicket', `${issueKey} skipped: request type ${requestType} (${requestTypeField.requestType?.name || 'unknown'}) is not configured in requests.json` ); return; } logger('processJiraTicket', `${issueKey} matched request config: ${JSON.stringify(requests[requestType])}`); if (transitionId && requests[requestType].transitions[transitionId]) { logger('processJiraTicket', `${issueKey} matched transition ${transitionId} (${requests[requestType].transitions[transitionId].name})`); if (projectKey == 'CHANGE') { handleApprovalNotifications(issueKey, jiraTicket, buildChangeApproval); } else { handleApprovalNotifications(issueKey, jiraTicket, buildRequestApproval); } return; } if (!jiraTicket.changelog) { if (transitionId) { logWarn( 'processJiraTicket', `${issueKey} skipped: transition ${transitionId} is not configured for request type ${requestType}` ); } else { logWarn('processJiraTicket', `${issueKey} skipped: no transition and no changelog in webhook`); } return; } const approvalStatuses = ['Pending Approval', 'Emergency Approval', 'Waiting for approval']; const statusName = jiraTicket.issue.fields?.status?.name; let approverChange = false; for (const change of jiraTicket.changelog.items) { if (change.field != 'Approvers' || !approvalStatuses.includes(statusName)) { continue; } approverChange = true; if (projectKey == 'CHANGE') { handleApprovalNotifications(issueKey, jiraTicket, buildChangeApproval); } else { handleApprovalNotifications(issueKey, jiraTicket, buildRequestApproval); } } if (!approverChange) { logWarn( 'processJiraTicket', `${issueKey} skipped: changelog did not include an Approvers update in an approval status (status=${statusName || '?'})` ); } } return { processJiraTicket }; }