Files
towercontrol/services/jira-summary.ts
Julien Froidefond fded7d0078 feat: add weekly summary features and components
- Introduced `CategoryBreakdown`, `JiraWeeklyMetrics`, `PeriodSelector`, and `VelocityMetrics` components to enhance the weekly summary dashboard.
- Updated `WeeklySummaryClient` to manage period selection and PDF export functionality.
- Enhanced `WeeklySummaryService` to support period comparisons and activity categorization.
- Added new API route for fetching weekly summary data based on selected period.
- Updated `package.json` and `package-lock.json` to include `jspdf` and related types for PDF generation.
- Marked several tasks as complete in `TODO.md` to reflect progress on summary features.
2025-09-19 12:28:11 +02:00

181 lines
6.0 KiB
TypeScript

import type { JiraConfig } from './jira';
import { Task } from '@/lib/types';
export interface JiraWeeklyMetrics {
totalJiraTasks: number;
completedJiraTasks: number;
totalStoryPoints: number; // Estimation basée sur le type de ticket
projectsContributed: string[];
ticketTypes: { [type: string]: number };
jiraLinks: Array<{
key: string;
title: string;
status: string;
type: string;
url: string;
estimatedPoints: number;
}>;
}
export class JiraSummaryService {
/**
* Enrichit les tâches hebdomadaires avec des métriques Jira
*/
static async getJiraWeeklyMetrics(
weeklyTasks: Task[],
jiraConfig?: JiraConfig
): Promise<JiraWeeklyMetrics | null> {
if (!jiraConfig?.baseUrl || !jiraConfig?.email || !jiraConfig?.apiToken) {
return null;
}
const jiraTasks = weeklyTasks.filter(task =>
task.source === 'jira' && task.jiraKey && task.jiraProject
);
if (jiraTasks.length === 0) {
return {
totalJiraTasks: 0,
completedJiraTasks: 0,
totalStoryPoints: 0,
projectsContributed: [],
ticketTypes: {},
jiraLinks: []
};
}
// Calculer les métriques basiques
const completedJiraTasks = jiraTasks.filter(task => task.status === 'done');
const projectsContributed = [...new Set(jiraTasks.map(task => task.jiraProject).filter((project): project is string => Boolean(project)))];
// Analyser les types de tickets
const ticketTypes: { [type: string]: number } = {};
jiraTasks.forEach(task => {
const type = task.jiraType || 'Unknown';
ticketTypes[type] = (ticketTypes[type] || 0) + 1;
});
// Estimer les story points basés sur le type de ticket
const estimateStoryPoints = (type: string): number => {
const typeMapping: { [key: string]: number } = {
'Story': 3,
'Task': 2,
'Bug': 1,
'Epic': 8,
'Sub-task': 1,
'Improvement': 2,
'New Feature': 5,
'défaut': 1, // French
'amélioration': 2, // French
'nouvelle fonctionnalité': 5, // French
};
return typeMapping[type] || typeMapping[type?.toLowerCase()] || 2; // Défaut: 2 points
};
const totalStoryPoints = jiraTasks.reduce((sum, task) => {
return sum + estimateStoryPoints(task.jiraType || '');
}, 0);
// Créer les liens Jira
const jiraLinks = jiraTasks.map(task => ({
key: task.jiraKey || '',
title: task.title,
status: task.status,
type: task.jiraType || 'Unknown',
url: `${jiraConfig.baseUrl.replace('/rest/api/3', '')}/browse/${task.jiraKey}`,
estimatedPoints: estimateStoryPoints(task.jiraType || '')
}));
return {
totalJiraTasks: jiraTasks.length,
completedJiraTasks: completedJiraTasks.length,
totalStoryPoints,
projectsContributed,
ticketTypes,
jiraLinks
};
}
/**
* Récupère la configuration Jira depuis les préférences utilisateur
*/
static async getJiraConfig(): Promise<JiraConfig | null> {
try {
// Import dynamique pour éviter les cycles de dépendance
const { userPreferencesService } = await import('./user-preferences');
const preferences = await userPreferencesService.getAllPreferences();
if (!preferences.jiraConfig?.baseUrl ||
!preferences.jiraConfig?.email ||
!preferences.jiraConfig?.apiToken) {
return null;
}
return {
baseUrl: preferences.jiraConfig.baseUrl,
email: preferences.jiraConfig.email,
apiToken: preferences.jiraConfig.apiToken,
projectKey: preferences.jiraConfig.projectKey,
ignoredProjects: preferences.jiraConfig.ignoredProjects
};
} catch (error) {
console.error('Erreur lors de la récupération de la config Jira:', error);
return null;
}
}
/**
* Génère des insights business basés sur les métriques Jira
*/
static generateBusinessInsights(jiraMetrics: JiraWeeklyMetrics): string[] {
const insights: string[] = [];
if (jiraMetrics.totalJiraTasks === 0) {
insights.push("Aucune tâche Jira cette semaine. Concentré sur des tâches internes ?");
return insights;
}
// Insights sur la completion
const completionRate = (jiraMetrics.completedJiraTasks / jiraMetrics.totalJiraTasks) * 100;
if (completionRate >= 80) {
insights.push(`🎯 Excellent taux de completion Jira: ${completionRate.toFixed(0)}%`);
} else if (completionRate < 50) {
insights.push(`⚠️ Taux de completion Jira faible: ${completionRate.toFixed(0)}%. Revoir les estimations ?`);
}
// Insights sur les story points
if (jiraMetrics.totalStoryPoints > 0) {
insights.push(`📊 Estimation: ${jiraMetrics.totalStoryPoints} story points traités cette semaine`);
const avgPointsPerTask = jiraMetrics.totalStoryPoints / jiraMetrics.totalJiraTasks;
if (avgPointsPerTask > 4) {
insights.push(`🏋️ Travail sur des tâches complexes (${avgPointsPerTask.toFixed(1)} pts/tâche en moyenne)`);
}
}
// Insights sur les projets
if (jiraMetrics.projectsContributed.length > 1) {
insights.push(`🤝 Contribution multi-projets: ${jiraMetrics.projectsContributed.join(', ')}`);
} else if (jiraMetrics.projectsContributed.length === 1) {
insights.push(`🎯 Focus sur le projet ${jiraMetrics.projectsContributed[0]}`);
}
// Insights sur les types de tickets
const bugCount = jiraMetrics.ticketTypes['Bug'] || jiraMetrics.ticketTypes['défaut'] || 0;
const totalTickets = Object.values(jiraMetrics.ticketTypes).reduce((sum, count) => sum + count, 0);
if (bugCount > 0) {
const bugRatio = (bugCount / totalTickets) * 100;
if (bugRatio > 50) {
insights.push(`🐛 Semaine focalisée sur la correction de bugs (${bugRatio.toFixed(0)}%)`);
} else if (bugRatio < 20) {
insights.push(`✨ Semaine productive avec peu de bugs (${bugRatio.toFixed(0)}%)`);
}
}
return insights;
}
}