/**
* Formatting utilities for Jira data
*/
import { JiraIssue } from '../types.js';
/**
* Format a date string to a more readable format
*/
export function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
});
}
/**
* Format a Jira issue for display
*/
export function formatIssue(issue: JiraIssue, storyPointsField: string | null = null): string {
// Add defensive checks for required fields
if (!issue || !issue.fields) {
return 'Invalid issue data';
}
let output = `${issue.key}: ${issue.fields.summary || 'No summary'}
- Type: ${issue.fields.issuetype?.name || 'Unknown type'}
- Status: ${issue.fields.status?.name || 'Unknown status'}
- Priority: ${issue.fields.priority?.name || 'Not set'}
- Assignee: ${issue.fields.assignee?.displayName || 'Unassigned'}`;
// Only show Story Points if field is configured
if (storyPointsField && issue.fields[storyPointsField] !== undefined) {
output += `\n- Story Points: ${issue.fields[storyPointsField] || 'Not set'}`;
}
// Add Rank information if available (customfield_10019)
if (issue.fields.customfield_10019) {
output += `\n- Rank: ${issue.fields.customfield_10019}`;
}
if (issue.fields.created) {
output += `\n- Created: ${formatDate(issue.fields.created)}`;
}
// Add Sprint information if available - using try/catch to handle potential issues
try {
if (
issue.fields.customfield_10020 &&
Array.isArray(issue.fields.customfield_10020) &&
issue.fields.customfield_10020.length > 0
) {
const sprint = issue.fields.customfield_10020[0];
if (sprint && typeof sprint === 'object') {
output += `\n- Sprint: ${sprint.name || 'Unknown'} (${sprint.state || 'Unknown'})`;
if (sprint.id) {
output += `\n- Sprint ID: ${sprint.id}`;
}
}
}
} catch (error) {
console.error('Error formatting sprint information:', error);
}
// Handle description - could be string, ADF object, or null
let description = 'No description';
if (issue.fields.description) {
if (typeof issue.fields.description === 'string') {
description = issue.fields.description;
} else if (typeof issue.fields.description === 'object') {
// For ADF format, just indicate it exists (full conversion would be complex)
description = '[Rich text content]';
}
}
output += `\n- Description: ${description}`;
if (issue.fields.creator?.displayName) {
output += `\n- Creator: ${issue.fields.creator.displayName}`;
}
// Add labels if any exist
if (issue.fields.labels && issue.fields.labels.length > 0) {
output += `\n- Labels: ${issue.fields.labels.join(', ')}`;
}
if (issue.fields.parent) {
output += `\n- Parent Epic: ${issue.fields.parent.key}`;
if (issue.fields.parent.fields?.summary) {
output += ` - ${issue.fields.parent.fields.summary}`;
}
}
// Note: Epic link is now handled via the parent field above
// Legacy Epic Link field support would go here if needed
const comments = issue.fields.comment?.comments;
if (comments && comments.length > 0) {
output += '\n\nComments:';
comments.forEach(comment => {
if (comment && comment.created && comment.author?.displayName && comment.body) {
output += `\n\n[${formatDate(comment.created)} by ${
comment.author.displayName
}]\n${comment.body}`;
}
});
}
return output;
}
/**
* Format a list of Jira issues for display
*/
export function formatIssueList(
issues: JiraIssue[],
projectKey: string,
storyPointsField: string | null = null
): string {
if (issues.length === 0) {
return 'No issues found.';
}
const formattedIssues = issues.map(issue => formatIssue(issue, storyPointsField)).join('\n\n');
return `Latest Jira Issues in ${projectKey} Project:\n\n${formattedIssues}\n\nTotal Issues: ${issues.length}`;
}
/**
* Format a created issue response
*/
export function formatCreatedIssue(issue: any, domain: string): string {
return `Issue created successfully:
- Key: ${issue.key}
- URL: https://${domain}.atlassian.net/browse/${issue.key}`;
}