sdp-request-watcher/src/tickets.js

176 lines
6.3 KiB
JavaScript

const axios = require('axios');
function toMillis(completedTime) {
if (!completedTime) return null;
const raw = typeof completedTime === 'object' ? completedTime.value : completedTime;
const ms = Number(raw);
return Number.isFinite(ms) ? ms : null;
}
function getRelevantTicketTime(ticket) {
return toMillis(ticket.completed_time)
|| toMillis(ticket.resolved_time)
|| toMillis(ticket.last_updated_time)
|| null;
}
async function fetchClosedTickets({ accessToken, state, tickets, technicians, statusSet, windowMinutes }) {
if (!accessToken) return;
try {
const windowStart = Date.now() - windowMinutes * 60 * 1000;
const listInfo = {
list_info: {
row_count: 200,
sort_field: 'last_updated_time',
sort_order: 'desc',
fields_required: [
'display_id',
'subject',
'status',
'completed_time',
'resolved_time',
'last_updated_time',
'technician'
],
search_criteria: [
{
field: 'last_updated_time',
condition: 'greater_than',
value: windowStart.toString()
}
]
}
};
const response = await axios.get('https://ithelp.mainecc.edu/api/v3/requests', {
params: { input_data: JSON.stringify(listInfo) },
headers: {
Authorization: `Zoho-oauthtoken ${accessToken}`,
Accept: 'application/vnd.manageengine.sdp.v3+json'
}
});
const found = response.data?.requests || [];
for (const ticket of found) {
const statusName = ticket.status?.name || '';
const statusInternalName = ticket.status?.internal_name || '';
const statusMatches = !statusSet.size
|| statusSet.has(statusName.toLowerCase())
|| statusSet.has(statusInternalName.toLowerCase());
if (!statusMatches) continue;
const closedAt = getRelevantTicketTime(ticket);
if (!closedAt || closedAt <= windowStart) continue;
const technician = ticket.technician ? ticket.technician.name : null;
if (!technician) continue;
if (technicians.length && !technicians.includes(technician)) continue;
tickets.set(String(ticket.display_id), {
id: ticket.display_id,
subject: ticket.subject,
technician,
status: statusName || null,
closedAt,
closedDisplay: ticket.completed_time?.display_value
|| ticket.resolved_time?.display_value
|| ticket.last_updated_time?.display_value
|| null
});
}
const cutoff = Date.now() - 12 * 60 * 60 * 1000;
for (const [key, ticket] of tickets) {
if (ticket.closedAt < cutoff) tickets.delete(key);
}
state.lastCheck = Date.now();
state.lastError = null;
} catch (err) {
const detail = err.response?.data;
state.lastError = detail?.response_status?.messages?.[0]?.message
|| (typeof detail === 'string' ? detail : null)
|| err.message;
state.lastCheck = Date.now();
console.error('Error: ', detail || err.message);
}
}
async function fetchUnassignedCount({ accessToken, state, statusSet, siteSet, groupSet }) {
if (!accessToken) return;
try {
let count = 0;
let startIndex = 1;
const rowCount = 100;
const maxPages = 50; // safety cap: 5000 unassigned tickets
// The SDP instance ignores search_criteria arrays with more than one
// condition, so only "technician is unassigned" is filtered server-side;
// status/site/group are matched client-side per ticket below.
for (let page = 0; page < maxPages; page++) {
const listInfo = {
list_info: {
row_count: rowCount,
start_index: startIndex,
search_criteria: {
field: 'technician',
condition: 'is',
value: null
},
fields_required: ['status', 'technician', 'group', 'requester']
}
};
const response = await axios.get('https://ithelp.mainecc.edu/api/v3/requests', {
params: { input_data: JSON.stringify(listInfo) },
headers: {
Authorization: `Zoho-oauthtoken ${accessToken}`,
Accept: 'application/vnd.manageengine.sdp.v3+json'
}
});
const found = response.data?.requests || [];
for (const ticket of found) {
if (ticket.technician) continue;
const statusName = (ticket.status?.name || '').toLowerCase();
const statusInternalName = (ticket.status?.internal_name || '').toLowerCase();
const statusMatches = !statusSet.size
|| statusSet.has(statusName)
|| statusSet.has(statusInternalName);
if (!statusMatches) continue;
const groupName = (ticket.group?.name || '').toLowerCase();
if (groupSet.size && !groupSet.has(groupName)) continue;
const siteName = (ticket.requester?.site?.name || '').toLowerCase();
if (siteSet.size && !siteSet.has(siteName)) continue;
count++;
}
if (!response.data?.list_info?.has_more_rows || found.length === 0) break;
startIndex += rowCount;
}
state.unassignedCount = count;
state.unassignedError = null;
} catch (err) {
const detail = err.response?.data;
state.unassignedError = detail?.response_status?.messages?.[0]?.message
|| (typeof detail === 'string' ? detail : null)
|| err.message;
state.unassignedCount = null;
console.error('Error fetching unassigned count: ', detail || err.message);
}
}
module.exports = {
fetchClosedTickets,
fetchUnassignedCount
};