import WorkflowValidator from '../resources/WorkflowValidator.js';
/**
* Validateur pour les aspects de performance dans les workflows n8n
*/
class PerformanceValidator implements WorkflowValidator {
/**
* Valide les aspects de performance dans un workflow n8n
* @param workflow Les données du workflow à valider
* @param strictness Le niveau de rigueur de la validation
* @returns Résultat de la validation avec les problèmes détectés
*/
validate(workflow: any, strictness: 'low' | 'medium' | 'high') {
const issues: Array<{
message: string;
recommendation: string;
severity: 'low' | 'medium' | 'high';
location?: string;
}> = [];
// Vérifier la complexité du workflow
this.checkWorkflowComplexity(workflow, issues, strictness);
// Vérifier les nœuds pour les problèmes de performance
if (workflow.nodes && Array.isArray(workflow.nodes)) {
workflow.nodes.forEach((node: any) => {
// Vérifier les boucles
this.checkLoops(node, workflow, issues, strictness);
// Vérifier les opérations de traitement de données
this.checkDataProcessing(node, issues, strictness);
// Vérifier les appels HTTP
this.checkHttpCalls(node, issues, strictness);
});
}
// Vérifier les connexions entre les nœuds
this.checkNodeConnections(workflow, issues, strictness);
return {
valid: issues.length === 0,
issues,
};
}
/**
* Vérifie la complexité globale du workflow
*/
private checkWorkflowComplexity(workflow: any, issues: any[], strictness: string): void {
if (!workflow.nodes || !Array.isArray(workflow.nodes)) {
return;
}
// Vérifier le nombre de nœuds
const nodeCount = workflow.nodes.length;
if (strictness === 'high' && nodeCount > 30) {
issues.push({
message: `Le workflow contient un grand nombre de nœuds (${nodeCount})`,
recommendation: 'Envisagez de diviser le workflow en plusieurs workflows plus petits pour améliorer la maintenabilité et les performances',
severity: 'medium',
});
} else if (strictness === 'medium' && nodeCount > 50) {
issues.push({
message: `Le workflow contient un très grand nombre de nœuds (${nodeCount})`,
recommendation: 'Divisez le workflow en plusieurs workflows plus petits pour améliorer la maintenabilité et les performances',
severity: 'medium',
});
}
// Vérifier la profondeur du workflow (chemin le plus long)
if (workflow.connections) {
const depth = this.calculateWorkflowDepth(workflow);
if ((strictness === 'high' && depth > 15) || (strictness === 'medium' && depth > 25)) {
issues.push({
message: `Le workflow a une profondeur élevée (${depth} nœuds dans le chemin le plus long)`,
recommendation: 'Réduisez la profondeur du workflow en le divisant en sous-workflows ou en optimisant la logique',
severity: 'low',
});
}
}
}
/**
* Calcule la profondeur maximale du workflow (chemin le plus long)
*/
private calculateWorkflowDepth(workflow: any): number {
// Implémentation simplifiée pour estimer la profondeur
// Dans une implémentation complète, on utiliserait un algorithme de parcours de graphe
// Identifier les nœuds de départ (triggers)
const startNodes = workflow.nodes.filter((node: any) =>
(node.type || '').toLowerCase().includes('trigger')
).map((node: any) => node.id);
// Si aucun nœud de départ n'est trouvé, utiliser le premier nœud
const rootNodes = startNodes.length > 0 ? startNodes : [workflow.nodes[0]?.id];
// Estimation de la profondeur basée sur le nombre de connexions
let depth = 0;
if (workflow.connections) {
// Compter le nombre de connexions pour estimer la profondeur
const connectionCount = Object.keys(workflow.connections).length;
// Estimation grossière: chaque connexion ajoute potentiellement un niveau de profondeur
depth = Math.min(connectionCount, workflow.nodes.length);
}
return depth;
}
/**
* Vérifie les nœuds de boucle pour les problèmes de performance
*/
private checkLoops(node: any, workflow: any, issues: any[], strictness: string): void {
// Identifier les nœuds de boucle
if ((node.type || '').toLowerCase().includes('loop') ||
(node.type || '').toLowerCase().includes('each') ||
(node.type || '').toLowerCase().includes('split')) {
// Vérifier si la boucle a une limite
if (!node.parameters?.limit && strictness !== 'low') {
issues.push({
message: `Le nœud de boucle "${node.name}" n'a pas de limite définie`,
recommendation: 'Définissez une limite pour éviter les boucles infinies ou le traitement d\'un trop grand nombre d\'éléments',
severity: 'medium',
location: node.id,
});
}
// Vérifier si la boucle traite potentiellement un grand nombre d'éléments
if (node.parameters?.batchSize && node.parameters.batchSize > 100 && strictness === 'high') {
issues.push({
message: `Le nœud de boucle "${node.name}" traite de grands lots (taille: ${node.parameters.batchSize})`,
recommendation: 'Réduisez la taille des lots pour améliorer les performances et éviter les timeouts',
severity: 'low',
location: node.id,
});
}
}
}
/**
* Vérifie les nœuds de traitement de données pour les problèmes de performance
*/
private checkDataProcessing(node: any, issues: any[], strictness: string): void {
// Vérifier les nœuds de transformation de données
if ((node.type || '').toLowerCase().includes('set') ||
(node.type || '').toLowerCase().includes('function') ||
(node.type || '').toLowerCase().includes('transform')) {
// Vérifier le code des nœuds Function pour les opérations coûteuses
if (node.parameters?.functionCode || node.parameters?.code) {
const code = node.parameters.functionCode || node.parameters.code;
// Vérifier les boucles imbriquées
if (typeof code === 'string' &&
(code.match(/for\s*\(/g) || []).length > 1 &&
strictness !== 'low') {
issues.push({
message: `Le nœud "${node.name}" contient des boucles imbriquées qui peuvent affecter les performances`,
recommendation: 'Évitez les boucles imbriquées ou optimisez l\'algorithme pour améliorer les performances',
severity: 'medium',
location: node.id,
});
}
// Vérifier les opérations sur de grands tableaux
if (typeof code === 'string' &&
(code.includes('.map(') || code.includes('.filter(') || code.includes('.reduce(')) &&
code.includes('items.json') &&
strictness === 'high') {
issues.push({
message: `Le nœud "${node.name}" effectue des opérations sur des tableaux potentiellement grands`,
recommendation: 'Envisagez d\'utiliser des nœuds de traitement par lots pour les grands ensembles de données',
severity: 'low',
location: node.id,
});
}
}
}
}
/**
* Vérifie les nœuds HTTP pour les problèmes de performance
*/
private checkHttpCalls(node: any, issues: any[], strictness: string): void {
if ((node.type || '').toLowerCase().includes('http')) {
// Vérifier si le nœud HTTP a un timeout configuré
if (!node.parameters?.timeout && strictness !== 'low') {
issues.push({
message: `Le nœud HTTP "${node.name}" n'a pas de timeout configuré`,
recommendation: 'Configurez un timeout pour éviter que le workflow ne reste bloqué sur des requêtes lentes',
severity: 'low',
location: node.id,
});
}
// Vérifier les requêtes HTTP dans une boucle
// Cette vérification nécessiterait une analyse des connexions entre les nœuds
// Implémentation simplifiée pour l'exemple
}
}
/**
* Vérifie les connexions entre les nœuds pour les problèmes de performance
*/
private checkNodeConnections(workflow: any, issues: any[], strictness: string): void {
if (!workflow.nodes || !Array.isArray(workflow.nodes) || !workflow.connections) {
return;
}
// Vérifier les nœuds avec de nombreuses connexions sortantes (fan-out)
Object.entries(workflow.connections).forEach(([nodeId, connections]: [string, any]) => {
const connectionCount = Object.values(connections).flat().length;
if (connectionCount > 5 && strictness !== 'low') {
const nodeName = workflow.nodes.find((n: any) => n.id === nodeId)?.name || 'Inconnu';
issues.push({
message: `Le nœud "${nodeName}" a un grand nombre de connexions sortantes (${connectionCount})`,
recommendation: 'Un grand nombre de connexions sortantes peut affecter les performances. Envisagez de restructurer le workflow',
severity: 'low',
location: nodeId,
});
}
});
// Vérifier les chemins parallèles excessifs
// Cette vérification nécessiterait une analyse plus complexe du graphe de workflow
// Implémentation simplifiée pour l'exemple
}
}
export default PerformanceValidator;