chore: prettier everywhere

This commit is contained in:
Julien Froidefond
2025-10-09 13:40:03 +02:00
parent f8100ae3e9
commit d9cf9a2655
303 changed files with 15420 additions and 9391 deletions

View File

@@ -4,7 +4,10 @@
* Usage: tsx scripts/backup-manager.ts [command] [options]
*/
import { backupService, BackupConfig } from '../src/services/data-management/backup';
import {
backupService,
BackupConfig,
} from '../src/services/data-management/backup';
import { backupScheduler } from '../src/services/data-management/backup-scheduler';
import { formatDateForDisplay } from '../src/lib/date-utils';
@@ -57,7 +60,7 @@ OPTIONS:
for (let i = 1; i < args.length; i++) {
const arg = args[i];
if (arg === '--force') {
options.force = true;
} else if (arg === '--help') {
@@ -70,9 +73,12 @@ OPTIONS:
return options;
}
private async confirmAction(message: string, force?: boolean): Promise<boolean> {
private async confirmAction(
message: string,
force?: boolean
): Promise<boolean> {
if (force) return true;
// Simulation d'une confirmation (en CLI réel, utiliser readline)
console.log(`⚠️ ${message}`);
console.log('✅ Action confirmée (--force activé ou mode auto)');
@@ -83,12 +89,12 @@ OPTIONS:
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
@@ -170,15 +176,19 @@ OPTIONS:
}
private async createBackup(force: boolean = false): Promise<void> {
console.log('🔄 Création d\'une sauvegarde...');
console.log("🔄 Création d'une sauvegarde...");
const result = await backupService.createBackup('manual', force);
if (result === null) {
console.log('⏭️ Sauvegarde sautée: Aucun changement détecté depuis la dernière sauvegarde');
console.log(' 💡 Utilisez --force pour créer une sauvegarde malgré tout');
console.log(
'⏭️ Sauvegarde sautée: Aucun changement détecté depuis la dernière sauvegarde'
);
console.log(
' 💡 Utilisez --force pour créer une sauvegarde malgré tout'
);
return;
}
if (result.status === 'success') {
console.log(`✅ Sauvegarde créée: ${result.filename}`);
console.log(` Taille: ${this.formatFileSize(result.size)}`);
@@ -194,24 +204,28 @@ OPTIONS:
private async listBackups(): Promise<void> {
console.log('📋 Liste des sauvegardes:\n');
const backups = await backupService.listBackups();
if (backups.length === 0) {
console.log(' Aucune sauvegarde disponible');
return;
}
console.log(`${'Nom'.padEnd(40)} ${'Taille'.padEnd(10)} ${'Type'.padEnd(12)} ${'Date'}`);
console.log(
`${'Nom'.padEnd(40)} ${'Taille'.padEnd(10)} ${'Type'.padEnd(12)} ${'Date'}`
);
console.log('─'.repeat(80));
for (const backup of backups) {
const name = backup.filename.padEnd(40);
const size = this.formatFileSize(backup.size).padEnd(10);
const type = (backup.type === 'manual' ? 'Manuelle' : 'Automatique').padEnd(12);
const type = (
backup.type === 'manual' ? 'Manuelle' : 'Automatique'
).padEnd(12);
const date = this.formatDate(backup.createdAt);
console.log(`${name} ${size} ${type} ${date}`);
}
console.log(`\n📊 Total: ${backups.length} sauvegarde(s)`);
}
@@ -220,7 +234,7 @@ OPTIONS:
`Supprimer la sauvegarde "${filename}" ?`,
force
);
if (!confirmed) {
console.log('❌ Suppression annulée');
return;
@@ -230,12 +244,15 @@ OPTIONS:
console.log(`✅ Sauvegarde supprimée: ${filename}`);
}
private async restoreBackup(filename: string, force?: boolean): Promise<void> {
private async restoreBackup(
filename: string,
force?: boolean
): Promise<void> {
const confirmed = await this.confirmAction(
`Restaurer la base de données depuis "${filename}" ? ATTENTION: Cela remplacera toutes les données actuelles !`,
force
);
if (!confirmed) {
console.log('❌ Restauration annulée');
return;
@@ -247,7 +264,7 @@ OPTIONS:
}
private async verifyDatabase(): Promise<void> {
console.log('🔍 Vérification de l\'intégrité de la base...');
console.log("🔍 Vérification de l'intégrité de la base...");
await backupService.verifyDatabaseHealth();
console.log('✅ Base de données vérifiée avec succès');
}
@@ -255,21 +272,29 @@ OPTIONS:
private async showConfig(): Promise<void> {
const config = backupService.getConfig();
const status = backupScheduler.getStatus();
console.log('⚙️ Configuration des sauvegardes:\n');
console.log(` Activé: ${config.enabled ? '✅ Oui' : '❌ Non'}`);
console.log(
` Activé: ${config.enabled ? '✅ Oui' : '❌ Non'}`
);
console.log(` Fréquence: ${config.interval}`);
console.log(` Max sauvegardes: ${config.maxBackups}`);
console.log(` Compression: ${config.compression ? '✅ Oui' : '❌ Non'}`);
console.log(
` Compression: ${config.compression ? '✅ Oui' : '❌ Non'}`
);
console.log(` Chemin: ${config.backupPath}`);
console.log(`\n📊 Statut du planificateur:`);
console.log(` En cours: ${status.isRunning ? '✅ Oui' : '❌ Non'}`);
console.log(` Prochaine: ${status.nextBackup ? this.formatDate(status.nextBackup) : 'Non planifiée'}`);
console.log(
` En cours: ${status.isRunning ? '✅ Oui' : 'Non'}`
);
console.log(
` Prochaine: ${status.nextBackup ? this.formatDate(status.nextBackup) : 'Non planifiée'}`
);
}
private async setConfig(configString: string): Promise<void> {
const [key, value] = configString.split('=');
if (!key || !value) {
console.error('❌ Format invalide. Utilisez: key=value');
process.exit(1);
@@ -283,7 +308,9 @@ OPTIONS:
break;
case 'interval':
if (!['hourly', 'daily', 'weekly'].includes(value)) {
console.error('❌ Interval invalide. Utilisez: hourly, daily, ou weekly');
console.error(
'❌ Interval invalide. Utilisez: hourly, daily, ou weekly'
);
process.exit(1);
}
newConfig.interval = value as BackupConfig['interval'];
@@ -306,7 +333,7 @@ OPTIONS:
backupService.updateConfig(newConfig);
console.log(`✅ Configuration mise à jour: ${key} = ${value}`);
// Redémarrer le scheduler si nécessaire
if (key === 'enabled' || key === 'interval') {
backupScheduler.restart();
@@ -326,12 +353,18 @@ OPTIONS:
private async schedulerStatus(): Promise<void> {
const status = backupScheduler.getStatus();
console.log('📊 Statut du planificateur:\n');
console.log(` État: ${status.isRunning ? '✅ Actif' : '❌ Arrêté'}`);
console.log(` Activé: ${status.isEnabled ? '✅ Oui' : '❌ Non'}`);
console.log(
` État: ${status.isRunning ? '✅ Actif' : '❌ Arrêté'}`
);
console.log(
` Activé: ${status.isEnabled ? '✅ Oui' : '❌ Non'}`
);
console.log(` Fréquence: ${status.interval}`);
console.log(` Prochaine: ${status.nextBackup ? this.formatDate(status.nextBackup) : 'Non planifiée'}`);
console.log(
` Prochaine: ${status.nextBackup ? this.formatDate(status.nextBackup) : 'Non planifiée'}`
);
console.log(` Max sauvegardes: ${status.maxBackups}`);
}
}
@@ -340,7 +373,7 @@ OPTIONS:
if (require.main === module) {
const cli = new BackupManagerCLI();
const args = process.argv.slice(2);
cli.run(args).catch((error) => {
console.error('❌ Erreur fatale:', error);
process.exit(1);

View File

@@ -10,18 +10,18 @@ import * as readline from 'readline';
function displayCacheStats() {
console.log('\n📊 === STATISTIQUES DU CACHE JIRA ANALYTICS ===');
const stats = jiraAnalyticsCache.getStats();
console.log(`\n📈 Total des entrées: ${stats.totalEntries}`);
if (stats.projects.length === 0) {
console.log('📭 Aucune donnée en cache');
return;
}
console.log('\n📋 Projets en cache:');
stats.projects.forEach(project => {
stats.projects.forEach((project) => {
const status = project.isExpired ? '❌ EXPIRÉ' : '✅ VALIDE';
console.log(`${project.projectKey}:`);
console.log(` - Âge: ${project.age}`);
@@ -44,13 +44,13 @@ function displayCacheActions() {
async function monitorRealtime() {
console.log('\n👀 Surveillance en temps réel (Ctrl+C pour arrêter)...');
const interval = setInterval(() => {
console.clear();
displayCacheStats();
console.log('\n⏰ Mise à jour toutes les 5 secondes...');
}, 5000);
// Gérer l'arrêt propre
process.on('SIGINT', () => {
clearInterval(interval);
@@ -61,74 +61,77 @@ async function monitorRealtime() {
async function main() {
console.log('🚀 Cache Monitor Jira Analytics');
const args = process.argv.slice(2);
const command = args[0];
switch (command) {
case 'stats':
displayCacheStats();
break;
case 'cleanup':
console.log('\n🧹 Nettoyage forcé du cache...');
const cleaned = jiraAnalyticsCache.forceCleanup();
console.log(`${cleaned} entrées supprimées`);
break;
case 'clear':
console.log('\n🗑 Invalidation de tout le cache...');
jiraAnalyticsCache.invalidateAll();
console.log('✅ Cache vidé');
break;
case 'monitor':
await monitorRealtime();
break;
default:
displayCacheStats();
displayCacheActions();
// Interface interactive simple
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
});
const askAction = () => {
rl.question('\nChoisissez une action (1-5): ', async (answer: string) => {
switch (answer.trim()) {
case '1':
displayCacheStats();
askAction();
break;
case '2':
const cleaned = jiraAnalyticsCache.forceCleanup();
console.log(`${cleaned} entrées supprimées`);
askAction();
break;
case '3':
jiraAnalyticsCache.invalidateAll();
console.log('✅ Cache vidé');
askAction();
break;
case '4':
rl.close();
await monitorRealtime();
break;
case '5':
console.log('👋 Au revoir !');
rl.close();
process.exit(0);
break;
default:
console.log('❌ Action invalide');
askAction();
rl.question(
'\nChoisissez une action (1-5): ',
async (answer: string) => {
switch (answer.trim()) {
case '1':
displayCacheStats();
askAction();
break;
case '2':
const cleaned = jiraAnalyticsCache.forceCleanup();
console.log(`${cleaned} entrées supprimées`);
askAction();
break;
case '3':
jiraAnalyticsCache.invalidateAll();
console.log('✅ Cache vidé');
askAction();
break;
case '4':
rl.close();
await monitorRealtime();
break;
case '5':
console.log('👋 Au revoir !');
rl.close();
process.exit(0);
break;
default:
console.log('❌ Action invalide');
askAction();
}
}
});
);
};
askAction();
}
}

View File

@@ -10,9 +10,13 @@ async function resetDatabase() {
try {
// Compter les tâches avant suppression
const beforeCount = await prisma.task.count();
const manualCount = await prisma.task.count({ where: { source: 'manual' } });
const remindersCount = await prisma.task.count({ where: { source: 'reminders' } });
const manualCount = await prisma.task.count({
where: { source: 'manual' },
});
const remindersCount = await prisma.task.count({
where: { source: 'reminders' },
});
console.log(`📊 État actuel:`);
console.log(` Total: ${beforeCount} tâches`);
console.log(` Manuelles: ${manualCount} tâches`);
@@ -22,8 +26,8 @@ async function resetDatabase() {
// Supprimer toutes les tâches de synchronisation
const deletedTasks = await prisma.task.deleteMany({
where: {
source: 'reminders'
}
source: 'reminders',
},
});
console.log(`✅ Supprimé ${deletedTasks.count} tâches de synchronisation`);
@@ -38,11 +42,11 @@ async function resetDatabase() {
// Compter après nettoyage
const afterCount = await prisma.task.count();
console.log('');
console.log('🎉 Base de données nettoyée !');
console.log(`📊 Résultat: ${afterCount} tâches restantes`);
// Afficher les tâches restantes
if (afterCount > 0) {
console.log('');
@@ -51,30 +55,32 @@ async function resetDatabase() {
include: {
taskTags: {
include: {
tag: true
}
}
tag: true,
},
},
},
orderBy: { createdAt: 'desc' }
orderBy: { createdAt: 'desc' },
});
remainingTasks.forEach((task, index) => {
const statusEmoji = {
'todo': '⏳',
'in_progress': '🔄',
'done': '',
'cancelled': ''
}[task.status] || '';
const statusEmoji =
{
todo: '',
in_progress: '🔄',
done: '',
cancelled: '',
}[task.status] || '❓';
// Utiliser les relations TaskTag
const tags = task.taskTags ? task.taskTags.map(tt => tt.tag.name) : [];
const tags = task.taskTags
? task.taskTags.map((tt) => tt.tag.name)
: [];
const tagsStr = tags.length > 0 ? ` [${tags.join(', ')}]` : '';
console.log(` ${index + 1}. ${statusEmoji} ${task.title}${tagsStr}`);
});
}
} catch (error) {
console.error('❌ Erreur lors du reset:', error);
throw error;
@@ -83,14 +89,16 @@ async function resetDatabase() {
// Exécuter le script
if (require.main === module) {
resetDatabase().then(() => {
console.log('');
console.log('✨ Reset terminé avec succès !');
process.exit(0);
}).catch((error) => {
console.error('💥 Erreur fatale:', error);
process.exit(1);
});
resetDatabase()
.then(() => {
console.log('');
console.log('✨ Reset terminé avec succès !');
process.exit(0);
})
.catch((error) => {
console.error('💥 Erreur fatale:', error);
process.exit(1);
});
}
export { resetDatabase };

View File

@@ -11,19 +11,21 @@ async function seedTestData() {
const testTasks = [
{
title: '🎨 Design System Implementation',
description: 'Create and implement a comprehensive design system with reusable components',
description:
'Create and implement a comprehensive design system with reusable components',
status: 'in_progress' as TaskStatus,
priority: 'high' as TaskPriority,
tags: ['design', 'ui', 'frontend'],
dueDate: new Date('2025-12-31')
dueDate: new Date('2025-12-31'),
},
{
title: '🔧 API Performance Optimization',
description: 'Optimize API endpoints response time and implement pagination',
description:
'Optimize API endpoints response time and implement pagination',
status: 'todo' as TaskStatus,
priority: 'medium' as TaskPriority,
tags: ['backend', 'performance', 'api'],
dueDate: new Date('2025-12-15')
dueDate: new Date('2025-12-15'),
},
{
title: '✅ Test Coverage Improvement',
@@ -31,7 +33,7 @@ async function seedTestData() {
status: 'todo' as TaskStatus,
priority: 'medium' as TaskPriority,
tags: ['testing', 'quality'],
dueDate: new Date('2025-12-20')
dueDate: new Date('2025-12-20'),
},
{
title: '📱 Mobile Responsive Design',
@@ -39,7 +41,7 @@ async function seedTestData() {
status: 'todo' as TaskStatus,
priority: 'high' as TaskPriority,
tags: ['frontend', 'mobile', 'ui'],
dueDate: new Date('2025-12-10')
dueDate: new Date('2025-12-10'),
},
{
title: '🔒 Security Audit',
@@ -47,8 +49,8 @@ async function seedTestData() {
status: 'backlog' as TaskStatus,
priority: 'urgent' as TaskPriority,
tags: ['security', 'audit'],
dueDate: new Date('2026-01-15')
}
dueDate: new Date('2026-01-15'),
},
];
let createdCount = 0;
@@ -57,34 +59,39 @@ async function seedTestData() {
for (const taskData of testTasks) {
try {
const task = await tasksService.createTask(taskData);
const statusEmoji = {
'backlog': '📋',
'todo': '⏳',
'in_progress': '🔄',
'freeze': '🧊',
'done': '✅',
'cancelled': '❌',
'archived': '📦'
backlog: '📋',
todo: '⏳',
in_progress: '🔄',
freeze: '🧊',
done: '✅',
cancelled: '❌',
archived: '📦',
}[task.status];
const priorityEmoji = {
'low': '🔵',
'medium': '🟡',
'high': '🔴',
'urgent': '🚨'
low: '🔵',
medium: '🟡',
high: '🔴',
urgent: '🚨',
}[task.priority];
console.log(` ${statusEmoji} ${priorityEmoji} ${task.title}`);
console.log(` Tags: ${task.tags?.join(', ') || 'aucun'}`);
if (task.dueDate) {
console.log(` Échéance: ${task.dueDate.toLocaleDateString('fr-FR')}`);
console.log(
` Échéance: ${task.dueDate.toLocaleDateString('fr-FR')}`
);
}
console.log('');
createdCount++;
} catch (error) {
console.error(` ❌ Erreur pour "${taskData.title}":`, error instanceof Error ? error.message : error);
console.error(
` ❌ Erreur pour "${taskData.title}":`,
error instanceof Error ? error.message : error
);
errorCount++;
}
}
@@ -92,7 +99,7 @@ async function seedTestData() {
console.log('📊 Résumé:');
console.log(` ✅ Tâches créées: ${createdCount}`);
console.log(` ❌ Erreurs: ${errorCount}`);
// Afficher les stats finales
const stats = await tasksService.getTaskStats();
console.log('');
@@ -107,14 +114,16 @@ async function seedTestData() {
// Exécuter le script
if (require.main === module) {
seedTestData().then(() => {
console.log('');
console.log('✨ Données de test ajoutées avec succès !');
process.exit(0);
}).catch((error) => {
console.error('💥 Erreur fatale:', error);
process.exit(1);
});
seedTestData()
.then(() => {
console.log('');
console.log('✨ Données de test ajoutées avec succès !');
process.exit(0);
})
.catch((error) => {
console.error('💥 Erreur fatale:', error);
process.exit(1);
});
}
export { seedTestData };

View File

@@ -10,13 +10,18 @@ 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 pour l'utilisateur spécifié ou 'default'
const userId = process.argv[2] || 'default';
const jiraConfig = await userPreferencesService.getJiraConfig(userId);
if (!jiraConfig.enabled || !jiraConfig.baseUrl || !jiraConfig.email || !jiraConfig.apiToken) {
if (
!jiraConfig.enabled ||
!jiraConfig.baseUrl ||
!jiraConfig.email ||
!jiraConfig.apiToken
) {
console.log('❌ Configuration Jira manquante');
return;
}
@@ -27,14 +32,14 @@ async function testJiraFields() {
}
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;
@@ -44,17 +49,23 @@ async function testJiraFields() {
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🎯 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(
'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');
@@ -65,7 +76,7 @@ async function testJiraFields() {
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');
@@ -73,7 +84,6 @@ async function testJiraFields() {
console.log('• Task: 3 points');
console.log('• Bug: 2 points');
console.log('• Subtask: 1 point');
} catch (error) {
console.error('❌ Erreur lors du test:', error);
}
@@ -81,4 +91,3 @@ async function testJiraFields() {
// Exécution du script
testJiraFields().catch(console.error);

View File

@@ -10,13 +10,18 @@ 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 pour l'utilisateur spécifié ou 'default'
const userId = process.argv[2] || 'default';
const jiraConfig = await userPreferencesService.getJiraConfig(userId);
if (!jiraConfig.enabled || !jiraConfig.baseUrl || !jiraConfig.email || !jiraConfig.apiToken) {
if (
!jiraConfig.enabled ||
!jiraConfig.baseUrl ||
!jiraConfig.email ||
!jiraConfig.apiToken
) {
console.log('❌ Configuration Jira manquante');
return;
}
@@ -27,41 +32,47 @@ async function testStoryPoints() {
}
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 }> = {};
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(
` 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;
storyPointsDistribution[storyPoints] =
(storyPointsDistribution[storyPoints] || 0) + 1;
} else {
ticketsWithoutStoryPoints++;
}
// Distribution par type
if (!typeDistribution[issueType]) {
typeDistribution[issueType] = { count: 0, totalPoints: 0 };
@@ -69,37 +80,47 @@ async function testStoryPoints() {
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(
`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)
.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`);
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(
'• 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');
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);
}
@@ -107,4 +128,3 @@ async function testStoryPoints() {
// Exécution du script
testStoryPoints().catch(console.error);