collabSupport/commands/callReport.js
jmcqueen a25fc08fe2 Rename /voicereport to /callreport with scoped user and phone targets.
Add store/email/phone filtering, richer call-line formatting, CDR feed pagination and queueing, and split Jira poller enrichment into testable modules.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 10:10:03 -04:00

107 lines
3.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// commands/callReport.js
import { DateTime } from 'luxon';
import { logger } from '../utils/logger.js';
import { collectCallReport } from '../services/callReport/callReportService.js';
import { renderCallReportMarkdown } from '../services/renderers/callReportRenderer.js';
import { parseCallReportTarget } from '../services/callReport/parseTarget.js';
function pickDateArg(args) {
const targetParsed = parseCallReportTarget(args[0]);
for (let i = 1; i < args.length; i++) {
const a = String(args[i] || '').trim();
if (!a || a.startsWith('--')) continue;
if (i === 1 && targetParsed?.kind === 'store' && /^\d{2,4}$/.test(a)) continue;
return a;
}
const flag = args.find((a) => String(a).startsWith('--date='));
if (flag) return flag.split('=').slice(1).join('=');
const idx = args.indexOf('--date');
if (idx >= 0 && args[idx + 1]) return args[idx + 1];
return null;
}
function hasDetailFlag(args, query) {
return args.some((a) => ['detail', 'detailed', '--detail', '--detailed'].includes(String(a).toLowerCase()))
|| query.detail === 'true'
|| query.detailed === 'true';
}
function formatQueuedRunAt(runAt) {
return DateTime.fromJSDate(runAt).toFormat('h:mm:ss a');
}
function pickTargetArg(args, query) {
return args[0]?.trim()
|| query.target
|| query.storeNum
|| query.store
|| query.email
|| query.number
|| query.s
|| null;
}
function usageMarkdown() {
return (
'**Call report usage**\n\n' +
'`/callreport <store>` — yesterday 9am9pm local (default)\n' +
'`/callreport <email>` — calls for that user at their location\n' +
'`/callreport <phone>` — calls for that assigned number\n' +
'`/callreport <target> today` — today 9am → 5 min ago\n' +
'`/callreport <target> 2026-07-22` — specific day\n' +
'`/callreport <target> --detail` — include per-call table\n\n' +
'HTTP: `?target=782&date=yesterday&detail=true` (also `storeNum`, `email`, `number`)'
);
}
function ackLabel(target) {
if (!target) return 'target';
if (target.kind === 'store') return `store **${target.storeNum}**`;
if (target.locationName) return `**${target.label}** (${target.locationName})`;
return `**${target.label}**`;
}
export async function handleCallReport(bot, trigger) {
logger('callreport', 'Handler entered', 'debug');
const query = trigger.query || {};
const args = trigger.args || [];
const targetToken = pickTargetArg(args, query);
const dateArg = pickDateArg(args) || query.date || null;
const detail = hasDetailFlag(args, query);
if (!targetToken || !parseCallReportTarget(targetToken)) {
await bot.say('markdown', usageMarkdown());
return;
}
await bot.say(
'markdown',
`**Call report** — generating for ${ackLabel({ label: targetToken })} (${dateArg || 'yesterday'})…`,
);
let cdrQueuedNoticeSent = false;
try {
const report = await collectCallReport(targetToken, {
dateArg,
onCdrQueued: async ({ runAt, waitMs }) => {
if (cdrQueuedNoticeSent) return;
cdrQueuedNoticeSent = true;
const secs = Math.max(1, Math.ceil(waitMs / 1000));
await bot.say(
'markdown',
`⏳ **Call report** — CDR query queued. Webex allows one \`cdr_feed\` request per ~90 seconds. ` +
`Your request will run at **${formatQueuedRunAt(runAt)}** (~${secs}s).`,
);
},
});
const md = renderCallReportMarkdown(report, { detail });
await bot.say('markdown', md);
} catch (err) {
logger('callreport', `Failed for target ${targetToken}: ${err.message}`, 'error');
await bot.say('markdown', `❌ Call report failed for **${targetToken}**: ${err.message}`);
}
}