#!/usr/bin/env python3 """ Script de Diagnostic - Propriétés d'Étapes Vides VWB Auteur : Dom, Alice, Kiro - 12 janvier 2026 Ce script diagnostique le problème des propriétés d'étapes vides dans le Visual Workflow Builder en analysant la configuration, les types d'étapes et les mappings. """ import json import os import sys from pathlib import Path from typing import Dict, List, Any, Optional import subprocess import re class VWBPropertiesDiagnostic: """Diagnostic complet du système de propriétés d'étapes VWB.""" def __init__(self): """Initialise le diagnostic.""" self.project_root = Path(__file__).parent.parent self.frontend_path = self.project_root / "visual_workflow_builder" / "frontend" self.backend_path = self.project_root / "visual_workflow_builder" / "backend" self.results = { "timestamp": "2026-01-12", "diagnostic_version": "1.0.0", "issues_found": [], "recommendations": [], "file_analysis": {}, "type_mappings": {}, "vwb_actions": {}, "configuration_status": {} } print("🔍 Diagnostic des Propriétés d'Étapes VWB - Démarrage") print(f"📁 Racine du projet : {self.project_root}") def run_full_diagnostic(self) -> Dict[str, Any]: """Exécute le diagnostic complet.""" try: print("\n" + "="*60) print("🔍 DIAGNOSTIC COMPLET DES PROPRIÉTÉS D'ÉTAPES") print("="*60) # 1. Analyser la configuration des paramètres self._analyze_step_parameters_config() # 2. Analyser les types d'étapes self._analyze_step_types() # 3. Analyser les actions VWB self._analyze_vwb_actions() # 4. Analyser les hooks d'intégration self._analyze_integration_hooks() # 5. Analyser le composant PropertiesPanel self._analyze_properties_panel() # 6. Vérifier la cohérence TypeScript self._check_typescript_consistency() # 7. Générer les recommandations self._generate_recommendations() # 8. Sauvegarder le rapport self._save_diagnostic_report() print(f"\n✅ Diagnostic terminé - {len(self.results['issues_found'])} problèmes identifiés") return self.results except Exception as e: print(f"❌ Erreur lors du diagnostic : {e}") self.results["fatal_error"] = str(e) return self.results def _analyze_step_parameters_config(self): """Analyse la configuration stepParametersConfig.""" print("\n📋 Analyse de la configuration stepParametersConfig...") properties_panel_path = self.frontend_path / "src" / "components" / "PropertiesPanel" / "index.tsx" if not properties_panel_path.exists(): self._add_issue("CRITICAL", "Fichier PropertiesPanel/index.tsx introuvable", { "expected_path": str(properties_panel_path) }) return try: content = properties_panel_path.read_text(encoding='utf-8') # Extraire la configuration stepParametersConfig config_match = re.search( r'const stepParametersConfig:\s*Record\s*=\s*{([^}]+)}', content, re.DOTALL ) if config_match: config_content = config_match.group(1) # Extraire les types configurés type_matches = re.findall(r'(\w+):\s*\[', config_content) configured_types = set(type_matches) self.results["configuration_status"]["stepParametersConfig"] = { "found": True, "configured_types": list(configured_types), "type_count": len(configured_types) } print(f" ✅ Configuration trouvée avec {len(configured_types)} types :") for step_type in sorted(configured_types): print(f" - {step_type}") # Analyser chaque type configuré for step_type in configured_types: self._analyze_step_type_config(content, step_type) else: self._add_issue("CRITICAL", "Configuration stepParametersConfig non trouvée", { "file": str(properties_panel_path), "search_pattern": "stepParametersConfig" }) except Exception as e: self._add_issue("ERROR", f"Erreur lecture PropertiesPanel : {e}", { "file": str(properties_panel_path) }) def _analyze_step_type_config(self, content: str, step_type: str): """Analyse la configuration d'un type d'étape spécifique.""" # Extraire la configuration pour ce type pattern = rf'{step_type}:\s*\[(.*?)\]' match = re.search(pattern, content, re.DOTALL) if match: config_content = match.group(1) # Compter les paramètres param_matches = re.findall(r'{\s*name:\s*[\'"](\w+)[\'"]', config_content) self.results["type_mappings"][step_type] = { "parameters": param_matches, "parameter_count": len(param_matches), "has_required_params": "required: true" in config_content, "has_visual_params": "type: 'visual'" in config_content } print(f" 📝 {step_type}: {len(param_matches)} paramètres") def _analyze_step_types(self): """Analyse les types d'étapes définis.""" print("\n🏷️ Analyse des types d'étapes...") types_path = self.frontend_path / "src" / "types" / "index.ts" if not types_path.exists(): self._add_issue("CRITICAL", "Fichier types/index.ts introuvable", { "expected_path": str(types_path) }) return try: content = types_path.read_text(encoding='utf-8') # Chercher la définition de StepType step_type_match = re.search( r'export\s+type\s+StepType\s*=\s*([^;]+);', content, re.DOTALL ) if step_type_match: step_type_def = step_type_match.group(1) # Extraire les types définis type_matches = re.findall(r"['\"](\w+)['\"]", step_type_def) defined_types = set(type_matches) self.results["configuration_status"]["stepTypes"] = { "found": True, "defined_types": list(defined_types), "type_count": len(defined_types) } print(f" ✅ Types StepType trouvés ({len(defined_types)}) :") for step_type in sorted(defined_types): print(f" - {step_type}") # Comparer avec la configuration configured_types = set(self.results["configuration_status"].get("stepParametersConfig", {}).get("configured_types", [])) missing_in_config = defined_types - configured_types extra_in_config = configured_types - defined_types if missing_in_config: self._add_issue("WARNING", "Types définis mais non configurés", { "missing_types": list(missing_in_config) }) if extra_in_config: self._add_issue("WARNING", "Types configurés mais non définis", { "extra_types": list(extra_in_config) }) else: self._add_issue("CRITICAL", "Définition StepType non trouvée", { "file": str(types_path) }) except Exception as e: self._add_issue("ERROR", f"Erreur lecture types : {e}", { "file": str(types_path) }) def _analyze_vwb_actions(self): """Analyse les actions VWB du catalogue.""" print("\n🎯 Analyse des actions VWB...") # Analyser le catalogue statique static_catalog_path = self.frontend_path / "src" / "data" / "staticCatalog.ts" if static_catalog_path.exists(): try: content = static_catalog_path.read_text(encoding='utf-8') # Extraire les IDs d'actions id_matches = re.findall(r"id:\s*['\"](\w+)['\"]", content) vwb_action_ids = set(id_matches) self.results["vwb_actions"]["static_catalog"] = { "found": True, "action_ids": list(vwb_action_ids), "action_count": len(vwb_action_ids) } print(f" ✅ Catalogue statique trouvé avec {len(vwb_action_ids)} actions :") for action_id in sorted(vwb_action_ids): print(f" - {action_id}") except Exception as e: self._add_issue("ERROR", f"Erreur lecture catalogue statique : {e}", { "file": str(static_catalog_path) }) # Analyser le registry backend registry_path = self.backend_path / "actions" / "registry.py" if registry_path.exists(): try: content = registry_path.read_text(encoding='utf-8') # Vérifier la présence du registry if "class VWBActionRegistry" in content: self.results["vwb_actions"]["backend_registry"] = { "found": True, "has_auto_discovery": "auto_discover_actions" in content, "has_global_instance": "get_global_registry" in content } print(" ✅ Registry backend trouvé et fonctionnel") else: self._add_issue("WARNING", "Registry backend incomplet", { "file": str(registry_path) }) except Exception as e: self._add_issue("ERROR", f"Erreur lecture registry : {e}", { "file": str(registry_path) }) def _analyze_integration_hooks(self): """Analyse les hooks d'intégration VWB.""" print("\n🔗 Analyse des hooks d'intégration...") hooks_path = self.frontend_path / "src" / "hooks" / "useVWBStepIntegration.ts" if not hooks_path.exists(): self._add_issue("CRITICAL", "Hooks d'intégration VWB introuvables", { "expected_path": str(hooks_path) }) return try: content = hooks_path.read_text(encoding='utf-8') # Vérifier les hooks principaux hooks_found = { "useVWBStepIntegration": "useVWBStepIntegration" in content, "useIsVWBStep": "useIsVWBStep" in content, "useVWBActionId": "useVWBActionId" in content } self.results["configuration_status"]["integration_hooks"] = hooks_found print(" 🔍 Hooks d'intégration :") for hook_name, found in hooks_found.items(): status = "✅" if found else "❌" print(f" {status} {hook_name}") if not found: self._add_issue("CRITICAL", f"Hook {hook_name} manquant", { "file": str(hooks_path) }) # Analyser la logique de détection VWB if "useIsVWBStep" in content: # Chercher la logique de détection detection_patterns = [ "isVWBCatalogAction", "vwbActionId", "startsWith('vwb_')", "includes('catalog_')" ] detection_logic = {} for pattern in detection_patterns: detection_logic[pattern] = pattern in content self.results["configuration_status"]["vwb_detection_logic"] = detection_logic print(" 🎯 Logique de détection VWB :") for pattern, found in detection_logic.items(): status = "✅" if found else "❌" print(f" {status} {pattern}") except Exception as e: self._add_issue("ERROR", f"Erreur lecture hooks : {e}", { "file": str(hooks_path) }) def _analyze_properties_panel(self): """Analyse le composant PropertiesPanel en détail.""" print("\n🎛️ Analyse détaillée du PropertiesPanel...") properties_panel_path = self.frontend_path / "src" / "components" / "PropertiesPanel" / "index.tsx" try: content = properties_panel_path.read_text(encoding='utf-8') # Analyser la fonction getParameterConfig get_param_config_match = re.search( r'const getParameterConfig = useCallback\(\(\):\s*ParameterConfig\[\]\s*=>\s*{([^}]+)}\s*,', content, re.DOTALL ) if get_param_config_match: function_body = get_param_config_match.group(1) analysis = { "function_found": True, "has_null_check": "if (!selectedStep)" in function_body, "uses_step_type": "selectedStep.type" in function_body, "uses_config_lookup": "stepParametersConfig[" in function_body, "has_fallback": "|| []" in function_body, "function_body": function_body.strip() } self.results["configuration_status"]["getParameterConfig"] = analysis print(" 🔍 Fonction getParameterConfig :") for key, value in analysis.items(): if key != "function_body": status = "✅" if value else "❌" print(f" {status} {key}") # Identifier le problème potentiel if analysis["uses_step_type"] and analysis["uses_config_lookup"]: print(" ⚠️ Problème potentiel identifié :") print(" La fonction utilise selectedStep.type pour chercher dans stepParametersConfig") print(" Mais il pourrait y avoir une incohérence entre les types d'étapes créées") print(" et les clés de configuration disponibles.") self._add_issue("CRITICAL", "Incohérence potentielle dans getParameterConfig", { "description": "La fonction getParameterConfig utilise selectedStep.type mais pourrait ne pas trouver la configuration correspondante", "function_body": function_body.strip() }) else: self._add_issue("CRITICAL", "Fonction getParameterConfig non trouvée", { "file": str(properties_panel_path) }) # Analyser la logique de rendu conditionnel vwb_rendering_patterns = [ "isVWBCatalogAction", "VWBActionProperties", "vwbAction", "parameterConfigs.length === 0" ] rendering_analysis = {} for pattern in vwb_rendering_patterns: rendering_analysis[pattern] = pattern in content self.results["configuration_status"]["rendering_logic"] = rendering_analysis print(" 🎨 Logique de rendu :") for pattern, found in rendering_analysis.items(): status = "✅" if found else "❌" print(f" {status} {pattern}") except Exception as e: self._add_issue("ERROR", f"Erreur analyse PropertiesPanel : {e}", { "file": str(properties_panel_path) }) def _check_typescript_consistency(self): """Vérifie la cohérence TypeScript.""" print("\n🔧 Vérification de la cohérence TypeScript...") try: # Changer vers le répertoire frontend frontend_dir = self.frontend_path if not frontend_dir.exists(): self._add_issue("CRITICAL", "Répertoire frontend introuvable", { "expected_path": str(frontend_dir) }) return # Exécuter la vérification TypeScript result = subprocess.run( ["npx", "tsc", "--noEmit"], cwd=frontend_dir, capture_output=True, text=True, timeout=60 ) typescript_status = { "exit_code": result.returncode, "has_errors": result.returncode != 0, "stdout": result.stdout, "stderr": result.stderr } self.results["configuration_status"]["typescript"] = typescript_status if result.returncode == 0: print(" ✅ Compilation TypeScript réussie") else: print(" ❌ Erreurs TypeScript détectées :") if result.stderr: for line in result.stderr.split('\n')[:10]: # Limiter à 10 lignes if line.strip(): print(f" {line}") self._add_issue("ERROR", "Erreurs de compilation TypeScript", { "stderr": result.stderr, "stdout": result.stdout }) except subprocess.TimeoutExpired: self._add_issue("ERROR", "Timeout lors de la vérification TypeScript", {}) except FileNotFoundError: self._add_issue("WARNING", "npx/tsc non disponible pour la vérification", {}) except Exception as e: self._add_issue("ERROR", f"Erreur vérification TypeScript : {e}", {}) def _generate_recommendations(self): """Génère les recommandations basées sur l'analyse.""" print("\n💡 Génération des recommandations...") # Analyser les problèmes trouvés critical_issues = [issue for issue in self.results["issues_found"] if issue["severity"] == "CRITICAL"] if critical_issues: self.results["recommendations"].append({ "priority": "URGENT", "title": "Corriger les problèmes critiques identifiés", "description": "Plusieurs problèmes critiques empêchent le bon fonctionnement des propriétés d'étapes", "actions": [ "Vérifier la cohérence entre les types d'étapes et la configuration stepParametersConfig", "Corriger la logique de détection des actions VWB", "Ajouter des logs de débogage dans getParameterConfig()", "Implémenter un système de fallback robuste" ] }) # Recommandations spécifiques basées sur l'analyse config_status = self.results["configuration_status"] if config_status.get("stepParametersConfig", {}).get("found"): configured_types = set(config_status["stepParametersConfig"]["configured_types"]) defined_types = set(config_status.get("stepTypes", {}).get("defined_types", [])) if configured_types != defined_types: self.results["recommendations"].append({ "priority": "HIGH", "title": "Synchroniser les types d'étapes et leur configuration", "description": "Incohérence entre les types StepType définis et la configuration stepParametersConfig", "actions": [ f"Ajouter la configuration pour : {list(defined_types - configured_types)}", f"Supprimer ou corriger : {list(configured_types - defined_types)}", "Créer un système de validation automatique" ] }) if not config_status.get("integration_hooks", {}).get("useIsVWBStep", False): self.results["recommendations"].append({ "priority": "HIGH", "title": "Corriger les hooks d'intégration VWB", "description": "Les hooks de détection des actions VWB sont manquants ou défaillants", "actions": [ "Implémenter useIsVWBStep avec logique robuste", "Corriger useVWBActionId pour la détection d'ID", "Ajouter la gestion d'erreurs dans les hooks" ] }) # Recommandation pour le système de diagnostic self.results["recommendations"].append({ "priority": "MEDIUM", "title": "Implémenter un système de diagnostic intégré", "description": "Ajouter des outils de diagnostic dans l'interface pour faciliter le débogage", "actions": [ "Créer un composant DebugPanel pour le mode développement", "Ajouter des logs structurés dans les composants critiques", "Implémenter des métriques de performance", "Créer des tests automatisés pour la détection de régression" ] }) print(f" ✅ {len(self.results['recommendations'])} recommandations générées") def _add_issue(self, severity: str, description: str, details: Dict[str, Any]): """Ajoute un problème identifié.""" issue = { "severity": severity, "description": description, "details": details, "timestamp": "2026-01-12" } self.results["issues_found"].append(issue) # Afficher immédiatement les problèmes critiques if severity == "CRITICAL": print(f" 🚨 CRITIQUE : {description}") def _save_diagnostic_report(self): """Sauvegarde le rapport de diagnostic.""" report_path = self.project_root / "docs" / "DIAGNOSTIC_PROPRIETES_ETAPES_VIDES_12JAN2026.json" try: # Créer le répertoire docs s'il n'existe pas report_path.parent.mkdir(exist_ok=True) # Sauvegarder le rapport JSON with open(report_path, 'w', encoding='utf-8') as f: json.dump(self.results, f, indent=2, ensure_ascii=False) print(f"\n📄 Rapport sauvegardé : {report_path}") # Créer aussi un résumé markdown self._create_markdown_summary(report_path.with_suffix('.md')) except Exception as e: print(f"❌ Erreur sauvegarde rapport : {e}") def _create_markdown_summary(self, md_path: Path): """Crée un résumé markdown du diagnostic.""" try: with open(md_path, 'w', encoding='utf-8') as f: f.write("# Diagnostic des Propriétés d'Étapes Vides - Rapport\n\n") f.write("**Auteur :** Dom, Alice, Kiro \n") f.write("**Date :** 12 janvier 2026 \n") f.write("**Version :** 1.0.0\n\n") # Résumé exécutif f.write("## Résumé Exécutif\n\n") f.write(f"- **Problèmes identifiés :** {len(self.results['issues_found'])}\n") f.write(f"- **Recommandations :** {len(self.results['recommendations'])}\n") critical_count = len([i for i in self.results['issues_found'] if i['severity'] == 'CRITICAL']) f.write(f"- **Problèmes critiques :** {critical_count}\n\n") # Problèmes identifiés if self.results['issues_found']: f.write("## Problèmes Identifiés\n\n") for issue in self.results['issues_found']: f.write(f"### {issue['severity']} : {issue['description']}\n\n") if issue['details']: f.write("**Détails :**\n") for key, value in issue['details'].items(): f.write(f"- **{key}** : {value}\n") f.write("\n") # Recommandations if self.results['recommendations']: f.write("## Recommandations\n\n") for rec in self.results['recommendations']: f.write(f"### {rec['priority']} : {rec['title']}\n\n") f.write(f"{rec['description']}\n\n") f.write("**Actions recommandées :**\n") for action in rec['actions']: f.write(f"- {action}\n") f.write("\n") # Configuration actuelle f.write("## État de la Configuration\n\n") for section, status in self.results['configuration_status'].items(): f.write(f"### {section}\n\n") f.write(f"```json\n{json.dumps(status, indent=2)}\n```\n\n") print(f"📄 Résumé markdown créé : {md_path}") except Exception as e: print(f"❌ Erreur création résumé markdown : {e}") def main(): """Fonction principale.""" print("🚀 Lancement du diagnostic des propriétés d'étapes VWB") diagnostic = VWBPropertiesDiagnostic() results = diagnostic.run_full_diagnostic() # Afficher le résumé final print("\n" + "="*60) print("📊 RÉSUMÉ DU DIAGNOSTIC") print("="*60) print(f"✅ Diagnostic terminé avec succès") print(f"🔍 Problèmes identifiés : {len(results['issues_found'])}") print(f"💡 Recommandations : {len(results['recommendations'])}") critical_issues = [i for i in results['issues_found'] if i['severity'] == 'CRITICAL'] if critical_issues: print(f"🚨 Problèmes critiques : {len(critical_issues)}") print("\nProblèmes critiques à traiter en priorité :") for issue in critical_issues: print(f" - {issue['description']}") print(f"\n📄 Rapport complet disponible dans docs/") print("🔧 Utilisez ce diagnostic pour implémenter les corrections nécessaires.") return 0 if len(critical_issues) == 0 else 1 if __name__ == "__main__": sys.exit(main())