From 106b24cd5372cc907ff197c257c71954e9403440 Mon Sep 17 00:00:00 2001 From: Andrew Balmos Date: Wed, 11 Sep 2024 00:41:15 -0400 Subject: [PATCH] feat: Process pending tickets in reverse order Signed-off-by: Andrew Balmos --- service/src/index.ts | 18 +++--- service/src/services/poller.ts | 106 ++++++++++++++++++--------------- service/src/types.ts | 96 ++++++++++++++--------------- 3 files changed, 112 insertions(+), 108 deletions(-) diff --git a/service/src/index.ts b/service/src/index.ts index 16ee8fb..89d1ce7 100644 --- a/service/src/index.ts +++ b/service/src/index.ts @@ -14,11 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - archiveTicketService, - makeArchiveTicketJob, -} from './services/archiveTicket.js'; import { Service } from '@oada/jobs'; +import { archiveTicketService } from './services/archiveTicket.js'; import { config } from './config.js'; import { connect } from '@oada/client'; import esMain from 'es-main'; @@ -26,7 +23,6 @@ import { lfCloserService } from './services/lfCloser.js'; import { makeLoggers } from './logger.js'; import { pollerService } from './services/poller.js'; import { readFileSync } from 'node:fs'; -import { isCloser } from './types.js'; const log = makeLoggers(''); @@ -64,12 +60,12 @@ async function run() { concurrency: config.get('zendesk.concurrency'), }); - // log.info({}, 'Initialize `archiveTicket` service'); - // service.on( - // config.get('service.archiveTicket.name'), - // config.get('service.archiveTicket.timeout'), - // archiveTicketService, - // ); + log.info({}, 'Initialize `archiveTicket` service'); + service.on( + config.get('service.archiveTicket.name'), + config.get('service.archiveTicket.timeout'), + archiveTicketService, + ); log.info({}, 'Initialize `lfCloser` service'); service.on( diff --git a/service/src/services/poller.ts b/service/src/services/poller.ts index fe4f773..913b076 100644 --- a/service/src/services/poller.ts +++ b/service/src/services/poller.ts @@ -50,46 +50,52 @@ export function pollerService(oada: OADAClient): CronJob { try { log.info('Polling ZenDesk for eligible tickets'); - const tickets = await searchTickets('status:solved'); + let tickets = await searchTickets('status:solved'); + // Process them in new to old order, where old is likely to still have the same + // issues that held it up prior + tickets = tickets.reverse(); + log.trace({}, `Found ${tickets.length} tickets.`); - // Check each ticket from the search result in parallel - await Promise.all( - tickets.map(async (t) => { - // NOTE: The ticket that is returned by the search can be out of date. - // Even thought we already have the ticket, we need to get it a - // fresh copy from the API to make the following tests are done - // against the current ticket state rather than some old cache - // from the search API. - const ticket = await getTicket(t.id); - - const nextState = await computeNextState(ticket); - - // Update Zendesk with the new state, but only if changed. Otherwise, Zendesk tickets are flodded with "useless" updates - if ( - getTicketFieldValue( - ticket, - config.get('zendesk.fields.state'), - ) !== nextState.state || - getTicketFieldValue( - ticket, - config.get('zendesk.fields.status'), - ) !== nextState.status - ) { - await setTrellisState(ticket, nextState); - } - - // Make a job to move foward in the state machine - if (nextState.state === 'trellis-processing') { - log.info({ ticketId: ticket.id }, 'Creating an archive job'); - - await makeArchiveTicketJob(oada, { - ticketId: ticket.id, - closer: isCloser(config.get('service.poller.closer')), - }); - } - }), - ); + // TODO: Check the potential tickets in parallel? + for await (const t of tickets) { + log.info({ ticketId: t.id }, 'Checking ticket'); + + // NOTE: The ticket that is returned by the search can be out of date. + // Even thought we already have the ticket, we need to get it a + // fresh copy from the API to make the following tests are done + // against the current ticket state rather than some old cache + // from the search API. + const ticket = await getTicket(t.id); + + const nextState = await computeNextState(ticket); + const currentState = getTicketFieldValue( + ticket, + config.get('zendesk.fields.state'), + ); + const currentStatus = getTicketFieldValue( + ticket, + config.get('zendesk.fields.status'), + ); + + // Update Zendesk with the new state, but only if changed. Otherwise, Zendesk tickets are flodded with "useless" updates + if ( + currentState !== nextState.state || + currentStatus !== nextState.status + ) { + await setTrellisState(ticket, nextState); + } + + // Make a job to move foward in the state machine + if (nextState.state === 'trellis-processing') { + log.info({ ticketId: ticket.id }, 'Creating an archive job'); + + await makeArchiveTicketJob(oada, { + ticketId: ticket.id, + closer: isCloser(config.get('service.poller.closer')), + }); + } + } } catch (error) { log.error({ error }, `Error polling ZenDesk: ${error} `); } finally { @@ -138,7 +144,9 @@ async function computeNextState(ticket: Ticket): Promise { }; // If the ticket is yound, wait for someone to set the SAP ID on the organization - } else if (age <= config.get('service.poller.force-age')) { + } + + if (age <= config.get('service.poller.force-age')) { log.trace({ ticketId }, 'Skipping young ticket with no SAPID.'); return { state: 'trellis-pending', @@ -146,15 +154,15 @@ async function computeNextState(ticket: Ticket): Promise { }; // The ticket is too old, and will be auto-closed by ZenDesk. Force an archive without an SAP ID. - } else { - log.trace( - { ticketId }, - `Archive old ticket (> ${(config.get('service.poller.force-age') / (24 * 60 * 60)).toFixed(1)} days old)`, - ); - - return { - state: 'trellis-processing', - status: `Forced archive due to age (> ${(config.get('service.poller.force-age') / (24 * 60 * 60)).toFixed(1)} days old)`, - }; } + + log.trace( + { ticketId }, + `Archive old ticket (> ${(config.get('service.poller.force-age') / (24 * 60 * 60)).toFixed(1)} days old)`, + ); + + return { + state: 'trellis-processing', + status: `Forced archive due to age (> ${(config.get('service.poller.force-age') / (24 * 60 * 60)).toFixed(1)} days old)`, + }; } \ No newline at end of file diff --git a/service/src/types.ts b/service/src/types.ts index 69712db..a5d8de1 100644 --- a/service/src/types.ts +++ b/service/src/types.ts @@ -82,17 +82,17 @@ export interface Ticket { channel: string | number; source: { from: - | Record - | { - name: string; - address: string; - }; + | Record + | { + name: string; + address: string; + }; to: - | Record - | { - name: string; - address: string; - }; + | Record + | { + name: string; + address: string; + }; rel: string | undefined; }; }; @@ -247,19 +247,19 @@ export interface Comment { channel: string; source: { from: - | undefined - | { - address: string; - name: string | undefined; - organization_recipients: string[] | undefined; - }; + | undefined + | { + address: string; + name: string | undefined; + organization_recipients: string[] | undefined; + }; to: - | undefined - | { - name: string | undefined; - address: string; - email_ccs: number[]; - }; + | undefined + | { + name: string | undefined; + address: string; + email_ccs: number[]; + }; rel: undefined; }; }; @@ -310,32 +310,32 @@ export interface SideConversationEvent { via: string; created_at: string; message: - | undefined - | { - subject: string | undefined; - preview_text: string; - from: { - user_id: number; - group_id?: number; - name: string; - email: string; - }; - to: Array<{ - user_id: number; - group_id?: number; - name: string; - email: string; - }>; - body: string; - html_body: string; - external_ids: { - ticketAuditId: string; - targetTicketAuditId?: string; - outboundEmail?: string; - inboundEmail?: string; - }; - attachments: SideConversationAttachment[]; - }; + | undefined + | { + subject: string | undefined; + preview_text: string; + from: { + user_id: number; + group_id?: number; + name: string; + email: string; + }; + to: Array<{ + user_id: number; + group_id?: number; + name: string; + email: string; + }>; + body: string; + html_body: string; + external_ids: { + ticketAuditId: string; + targetTicketAuditId?: string; + outboundEmail?: string; + inboundEmail?: string; + }; + attachments: SideConversationAttachment[]; + }; updates: Record; ticket_id: number; }