collabSupport/services/renderers/callTestRenderer.js
Joseph McQueen 7aa8c37d1d Add Twilio /calltest for store and direct-dial voice path testing.
Enables outbound PSTN probes via TwiML webhooks with Webex result cards, status polling, and optional store CDR enrichment.
2026-07-23 17:59:10 -04:00

119 lines
3.8 KiB
JavaScript
Raw 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.

// services/renderers/callTestRenderer.js
function fmtMs(ms) {
if (ms == null || !Number.isFinite(ms)) return '—';
if (ms < 1000) return `${Math.round(ms)}ms`;
return `${(ms / 1000).toFixed(1)}s`;
}
function fmtTime(iso) {
if (!iso) return '—';
try {
return new Date(iso).toISOString();
} catch {
return String(iso);
}
}
/**
* @param {object} session
*/
export function renderCallTestStartedMarkdown(session) {
const modeLabel = session.mode === 'store' ? 'Store path' : 'Direct dial';
const storePart = session.storeNum ? ` for store **${session.storeNum}**` : '';
return (
`**Call test started** (${modeLabel})${storePart}\n\n` +
`- **Dialing:** ${session.dialNumber}\n` +
`- **Test ID:** \`${session.testId}\`\n` +
`- Results will post here when the call completes (~23 min).`
);
}
/**
* @param {object} session
*/
export function renderCallTestResultMarkdown(session) {
const modeLabel = session.mode === 'store' ? 'Store' : 'Direct';
const inProgress = session.result == null
&& (session.status === 'in_progress' || session.status === 'pending');
const pass = session.result === 'pass';
const warn = session.result === 'warn';
const icon = inProgress ? '⏳' : (pass || warn ? '✅' : '❌');
const headline = inProgress
? 'IN PROGRESS'
: (pass || warn ? 'PASSED' : 'FAILED');
let md = `**${icon} Call test ${headline}** (${modeLabel})\n\n`;
if (session.storeNum) md += `- **Store:** ${session.storeNum}\n`;
md += `- **Dialed:** ${session.dialNumber}\n`;
md += `- **Test ID:** \`${session.testId}\`\n`;
if (session.callSid) md += `- **Twilio CallSid:** \`${session.callSid}\`\n`;
if (session.resultReason) {
md += `- **Result:** ${session.resultReason}\n`;
}
if (session.failedAt) {
md += `- **Failed at:** ${session.failedAt}\n`;
}
const e = session.events || {};
md += '\n**Timings:**\n';
if (e.initiated) md += `- Initiated: ${fmtTime(e.initiated)}\n`;
if (e.ringing) md += `- Ringing: ${fmtTime(e.ringing)}\n`;
if (e.answered) md += `- Answered: ${fmtTime(e.answered)}\n`;
if (!e.answered && e['in-progress']) md += `- In progress: ${fmtTime(e['in-progress'])}\n`;
if (e.completed) md += `- Completed: ${fmtTime(e.completed)}\n`;
if (e.answered && e.initiated) {
md += `- Ring → answer: ${fmtMs(new Date(e.answered) - new Date(e.initiated))}\n`;
}
if (e.coreStartedAt) {
md += `- Core started: ${fmtTime(e.coreStartedAt)}\n`;
}
if (e.completed && e.answered) {
md += `- Answer → complete: ${fmtMs(new Date(e.completed) - new Date(e.answered))}\n`;
}
if (session.durationSec != null) {
md += `- Total duration: ${session.durationSec}s\n`;
}
if (session.mode === 'store' && e.dtmfStepAt) {
md += `- Entry → DTMF step: ${fmtTime(e.dtmfStepAt)}\n`;
}
return md.trim();
}
/**
* @param {object} session
* @param {object} cdr
*/
export function renderCallTestCdrMarkdown(session, cdr) {
if (!cdr?.available) {
return (
`**Call test CDR** (\`${session.testId}\`)\n\n` +
`_Detailed call history unavailable:_ ${cdr?.reason || 'not configured'}`
);
}
const summary = cdr.summary || {};
let md =
`**Call test CDR enrichment** (store **${session.storeNum}**)\n\n` +
`- **Window:** ${cdr.startTime}${cdr.endTime}\n` +
`- **Location:** ${cdr.location || '—'}\n` +
`- **Matching legs:** ${summary.totalCalls ?? cdr.rawCount ?? 0}\n`;
if (summary.inbound != null) md += `- Inbound legs: ${summary.inbound}\n`;
if (summary.missed != null) md += `- Missed / failed legs: ${summary.missed}\n`;
const samples = cdr.samples || [];
if (samples.length) {
md += '\n**Sample legs:**\n';
for (const s of samples.slice(0, 5)) {
md += `- ${s.start || '—'} | ${s.direction || '—'} | ${s.duration ?? '—'}s | ${s.otherParty || s.phoneNumber || '—'}\n`;
}
}
return md.trim();
}