chore: prettier everywhere
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user