collabSupport/commands/jiraHistory.js
Joseph McQueen 1117be40cc Format chat footers in DISPLAY_TIMEZONE instead of UTC.
Docker hosts default to UTC, so bare toLocaleTimeString() showed
wrong "Last checked" times in avstatus and other commands. Add
formatDisplayTime() (default America/New_York, overridable via
DISPLAY_TIMEZONE) and use it across renderers and command footers.
2026-07-21 14:41:26 -04:00

147 lines
No EOL
6 KiB
JavaScript

// src/commands/jiraHistory.js
import {
getJiraTicketsForStore,
getJiraTicketsForComponentWithStore,
getStatusEmoji,
calculateDaysOpen
} from '../services/jiraService.js';
import { summarizeJiraTicket } from '../services/jiraSummarizer.js';
import { analyzeCommonIssues } from '../services/jiraSummarizer.js';
import jira from '../integrations/jira/JiraClient.js';
import { formatDisplayTime } from '../utils/time.js';
import { logger } from '../utils/logger.js';
const MAX_TICKETS = 20;
export async function handleJiraHistory(bot, trigger) {
logger('jira:history', 'Handler entered', 'debug');
// Support both Webex (args) and HTTP (query)
const query = trigger.query || {};
const args = trigger.args || [];
const arg1 = args[0]?.trim() || query.storeNum || query.store || query.s;
const arg2 = args[1]?.trim() || query.component || query.components;
logger('jira:history', `Request for store/component: ${arg1} | ${arg2 || 'all'}`);
if (!arg1) {
const usage = '**Jira History Usage:**\n' +
'`/jiraHistory 782` → All tickets (max 20)\n' +
'`/jiraHistory 782 phone` → Communication Services only\n' +
'`/jiraHistory 782 av,voice,mobility` → Multiple components';
await bot.say('markdown', usage);
return;
}
if (!/^\d{2,4}$/.test(arg1)) {
await bot.say('markdown', 'Please provide a valid 2-4 digit store number.');
return;
}
const storeNum = arg1.padStart(5, '0'); // normalize to 5 digits if needed
try {
let reply = `**Jira Tickets - Store ${storeNum}**\n\n`;
let ticketKeys = [];
if (!arg2 || arg2.toLowerCase() === 'all') {
logger('jira:history', `Fetching all tickets for store ${storeNum}`, 'debug');
const results = await getJiraTicketsForStore(storeNum);
ticketKeys = results.map(t => t.key);
} else {
const components = arg2.split(',').map(c => c.trim().toLowerCase());
logger('jira:history', `Fetching tickets for components: ${components.join(', ')}`, 'debug');
for (const comp of components) {
const results = await getJiraTicketsForComponentWithStore(storeNum, comp);
ticketKeys = ticketKeys.concat(results.map(t => t.key));
}
}
ticketKeys = [...new Set(ticketKeys)]; // dedup across components if any
// Limit to most recent
if (ticketKeys.length > MAX_TICKETS) {
ticketKeys = ticketKeys.slice(0, MAX_TICKETS);
}
if (ticketKeys.length === 0) {
reply += 'No matching Jira tickets found.\n';
} else {
logger('jira:history', `Fetching details for ${ticketKeys.length} tickets in parallel`, 'debug');
const ticketPromises = ticketKeys.map(async (key) => {
try {
const fullTicket = await jira.getTicket(key);
const aiSummary = await summarizeJiraTicket(fullTicket).catch(() =>
"**Reported Problem:** No details available.\n**Steps Taken:** No information recorded."
);
const fields = fullTicket.fields || {};
const statusEmoji = getStatusEmoji(fields.status?.name);
const component = fields.components?.[0]?.name || '—';
const assignee = fields.assignee?.displayName || '**Unassigned**';
const days = calculateDaysOpen(fields.created, fields.resolved || fields.updated);
return {
key,
summary: fields.summary || '—',
statusEmoji,
status: fields.status?.name || '—',
component,
assignee,
days,
aiSummary: aiSummary.substring(0, 800) // Limit summary length
};
} catch (err) {
logger('jira:history', `Failed to process ticket ${key}: ${err.message}`, 'warn');
return null;
}
});
const ticketResults = (await Promise.all(ticketPromises)).filter(Boolean);
// Build reply with length control
let currentLength = reply.length;
for (const t of ticketResults) {
let ticketBlock = `**${t.key}** - ${t.summary}\n\n`;
ticketBlock += `${t.aiSummary}\n\n`;
ticketBlock += `${t.statusEmoji} **Status:** ${t.status} • Component: ${t.component}\n`;
ticketBlock += `Assigned: ${t.assignee} • Open for: ${t.days} days\n\n`;
ticketBlock += `---\n\n`;
// Check if adding this ticket would exceed safe limit
if (currentLength + ticketBlock.length > 6500) {
reply += `\n**... and ${ticketResults.length - ticketResults.indexOf(t)} more tickets.**\n`;
reply += `Use /jiraHistory ${storeNum} for full list.\n`;
break;
}
reply += ticketBlock;
currentLength += ticketBlock.length;
}
// Common Issues Analysis (only if we have enough tickets and space)
if (ticketResults.length >= 3 && currentLength < 6000) {
reply += `**Common Issues Across These Tickets:**\n`;
const common = await analyzeCommonIssues(ticketResults).catch(() =>
"Unable to identify common patterns at this time."
);
reply += `${common}\n`;
}
}
reply += `\n*Last checked: ${formatDisplayTime()}*`;
await bot.say('markdown', reply.trim());
} catch (err) {
logger('jira:history', `Error processing jiraHistory: ${err.message}`, 'error');
await bot.say('markdown', `❌ Error retrieving Jira history: ${err.message}`);
}
}
// (helpers imported from jiraService for consistency with jiraTicket etc.)