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>
15 KiB
15 KiB
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
- Ajouter des logs détaillés dans
getParameterConfig() - Créer le composant
DebugPanelpour visualiser les problèmes - Identifier tous les cas où le mapping échoue
Phase 2 : Refactoring Progressif
- Créer
StepTypeResolveravec tests unitaires - Migrer
PropertiesPanelpour utiliser le nouveau résolveur - Améliorer les hooks VWB avec gestion d'erreurs
Phase 3 : Validation et Optimisation
- Tests d'intégration complets
- Optimisation des performances avec mémorisation
- 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.