feat: enhance Jira filters and dashboard functionality

- Added new test scripts in `package.json` for story points and Jira fields validation.
- Updated `JiraDashboardPageClient` to utilize raw analytics for filtering, improving data handling with active filters.
- Introduced a loading state in `FilterBar` with visual feedback for filter application, enhancing user experience.
- Refactored `useJiraFilters` to support local filtering based on initial analytics, streamlining filter management.
- Enhanced `JiraAnalyticsService` to calculate story points based on issue types, improving accuracy in analytics.
This commit is contained in:
Julien Froidefond
2025-09-26 11:54:41 +02:00
parent bd7ede412e
commit 7de060566f
8 changed files with 448 additions and 54 deletions

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env tsx
/**
* Script pour identifier les champs personnalisés disponibles dans Jira
* Usage: npm run test:jira-fields
*/
import { JiraService } from '../src/services/integrations/jira/jira';
import { userPreferencesService } from '../src/services/core/user-preferences';
async function testJiraFields() {
console.log('🔍 Identification des champs personnalisés Jira\n');
try {
// Récupérer la config Jira
const jiraConfig = await userPreferencesService.getJiraConfig();
if (!jiraConfig.enabled || !jiraConfig.baseUrl || !jiraConfig.email || !jiraConfig.apiToken) {
console.log('❌ Configuration Jira manquante');
return;
}
if (!jiraConfig.projectKey) {
console.log('❌ Aucun projet configuré');
return;
}
console.log(`📋 Analyse du projet: ${jiraConfig.projectKey}`);
// Créer le service Jira
const jiraService = new JiraService(jiraConfig);
// Récupérer un seul ticket pour analyser tous ses champs
const jql = `project = "${jiraConfig.projectKey}" ORDER BY updated DESC`;
const issues = await jiraService.searchIssues(jql);
if (issues.length === 0) {
console.log('❌ Aucun ticket trouvé');
return;
}
const firstIssue = issues[0];
console.log(`\n📄 Analyse du ticket: ${firstIssue.key}`);
console.log(`Titre: ${firstIssue.summary}`);
console.log(`Type: ${firstIssue.issuetype.name}`);
// Afficher les story points actuels
console.log(`\n🎯 Story Points actuels: ${firstIssue.storyPoints || 'Non défini'}`);
console.log('\n💡 Pour identifier le bon champ story points:');
console.log('1. Connectez-vous à votre instance Jira');
console.log('2. Allez dans Administration > Projets > [Votre projet]');
console.log('3. Regardez dans "Champs" ou "Story Points"');
console.log('4. Notez le nom du champ personnalisé (ex: customfield_10003)');
console.log('5. Modifiez le code dans src/services/integrations/jira/jira.ts ligne 167');
console.log('\n🔧 Champs couramment utilisés pour les story points:');
console.log('• customfield_10002 (par défaut)');
console.log('• customfield_10003');
console.log('• customfield_10004');
console.log('• customfield_10005');
console.log('• customfield_10006');
console.log('• customfield_10007');
console.log('• customfield_10008');
console.log('• customfield_10009');
console.log('• customfield_10010');
console.log('\n📝 Alternative: Utiliser les estimations par type');
console.log('Le système utilise déjà des estimations intelligentes:');
console.log('• Epic: 13 points');
console.log('• Story: 5 points');
console.log('• Task: 3 points');
console.log('• Bug: 2 points');
console.log('• Subtask: 1 point');
} catch (error) {
console.error('❌ Erreur lors du test:', error);
}
}
// Exécution du script
testJiraFields().catch(console.error);

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env tsx
/**
* Script de test pour vérifier la récupération des story points Jira
* Usage: npm run test:story-points
*/
import { JiraService } from '../src/services/integrations/jira/jira';
import { userPreferencesService } from '../src/services/core/user-preferences';
async function testStoryPoints() {
console.log('🧪 Test de récupération des story points Jira\n');
try {
// Récupérer la config Jira
const jiraConfig = await userPreferencesService.getJiraConfig();
if (!jiraConfig.enabled || !jiraConfig.baseUrl || !jiraConfig.email || !jiraConfig.apiToken) {
console.log('❌ Configuration Jira manquante');
return;
}
if (!jiraConfig.projectKey) {
console.log('❌ Aucun projet configuré');
return;
}
console.log(`📋 Test sur le projet: ${jiraConfig.projectKey}`);
// Créer le service Jira
const jiraService = new JiraService(jiraConfig);
// Récupérer quelques tickets pour tester
const jql = `project = "${jiraConfig.projectKey}" ORDER BY updated DESC`;
const issues = await jiraService.searchIssues(jql);
console.log(`\n📊 Analyse de ${issues.length} tickets:\n`);
let totalStoryPoints = 0;
let ticketsWithStoryPoints = 0;
let ticketsWithoutStoryPoints = 0;
const storyPointsDistribution: Record<number, number> = {};
const typeDistribution: Record<string, { count: number; totalPoints: number }> = {};
issues.slice(0, 20).forEach((issue, index) => {
const storyPoints = issue.storyPoints || 0;
const issueType = issue.issuetype.name;
console.log(`${index + 1}. ${issue.key} (${issueType})`);
console.log(` Titre: ${issue.summary.substring(0, 50)}...`);
console.log(` Story Points: ${storyPoints > 0 ? storyPoints : 'Non défini'}`);
console.log(` Statut: ${issue.status.name}`);
console.log('');
if (storyPoints > 0) {
ticketsWithStoryPoints++;
totalStoryPoints += storyPoints;
storyPointsDistribution[storyPoints] = (storyPointsDistribution[storyPoints] || 0) + 1;
} else {
ticketsWithoutStoryPoints++;
}
// Distribution par type
if (!typeDistribution[issueType]) {
typeDistribution[issueType] = { count: 0, totalPoints: 0 };
}
typeDistribution[issueType].count++;
typeDistribution[issueType].totalPoints += storyPoints;
});
console.log('📈 === RÉSUMÉ ===\n');
console.log(`Total tickets analysés: ${issues.length}`);
console.log(`Tickets avec story points: ${ticketsWithStoryPoints}`);
console.log(`Tickets sans story points: ${ticketsWithoutStoryPoints}`);
console.log(`Total story points: ${totalStoryPoints}`);
console.log(`Moyenne par ticket: ${issues.length > 0 ? (totalStoryPoints / issues.length).toFixed(2) : 0}`);
console.log('\n📊 Distribution des story points:');
Object.entries(storyPointsDistribution)
.sort(([a], [b]) => parseInt(a) - parseInt(b))
.forEach(([points, count]) => {
console.log(` ${points} points: ${count} tickets`);
});
console.log('\n🏷 Distribution par type:');
Object.entries(typeDistribution)
.sort(([,a], [,b]) => b.count - a.count)
.forEach(([type, stats]) => {
const avgPoints = stats.count > 0 ? (stats.totalPoints / stats.count).toFixed(2) : '0';
console.log(` ${type}: ${stats.count} tickets, ${stats.totalPoints} points total, ${avgPoints} points moyen`);
});
if (ticketsWithoutStoryPoints > 0) {
console.log('\n⚠ Recommandations:');
console.log('• Vérifiez que le champ "Story Points" est configuré dans votre projet Jira');
console.log('• Le champ par défaut est "customfield_10002"');
console.log('• Si votre projet utilise un autre champ, modifiez le code dans jira.ts');
console.log('• En attendant, le système utilise des estimations basées sur le type de ticket');
}
} catch (error) {
console.error('❌ Erreur lors du test:', error);
}
}
// Exécution du script
testStoryPoints().catch(console.error);