Files
rpa_vision_v3/.kiro/specs/correction-proprietes-etapes-vides/design.md
Dom a7de6a488b feat: replay E2E fonctionnel — 25/25 actions, 0 retries, SomEngine via serveur
Validé sur PC Windows (DESKTOP-58D5CAC, 2560x1600) :
- 8 clics résolus visuellement (1 anchor_template, 1 som_text_match, 6 som_vlm)
- Score moyen 0.75, temps moyen 1.6s
- Texte tapé correctement (bonjour, test word, date, email)
- 0 retries, 2 actions non vérifiées (OK)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:04:41 +02:00

15 KiB
Raw Blame History

Design Technique - Correction des Propriétés d'Étapes Vides

Auteur : Dom, Alice, Kiro
Date : 12 janvier 2026
Version : 1.0.0

Vue d'Ensemble de la Solution

Cette solution corrige le problème des propriétés d'étapes vides en refactorisant la logique de détection et de mapping des types d'étapes dans le Visual Workflow Builder.

Problème Diagnostiqué

// PROBLÈME ACTUEL : getParameterConfig() retourne []
const getParameterConfig = useCallback((): ParameterConfig[] => {
  if (!selectedStep) return [];
  return stepParametersConfig[selectedStep.type] || []; // ← Retourne toujours []
}, [selectedStep]);

Cause racine : Incohérence entre les types d'étapes créées et les clés de stepParametersConfig.

Architecture de la Solution

1. Normalisation des Types d'Étapes

// Nouveau système de mapping unifié
interface StepTypeResolver {
  resolveParameterConfig(step: Step): ParameterConfig[];
  isVWBCatalogAction(step: Step): boolean;
  getVWBActionId(step: Step): string | null;
  normalizeStepType(stepType: string): StepType | null;
}

// Implémentation du résolveur
class StepTypeResolverImpl implements StepTypeResolver {
  private readonly typeMapping = new Map<string, StepType>();
  private readonly vwbActionIds = new Set<string>();
  
  constructor() {
    this.initializeTypeMappings();
  }
  
  private initializeTypeMappings() {
    // Mapping explicite des types d'étapes
    this.typeMapping.set('click', 'click');
    this.typeMapping.set('type', 'type');
    this.typeMapping.set('wait', 'wait');
    // ... autres mappings
    
    // Actions VWB du catalogue
    this.vwbActionIds.add('click_anchor');
    this.vwbActionIds.add('type_text');
    this.vwbActionIds.add('type_secret');
    // ... autres actions VWB
  }
}

2. Refactoring du PropertiesPanel

// Nouvelle logique de résolution des paramètres
const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ selectedStep, ... }) => {
  const stepTypeResolver = useStepTypeResolver();
  const { vwbAction, isLoading } = useVWBActionDetails(selectedStep);
  
  // Résolution unifiée des paramètres
  const parameterConfig = useMemo(() => {
    if (!selectedStep) return [];
    
    // Log pour débogage
    console.log('🔍 Résolution paramètres pour étape:', {
      id: selectedStep.id,
      type: selectedStep.type,
      data: selectedStep.data
    });
    
    return stepTypeResolver.resolveParameterConfig(selectedStep);
  }, [selectedStep, stepTypeResolver]);
  
  // Détection VWB améliorée
  const isVWBAction = useMemo(() => {
    if (!selectedStep) return false;
    return stepTypeResolver.isVWBCatalogAction(selectedStep);
  }, [selectedStep, stepTypeResolver]);
  
  // Rendu conditionnel amélioré
  return (
    <Box>
      {/* Debug info en mode développement */}
      {process.env.NODE_ENV === 'development' && (
        <DebugPanel 
          step={selectedStep}
          parameterConfig={parameterConfig}
          isVWBAction={isVWBAction}
        />
      )}
      
      {/* Contenu principal */}
      {isVWBAction && vwbAction ? (
        <VWBActionProperties
          action={vwbAction}
          parameters={localParameters}
          variables={variables}
          onParameterChange={handleVWBParameterChange}
          onValidationChange={handleVWBValidationChange}
        />
      ) : parameterConfig.length > 0 ? (
        <StandardParametersEditor
          config={parameterConfig}
          parameters={localParameters}
          variables={variables}
          onParameterChange={handleParameterChange}
        />
      ) : (
        <EmptyStateMessage stepType={selectedStep?.type} />
      )}
    </Box>
  );
};

3. Amélioration des Hooks VWB

// Hook de résolution des types d'étapes
export const useStepTypeResolver = (): StepTypeResolver => {
  return useMemo(() => new StepTypeResolverImpl(), []);
};

// Hook amélioré pour les détails d'actions VWB
export const useVWBActionDetails = (step: Step | null) => {
  const [vwbAction, setVwbAction] = useState<VWBCatalogAction | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  
  const stepTypeResolver = useStepTypeResolver();
  
  useEffect(() => {
    const loadVWBAction = async () => {
      if (!step || !stepTypeResolver.isVWBCatalogAction(step)) {
        setVwbAction(null);
        return;
      }
      
      const actionId = stepTypeResolver.getVWBActionId(step);
      if (!actionId) {
        setVwbAction(null);
        return;
      }
      
      setIsLoading(true);
      setError(null);
      
      try {
        // Essayer le catalogue dynamique d'abord
        let action = await catalogService.getActionDetails(actionId);
        
        // Fallback vers le catalogue statique
        if (!action) {
          action = getStaticActionById(actionId);
        }
        
        setVwbAction(action);
        
        console.log('✅ Action VWB chargée:', {
          actionId,
          actionName: action?.name,
          source: action ? 'catalogue' : 'statique'
        });
        
      } catch (err) {
        const errorMessage = err instanceof Error ? err.message : 'Erreur inconnue';
        setError(errorMessage);
        console.error('❌ Erreur chargement action VWB:', err);
      } finally {
        setIsLoading(false);
      }
    };
    
    loadVWBAction();
  }, [step, stepTypeResolver]);
  
  return { vwbAction, isLoading, error };
};

4. Composant de Débogage

// Composant pour diagnostiquer les problèmes de mapping
const DebugPanel: React.FC<{
  step: Step | null;
  parameterConfig: ParameterConfig[];
  isVWBAction: boolean;
}> = ({ step, parameterConfig, isVWBAction }) => {
  if (!step) return null;
  
  return (
    <Accordion>
      <AccordionSummary expandIcon={<ExpandMoreIcon />}>
        <Typography variant="caption" color="primary">
          🔧 Debug Info (Dev Mode)
        </Typography>
      </AccordionSummary>
      <AccordionDetails>
        <Box sx={{ fontSize: '0.75rem', fontFamily: 'monospace' }}>
          <Typography variant="caption" display="block">
            <strong>Step ID:</strong> {step.id}
          </Typography>
          <Typography variant="caption" display="block">
            <strong>Step Type:</strong> {step.type}
          </Typography>
          <Typography variant="caption" display="block">
            <strong>Is VWB Action:</strong> {isVWBAction ? 'Yes' : 'No'}
          </Typography>
          <Typography variant="caption" display="block">
            <strong>Parameter Config Count:</strong> {parameterConfig.length}
          </Typography>
          <Typography variant="caption" display="block">
            <strong>Step Data:</strong>
          </Typography>
          <pre style={{ fontSize: '0.7rem', margin: '4px 0' }}>
            {JSON.stringify(step.data, null, 2)}
          </pre>
          <Typography variant="caption" display="block">
            <strong>Available Step Types in Config:</strong>
          </Typography>
          <pre style={{ fontSize: '0.7rem', margin: '4px 0' }}>
            {JSON.stringify(Object.keys(stepParametersConfig), null, 2)}
          </pre>
        </Box>
      </AccordionDetails>
    </Accordion>
  );
};

5. Composant d'État Vide Amélioré

// Message d'état vide avec diagnostic
const EmptyStateMessage: React.FC<{ stepType?: string }> = ({ stepType }) => {
  return (
    <Alert severity="warning" sx={{ m: 2 }}>
      <AlertTitle>Aucun paramètre configurable</AlertTitle>
      <Typography variant="body2" gutterBottom>
        Cette étape n'a pas de paramètres configurables.
      </Typography>
      {stepType && (
        <Typography variant="caption" color="text.secondary">
          Type d'étape : <code>{stepType}</code>
        </Typography>
      )}
      <Typography variant="caption" display="block" sx={{ mt: 1 }}>
        Si vous pensez que c'est une erreur, vérifiez la configuration des types d'étapes.
      </Typography>
    </Alert>
  );
};

Stratégie de Migration

Phase 1 : Diagnostic et Logging

  1. Ajouter des logs détaillés dans getParameterConfig()
  2. Créer le composant DebugPanel pour visualiser les problèmes
  3. Identifier tous les cas où le mapping échoue

Phase 2 : Refactoring Progressif

  1. Créer StepTypeResolver avec tests unitaires
  2. Migrer PropertiesPanel pour utiliser le nouveau résolveur
  3. Améliorer les hooks VWB avec gestion d'erreurs

Phase 3 : Validation et Optimisation

  1. Tests d'intégration complets
  2. Optimisation des performances avec mémorisation
  3. Documentation et guides utilisateur

Tests et Validation

Tests Unitaires

describe('StepTypeResolver', () => {
  let resolver: StepTypeResolver;
  
  beforeEach(() => {
    resolver = new StepTypeResolverImpl();
  });
  
  describe('resolveParameterConfig', () => {
    it('should return click parameters for click step', () => {
      const step: Step = {
        id: 'test-1',
        type: 'click',
        name: 'Test Click',
        position: { x: 0, y: 0 },
        data: {},
        executionState: StepExecutionState.IDLE,
        validationErrors: []
      };
      
      const config = resolver.resolveParameterConfig(step);
      
      expect(config).toHaveLength(2);
      expect(config[0].name).toBe('target');
      expect(config[1].name).toBe('clickType');
    });
    
    it('should detect VWB catalog actions', () => {
      const step: Step = {
        id: 'test-2',
        type: 'click_anchor',
        name: 'VWB Click',
        position: { x: 0, y: 0 },
        data: {
          isVWBCatalogAction: true,
          vwbActionId: 'click_anchor'
        },
        executionState: StepExecutionState.IDLE,
        validationErrors: []
      };
      
      const isVWB = resolver.isVWBCatalogAction(step);
      const actionId = resolver.getVWBActionId(step);
      
      expect(isVWB).toBe(true);
      expect(actionId).toBe('click_anchor');
    });
  });
});

Tests d'Intégration

describe('PropertiesPanel Integration', () => {
  it('should display parameters for standard steps', async () => {
    const step: Step = createMockStep('type', {
      target: null,
      text: 'Hello World',
      clearFirst: true
    });
    
    render(
      <PropertiesPanel
        selectedStep={step}
        variables={[]}
        onParameterChange={jest.fn()}
        onVisualSelection={jest.fn()}
      />
    );
    
    // Vérifier que les paramètres sont affichés
    expect(screen.getByLabelText('Champ de saisie')).toBeInTheDocument();
    expect(screen.getByLabelText('Texte à saisir')).toBeInTheDocument();
    expect(screen.getByLabelText('Vider le champ d\'abord')).toBeInTheDocument();
  });
  
  it('should display VWB action properties', async () => {
    const step: Step = createMockVWBStep('click_anchor');
    
    render(
      <PropertiesPanel
        selectedStep={step}
        variables={[]}
        onParameterChange={jest.fn()}
        onVisualSelection={jest.fn()}
      />
    );
    
    // Attendre le chargement de l'action VWB
    await waitFor(() => {
      expect(screen.getByText('Cliquer sur Ancre')).toBeInTheDocument();
    });
    
    // Vérifier que le composant VWB est affiché
    expect(screen.getByText('Élément visuel à cliquer')).toBeInTheDocument();
  });
});

Performance et Optimisation

Mémorisation des Résolutions

// Cache pour éviter les recalculs
const stepTypeCache = new Map<string, ParameterConfig[]>();

const resolveParameterConfigCached = (step: Step): ParameterConfig[] => {
  const cacheKey = `${step.type}_${step.data.isVWBCatalogAction}_${step.data.vwbActionId}`;
  
  if (stepTypeCache.has(cacheKey)) {
    return stepTypeCache.get(cacheKey)!;
  }
  
  const config = resolveParameterConfigInternal(step);
  stepTypeCache.set(cacheKey, config);
  
  return config;
};

Lazy Loading des Actions VWB

// Chargement différé des détails d'actions
const useVWBActionDetailsLazy = (step: Step | null) => {
  const [shouldLoad, setShouldLoad] = useState(false);
  
  // Ne charger que quand l'étape est sélectionnée et visible
  useEffect(() => {
    if (step && stepTypeResolver.isVWBCatalogAction(step)) {
      setShouldLoad(true);
    }
  }, [step]);
  
  return useVWBActionDetails(shouldLoad ? step : null);
};

Monitoring et Observabilité

Métriques de Performance

// Tracking des performances de résolution
const performanceTracker = {
  trackResolution: (stepType: string, duration: number) => {
    console.log(`⏱️ Résolution ${stepType}: ${duration}ms`);
    
    // En production, envoyer à un service de monitoring
    if (process.env.NODE_ENV === 'production') {
      analytics.track('step_resolution_time', {
        stepType,
        duration,
        timestamp: Date.now()
      });
    }
  }
};

Logs Structurés

// Système de logs pour diagnostic
const logger = {
  debug: (message: string, context: any) => {
    if (process.env.NODE_ENV === 'development') {
      console.log(`🔍 [PropertiesPanel] ${message}`, context);
    }
  },
  
  error: (message: string, error: Error, context: any) => {
    console.error(`❌ [PropertiesPanel] ${message}`, { error, context });
  },
  
  info: (message: string, context: any) => {
    console.log(` [PropertiesPanel] ${message}`, context);
  }
};

Sécurité et Robustesse

Validation des Types

// Validation stricte des types d'étapes
const validateStepType = (step: Step): boolean => {
  if (!step || !step.type) {
    logger.error('Step invalide', new Error('Step ou type manquant'), { step });
    return false;
  }
  
  if (typeof step.type !== 'string') {
    logger.error('Type d\'étape invalide', new Error('Type doit être string'), { 
      stepType: step.type,
      stepId: step.id 
    });
    return false;
  }
  
  return true;
};

Gestion d'Erreurs Gracieuse

// Fallback en cas d'erreur de résolution
const resolveParameterConfigSafe = (step: Step): ParameterConfig[] => {
  try {
    if (!validateStepType(step)) {
      return [];
    }
    
    return resolveParameterConfig(step);
  } catch (error) {
    logger.error('Erreur résolution paramètres', error as Error, { 
      stepId: step.id,
      stepType: step.type 
    });
    
    // Retourner une configuration minimale en cas d'erreur
    return [{
      name: 'error_fallback',
      label: 'Configuration en erreur',
      type: 'text',
      required: false,
      description: 'Une erreur est survenue lors du chargement des paramètres.'
    }];
  }
};

Cette architecture garantit une résolution robuste et performante des propriétés d'étapes, avec une observabilité complète pour faciliter le débogage et la maintenance.