/** * Service pour les filtres avancés Jira * Gère le filtrage par composant, version, type de ticket, etc. */ import { JiraTask, JiraAnalytics, JiraAnalyticsFilters, AvailableFilters, FilterOption } from '@/lib/types'; export class JiraAdvancedFiltersService { /** * Extrait toutes les options de filtrage disponibles depuis les données */ static extractAvailableFilters(issues: JiraTask[]): AvailableFilters { const componentCounts = new Map(); const fixVersionCounts = new Map(); const issueTypeCounts = new Map(); const statusCounts = new Map(); const assigneeCounts = new Map(); const labelCounts = new Map(); const priorityCounts = new Map(); issues.forEach(issue => { // Components if (issue.components) { issue.components.forEach(component => { componentCounts.set(component.name, (componentCounts.get(component.name) || 0) + 1); }); } // Fix Versions if (issue.fixVersions) { issue.fixVersions.forEach(version => { fixVersionCounts.set(version.name, (fixVersionCounts.get(version.name) || 0) + 1); }); } // Issue Types issueTypeCounts.set(issue.issuetype.name, (issueTypeCounts.get(issue.issuetype.name) || 0) + 1); // Statuses statusCounts.set(issue.status.name, (statusCounts.get(issue.status.name) || 0) + 1); // Assignees const assigneeName = issue.assignee?.displayName || 'Non assigné'; assigneeCounts.set(assigneeName, (assigneeCounts.get(assigneeName) || 0) + 1); // Labels issue.labels.forEach(label => { labelCounts.set(label, (labelCounts.get(label) || 0) + 1); }); // Priorities if (issue.priority) { priorityCounts.set(issue.priority.name, (priorityCounts.get(issue.priority.name) || 0) + 1); } }); return { components: this.mapToFilterOptions(componentCounts), fixVersions: this.mapToFilterOptions(fixVersionCounts), issueTypes: this.mapToFilterOptions(issueTypeCounts), statuses: this.mapToFilterOptions(statusCounts), assignees: this.mapToFilterOptions(assigneeCounts), labels: this.mapToFilterOptions(labelCounts), priorities: this.mapToFilterOptions(priorityCounts) }; } /** * Applique les filtres aux données analytics */ static applyFiltersToAnalytics(analytics: JiraAnalytics, filters: Partial, allIssues: JiraTask[]): JiraAnalytics { // Filtrer les issues d'abord const filteredIssues = this.filterIssues(allIssues, filters); // Recalculer les métriques avec les issues filtrées return this.recalculateAnalytics(analytics, filteredIssues); } /** * Filtre la liste des issues selon les critères */ static filterIssues(issues: JiraTask[], filters: Partial): JiraTask[] { return issues.filter(issue => { // Filtrage par composants if (filters.components && filters.components.length > 0) { const issueComponents = issue.components?.map(c => c.name) || []; if (!filters.components.some(comp => issueComponents.includes(comp))) { return false; } } // Filtrage par versions if (filters.fixVersions && filters.fixVersions.length > 0) { const issueVersions = issue.fixVersions?.map(v => v.name) || []; if (!filters.fixVersions.some(version => issueVersions.includes(version))) { return false; } } // Filtrage par types if (filters.issueTypes && filters.issueTypes.length > 0) { if (!filters.issueTypes.includes(issue.issuetype.name)) { return false; } } // Filtrage par statuts if (filters.statuses && filters.statuses.length > 0) { if (!filters.statuses.includes(issue.status.name)) { return false; } } // Filtrage par assignees if (filters.assignees && filters.assignees.length > 0) { const assigneeName = issue.assignee?.displayName || 'Non assigné'; if (!filters.assignees.includes(assigneeName)) { return false; } } // Filtrage par labels if (filters.labels && filters.labels.length > 0) { if (!filters.labels.some(label => issue.labels.includes(label))) { return false; } } // Filtrage par priorités if (filters.priorities && filters.priorities.length > 0) { const priorityName = issue.priority?.name; if (!priorityName || !filters.priorities.includes(priorityName)) { return false; } } // Filtrage par date if (filters.dateRange) { const issueDate = new Date(issue.created); if (issueDate < filters.dateRange.from || issueDate > filters.dateRange.to) { return false; } } return true; }); } /** * Recalcule les analytics avec un subset d'issues filtrées */ private static recalculateAnalytics(originalAnalytics: JiraAnalytics, filteredIssues: JiraTask[]): JiraAnalytics { // Pour une implémentation complète, il faudrait recalculer toutes les métriques // Ici on fait une version simplifiée qui garde la structure mais met à jour les counts const totalFilteredIssues = filteredIssues.length; // Calculer la nouvelle distribution par assignee const assigneeMap = new Map(); filteredIssues.forEach(issue => { const assigneeName = issue.assignee?.displayName || 'Non assigné'; const current = assigneeMap.get(assigneeName) || { completed: 0, inProgress: 0, total: 0 }; current.total++; if (issue.status.category === 'Done') { current.completed++; } else if (issue.status.category === 'In Progress') { current.inProgress++; } assigneeMap.set(assigneeName, current); }); const newIssuesDistribution = Array.from(assigneeMap.entries()).map(([assignee, stats]) => ({ assignee: assignee === 'Non assigné' ? '' : assignee, displayName: assignee, totalIssues: stats.total, completedIssues: stats.completed, inProgressIssues: stats.inProgress, percentage: totalFilteredIssues > 0 ? (stats.total / totalFilteredIssues) * 100 : 0 })); // Calculer la nouvelle distribution par statut const statusMap = new Map(); filteredIssues.forEach(issue => { statusMap.set(issue.status.name, (statusMap.get(issue.status.name) || 0) + 1); }); const newStatusDistribution = Array.from(statusMap.entries()).map(([status, count]) => ({ status, count, percentage: totalFilteredIssues > 0 ? (count / totalFilteredIssues) * 100 : 0 })); // Calculer la nouvelle charge par assignee const newAssigneeWorkload = Array.from(assigneeMap.entries()).map(([assignee, stats]) => ({ assignee: assignee === 'Non assigné' ? '' : assignee, displayName: assignee, todoCount: stats.total - stats.completed - stats.inProgress, inProgressCount: stats.inProgress, reviewCount: 0, // Simplified totalActive: stats.total - stats.completed })); return { ...originalAnalytics, project: { ...originalAnalytics.project, totalIssues: totalFilteredIssues }, teamMetrics: { ...originalAnalytics.teamMetrics, issuesDistribution: newIssuesDistribution }, workInProgress: { byStatus: newStatusDistribution, byAssignee: newAssigneeWorkload } }; } /** * Convertit une Map de counts en options de filtre triées */ private static mapToFilterOptions(countMap: Map): FilterOption[] { return Array.from(countMap.entries()) .map(([value, count]) => ({ value, label: value, count })) .sort((a, b) => b.count - a.count); // Trier par count décroissant } /** * Crée un filtre vide */ static createEmptyFilters(): JiraAnalyticsFilters { return { components: [], fixVersions: [], issueTypes: [], statuses: [], assignees: [], labels: [], priorities: [] }; } /** * Vérifie si des filtres sont actifs */ static hasActiveFilters(filters: Partial): boolean { return !!( filters.components?.length || filters.fixVersions?.length || filters.issueTypes?.length || filters.statuses?.length || filters.assignees?.length || filters.labels?.length || filters.priorities?.length || filters.dateRange ); } /** * Compte le nombre total de filtres actifs */ static countActiveFilters(filters: Partial): number { let count = 0; if (filters.components?.length) count += filters.components.length; if (filters.fixVersions?.length) count += filters.fixVersions.length; if (filters.issueTypes?.length) count += filters.issueTypes.length; if (filters.statuses?.length) count += filters.statuses.length; if (filters.assignees?.length) count += filters.assignees.length; if (filters.labels?.length) count += filters.labels.length; if (filters.priorities?.length) count += filters.priorities.length; if (filters.dateRange) count += 1; return count; } /** * Génère un résumé textuel des filtres actifs */ static getFiltersSummary(filters: Partial): string { const parts: string[] = []; if (filters.components?.length) { parts.push(`${filters.components.length} composant${filters.components.length > 1 ? 's' : ''}`); } if (filters.fixVersions?.length) { parts.push(`${filters.fixVersions.length} version${filters.fixVersions.length > 1 ? 's' : ''}`); } if (filters.issueTypes?.length) { parts.push(`${filters.issueTypes.length} type${filters.issueTypes.length > 1 ? 's' : ''}`); } if (filters.statuses?.length) { parts.push(`${filters.statuses.length} statut${filters.statuses.length > 1 ? 's' : ''}`); } if (filters.assignees?.length) { parts.push(`${filters.assignees.length} assigné${filters.assignees.length > 1 ? 's' : ''}`); } if (filters.labels?.length) { parts.push(`${filters.labels.length} label${filters.labels.length > 1 ? 's' : ''}`); } if (filters.priorities?.length) { parts.push(`${filters.priorities.length} priorité${filters.priorities.length > 1 ? 's' : ''}`); } if (filters.dateRange) { parts.push('période personnalisée'); } if (parts.length === 0) return 'Aucun filtre actif'; if (parts.length === 1) return `Filtré par ${parts[0]}`; if (parts.length === 2) return `Filtré par ${parts[0]} et ${parts[1]}`; return `Filtré par ${parts.slice(0, -1).join(', ')} et ${parts[parts.length - 1]}`; } }