/**
* Get component progress with issue counts and work distribution
*/
import { withJiraContext } from '../utils/tool-wrapper.js';
import type { SessionState } from '../session-manager.js';
interface GetComponentProgressArgs {
working_dir: string;
instance?: string;
projectKey?: string;
componentId: string;
}
export async function handleGetComponentProgress(
args: GetComponentProgressArgs,
session?: SessionState
) {
return withJiraContext(
args,
{ requiresProject: false },
async (toolArgs, { axiosInstance, projectKey: resolvedProjectKey }) => {
const projectKey = toolArgs.projectKey || resolvedProjectKey;
if (!projectKey) {
throw new Error('projectKey is required for getting component progress');
}
try {
// Get component details
const componentResponse = await axiosInstance.get(`/component/${toolArgs.componentId}`);
const component = componentResponse.data;
// Issue counts available via API if needed in future
// const issueCountsResponse = await axiosInstance.get(
// `/component/${toolArgs.componentId}/relatedIssueCounts`
// );
// Get detailed issue breakdown by searching for issues in this component
const searchResponse = await axiosInstance.post(`/search/jql`, {
jql: `project = "${projectKey}" AND component = "${component.name}"`,
fields: ['status', 'priority', 'assignee', 'summary', 'issuetype', 'created', 'updated'],
maxResults: 1000,
});
const issues = searchResponse.data.issues;
// Calculate status breakdown
const statusBreakdown: { [key: string]: number } = {};
const priorityBreakdown: { [key: string]: number } = {};
const assigneeBreakdown: { [key: string]: number } = {};
const issueTypeBreakdown: { [key: string]: number } = {};
let totalIssues = issues.length;
let completedIssues = 0;
issues.forEach((issue: any) => {
const status = issue.fields.status.name;
const priority = issue.fields.priority?.name || 'No Priority';
const assignee = issue.fields.assignee?.displayName || 'Unassigned';
const issueType = issue.fields.issuetype.name;
statusBreakdown[status] = (statusBreakdown[status] || 0) + 1;
priorityBreakdown[priority] = (priorityBreakdown[priority] || 0) + 1;
assigneeBreakdown[assignee] = (assigneeBreakdown[assignee] || 0) + 1;
issueTypeBreakdown[issueType] = (issueTypeBreakdown[issueType] || 0) + 1;
// Count completed issues (adjust status names as needed for your workflow)
if (
status.toLowerCase().includes('done') ||
status.toLowerCase().includes('closed') ||
status.toLowerCase().includes('resolved')
) {
completedIssues++;
}
});
// Calculate progress percentage
const progressPercentage =
totalIssues > 0 ? Math.round((completedIssues / totalIssues) * 100) : 0;
// Get recent activity (last 30 days)
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const recentIssues = issues.filter(
(issue: any) => new Date(issue.fields.updated) > thirtyDaysAgo
);
return {
content: [
{
type: 'text',
text: `# Component Progress Report: ${component.name}
## π§ Overview
- **Component ID**: ${component.id}
- **Project**: ${projectKey}
- **Description**: ${component.description || 'No description'}
- **Component Lead**: ${component.lead?.displayName || 'No lead assigned'}
- **Progress**: ${progressPercentage}% complete
## π Issue Metrics
- **Total Issues**: ${totalIssues}
- **Completed Issues**: ${completedIssues}
- **Active Issues**: ${totalIssues - completedIssues}
- **Progress Bar**: ${'β'.repeat(Math.floor(progressPercentage / 10))}${'β'.repeat(10 - Math.floor(progressPercentage / 10))} ${progressPercentage}%
## π Activity (Last 30 Days)
- **Recently Updated Issues**: ${recentIssues.length}
- **Activity Level**: ${recentIssues.length > totalIssues * 0.3 ? 'π₯ High' : recentIssues.length > totalIssues * 0.1 ? 'π Moderate' : 'π Low'}
## π Status Breakdown
${Object.entries(statusBreakdown)
.sort(([, a], [, b]) => (b as number) - (a as number))
.map(([status, count]) => `- **${status}**: ${count} issues`)
.join('\n')}
## π― Priority Breakdown
${Object.entries(priorityBreakdown)
.sort(([, a], [, b]) => (b as number) - (a as number))
.map(([priority, count]) => `- **${priority}**: ${count} issues`)
.join('\n')}
## π Issue Type Breakdown
${Object.entries(issueTypeBreakdown)
.sort(([, a], [, b]) => (b as number) - (a as number))
.map(([type, count]) => `- **${type}**: ${count} issues`)
.join('\n')}
## π₯ Assignee Distribution
${Object.entries(assigneeBreakdown)
.sort(([, a], [, b]) => (b as number) - (a as number))
.slice(0, 10)
.map(([assignee, count]) => `- **${assignee}**: ${count} issues`)
.join('\n')}${Object.keys(assigneeBreakdown).length > 10 ? '\n... and more' : ''}
## π‘ Insights
${
progressPercentage >= 80
? 'π Great progress! Component is nearly complete.'
: progressPercentage >= 50
? 'π Good progress! Keep up the momentum.'
: progressPercentage >= 25
? 'π Getting started! Focus on completing active work.'
: 'π© Early stage. Consider breaking down work into smaller tasks.'
}
${recentIssues.length === 0 ? 'β οΈ No recent activity. Component may need attention.' : ''}`,
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error getting component progress: ${error.response?.data?.errorMessages?.join(', ') || error.message}`,
},
],
isError: true,
};
}
},
session
);
}