Stabilize logging, correlation IDs, service-account Jira auth, and direct approver reminder DMs while splitting the monolith into focused modules with Compose-based deployment. Co-authored-by: Cursor <cursoragent@cursor.com>
100 lines
4.6 KiB
JavaScript
100 lines
4.6 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import { formatDuration } from '../lib/util.js';
|
|
|
|
export function createPendingApprovalsJob({ jiraService, webexService, log }) {
|
|
const { logger, logWarn, logError, logDebug } = log;
|
|
|
|
function sendApprovalReminder(personEmail, approvals) {
|
|
return new Promise(function (resolve, reject) {
|
|
let msgText = 'You have the following pending approvals:\n';
|
|
|
|
for (const approval of approvals) {
|
|
msgText += `- [${approval.key}](https://aeo.atlassian.net/servicedesk/customer/portal/135/${approval.key}): ${approval.summary} ${approval.duration}\n`;
|
|
}
|
|
|
|
msgText += '\nYou can find these in the [Requests Pending Approvals](https://aeo.atlassian.net/servicedesk/customer/user/approvals?page=1) portal. If you have any questions or experience issues, please submit a [support request](https://aeo.atlassian.net/servicedesk/customer/portals).';
|
|
|
|
logDebug('sendApprovalReminder', `Sending direct reminder to ${personEmail}:\n${msgText}`);
|
|
|
|
webexService.sendWebexMessage({
|
|
toPersonEmail: personEmail,
|
|
markdown: msgText,
|
|
})
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error));
|
|
});
|
|
}
|
|
|
|
function runPendingApprovals() {
|
|
return jiraService.searchUnapprovedRequests()
|
|
.then(async function (tickets) {
|
|
if (!tickets || tickets.length === 0) {
|
|
logger('runPendingApprovals', 'No pending approval tickets found.');
|
|
return;
|
|
}
|
|
|
|
const needApproval = {};
|
|
for (const ticket of tickets) {
|
|
if (ticket.fields.customfield_10033) {
|
|
for (const approvals of ticket.fields.customfield_10033) {
|
|
if (approvals.approvers.length > 0 && approvals.finalDecision == 'pending') {
|
|
for (const approver of approvals.approvers) {
|
|
if (approver.approverDecision != 'approved' && new Date(approvals.createdDate.iso8601) < new Date(new Date() - (24 * 60 * 60 * 1000))) {
|
|
if (!needApproval[approver.approver.emailAddress]) {
|
|
needApproval[approver.approver.emailAddress] = [];
|
|
}
|
|
needApproval[approver.approver.emailAddress].push({
|
|
key: ticket.key,
|
|
summary: ticket.fields.summary,
|
|
reporter: ticket.fields.reporter.displayName,
|
|
duration: formatDuration(approvals.createdDate.iso8601),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
logWarn('runPendingApprovals', `No approvals setup in customfield_10033 for ${ticket.key}.`);
|
|
}
|
|
}
|
|
|
|
for (const approver in needApproval) {
|
|
await sendApprovalReminder(approver, needApproval[approver])
|
|
.then(() => logger('runPendingApprovals', `Sent reminder to ${approver}.`))
|
|
.catch(error => logError('runPendingApprovals', `Failed to send reminder to ${approver}`, error));
|
|
}
|
|
})
|
|
.catch(error => logError('runPendingApprovals', 'Failed to run pending approvals job', error));
|
|
}
|
|
|
|
return { runPendingApprovals };
|
|
}
|
|
|
|
export async function cleanupOldLogs(log) {
|
|
const { logger, logError } = log;
|
|
const now = Date.now();
|
|
const directory = './logs';
|
|
const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000;
|
|
|
|
let files;
|
|
try {
|
|
files = await fs.promises.readdir(directory);
|
|
} catch (error) {
|
|
logError('cleanupOldLogs', `Failed to read log directory ${directory}`, error);
|
|
return;
|
|
}
|
|
|
|
for (const file of files) {
|
|
const filePath = path.join(directory, file);
|
|
try {
|
|
const stats = await fs.promises.stat(filePath);
|
|
if (stats.isFile() && (now - stats.mtimeMs) > sevenDaysInMs) {
|
|
await fs.promises.unlink(filePath);
|
|
logger('cleanupOldLogs', `Deleted old log file: ${file}`);
|
|
}
|
|
} catch (error) {
|
|
logError('cleanupOldLogs', `Failed to process log file ${file}`, error);
|
|
}
|
|
}
|
|
}
|