diff --git a/routes/webhooks.js b/routes/webhooks.js index 5fe28f8..8458377 100644 --- a/routes/webhooks.js +++ b/routes/webhooks.js @@ -25,6 +25,10 @@ export function createWebhookRoutes({ config, jiraProcessor, webexService, log } } safeProcess(routeName, () => { + logger( + routeName, + `Accepted webhook event=${req.body.webhookEvent || 'unknown'} issue=${validation.issueKey} project=${req.body.issue?.fields?.project?.key || '?'}` + ); logFile(WEBHOOK_LOG_PROVIDERS[routeName] || routeName, req.body); jiraProcessor.processJiraTicket(req.body); }, correlationMeta); diff --git a/services/jiraProcessor.js b/services/jiraProcessor.js index 8ea44e1..7160ec1 100644 --- a/services/jiraProcessor.js +++ b/services/jiraProcessor.js @@ -18,6 +18,8 @@ export function createJiraProcessor({ config, requests, jiraService, webexServic } 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)); @@ -30,54 +32,85 @@ export function createJiraProcessor({ config, requests, jiraService, webexServic } const issueKey = jiraTicket.issue.key; - let requestType; - let transitionId; + 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', `Processing ${issueKey}.`); + logger( + 'processJiraTicket', + `${issueKey} event=${webhookEvent} project=${projectKey || '?'} requestType=${requestType || '?'} transition=${transitionId || 'none'}` + ); - if (jiraTicket.transition) { - logger('processJiraTicket', `${issueKey} Transition: ${jiraTicket.transition.transitionId}.`); - const requestTypeField = jiraTicket.issue.fields?.customfield_10010; - if (requestTypeField?.requestType?.id) { - requestType = requestTypeField.requestType.id; - transitionId = jiraTicket.transition.transitionId; - } else { - logWarn('processJiraTicket', `${issueKey} - missing requestType: ${JSON.stringify(requestTypeField)}`); - } - } - - if (!issueKey || !requestType || !requests[requestType]) { + if (projectKey !== 'CHANGE' && projectKey !== 'REQUEST') { + logWarn('processJiraTicket', `${issueKey} skipped: project ${projectKey} is not CHANGE or REQUEST`); return; } - logger('processJiraTicket', `${issueKey} RequestType: ${requestType} Requests?: ${JSON.stringify(requests[requestType])}`); + if (!requestType) { + logWarn('processJiraTicket', `${issueKey} skipped: missing request type in customfield_10010`); + return; + } - if (requests[requestType].transitions[transitionId]) { - if (jiraTicket.issue.fields.project.key == 'CHANGE') { - logger('processJiraTicket', `${issueKey} is a CHANGE.`); + 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 if (jiraTicket.issue.fields.project.key == 'REQUEST') { - logger('processJiraTicket', `${issueKey} is a REQUEST.`); + } 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(jiraTicket.issue.fields.status.name)) { + if (change.field != 'Approvers' || !approvalStatuses.includes(statusName)) { continue; } - if (jiraTicket.issue.fields.project.key == 'CHANGE') { + 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 };