jiraCloud/services/webex.js
jmcqueen efc64a227c Add Webex inbound OAuth flow and room-to-Jira job request pipeline.
Introduces integration-based webhook registration, message parsing, dry-run
monitoring, JSM ticket creation, and OAuth token refresh for DC Ops spaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 08:27:52 -04:00

155 lines
5.9 KiB
JavaScript

import fetch from 'node-fetch';
export function createWebexService(config, log) {
const { logger, logError } = log;
function handleWebexResponse(context, issueKey, response, responseText) {
if (!response.ok) {
logError(context, `Webex API error for ${issueKey} (HTTP ${response.status})`, responseText);
return false;
}
logger(context, `Successfully sent notification for ${issueKey}`);
return true;
}
function sendTicketCard(issueKey, body) {
const requestOptions = {
method: 'POST',
headers: {
Authorization: `Bearer ${config.webex.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
redirect: 'follow',
};
fetch('https://webexapis.com/v1/messages', requestOptions)
.then(response => response.text().then(text => ({ response, text })))
.then(({ response, text }) => handleWebexResponse('sendTicketCard', issueKey, response, text))
.catch(error => logError('sendTicketCard', `Network error sending to Webex for ${issueKey}`, error));
}
function sendWebexMessage(body) {
return new Promise(function (resolve, reject) {
const requestOptions = {
method: 'POST',
headers: {
Authorization: `Bearer ${config.webex.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
redirect: 'follow',
};
fetch('https://webexapis.com/v1/messages', requestOptions)
.then(response => response.text().then(text => ({ response, text })))
.then(({ response, text }) => {
if (!response.ok) {
return reject(new Error(`Webex API error (HTTP ${response.status}): ${text}`));
}
try {
resolve(JSON.parse(text));
} catch {
reject(new Error(`Invalid JSON response from Webex: ${text}`));
}
})
.catch(error => reject(error));
});
}
function sendHighPriorityMessage(body) {
const requestOptions = {
method: 'POST',
headers: {
Authorization: `Bearer ${config.jiraNotifier.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
redirect: 'follow',
};
fetch('https://webexapis.com/v1/messages', requestOptions)
.then(response => response.text().then(text => ({ response, text })))
.then(({ response, text }) => handleWebexResponse('sendHighPriorityMessage', body.roomId || 'unknown', response, text))
.catch(error => logError('sendHighPriorityMessage', 'Network error sending to Webex', error));
}
function replyToMessage({ roomId, parentId, markdown }) {
return sendWebexMessage({
roomId,
parentId,
markdown,
});
}
async function getMessage(messageId) {
const response = await fetch(`https://webexapis.com/v1/messages/${encodeURIComponent(messageId)}`, {
headers: {
Authorization: `Bearer ${config.webex.token}`,
Accept: 'application/json',
},
});
const responseText = await response.text();
let data;
try {
data = responseText ? JSON.parse(responseText) : {};
} catch {
throw new Error(`Invalid JSON response from Webex: ${responseText}`);
}
if (!response.ok) {
throw new Error(data.message || `Webex API error (HTTP ${response.status}): ${responseText}`);
}
return data;
}
function processHighSeverity(jiraTicket, roomId) {
const jiraKey = jiraTicket.issue.key;
if (jiraTicket.webhookEvent == 'comment_created' && jiraTicket.comment) {
const action = jiraTicket.comment.created == jiraTicket.comment.updated ? 'added a comment' : 'edited a comment';
const userName = jiraTicket.comment.updateAuthor.displayName;
const projectKey = jiraTicket.issue.fields.project.name;
const comment = jiraTicket.comment.body;
sendHighPriorityMessage({
markdown: `${userName} ${action} on the [${jiraKey}](https://aeo.atlassian.net/browse/${jiraKey}) issue in the ${projectKey} project to read as follows:\n${comment}`,
roomId,
});
}
if (jiraTicket.webhookEvent == 'jira:issue_updated' && jiraTicket.changelog) {
const userName = jiraTicket.user.displayName;
const statuses = ['status', 'Severity', 'priority', 'Severity (migrated)'];
let text = '';
let addDescription = 0;
for (const change of jiraTicket.changelog.items) {
if (statuses.includes(change.field)) {
text += `${userName} changed ${change.field} from ${change.fromString} to ${change.toString} for issue [${jiraKey}](https://aeo.atlassian.net/browse/${jiraKey}).\n`;
}
if (change.field == 'Severity' || change.field == 'priority') {
addDescription = 1;
}
}
if (addDescription > 0) {
text += `Description / Details: ${jiraTicket.issue.fields.description}\n`;
}
log.logDebug('processHighSeverity', `Sending high-priority update for ${jiraKey}`);
sendHighPriorityMessage({ markdown: text, roomId });
}
}
return {
sendTicketCard,
sendWebexMessage,
sendHighPriorityMessage,
replyToMessage,
getMessage,
processHighSeverity,
};
}