Appearance
BDD-143 — Correction de l'erreur serveur lors de la sélection d'un dossier (bloc Entrée)
Contexte
Lors de la configuration d'un bloc Entrée en mode Dossier, la sélection d'un dossier déclenchait systématiquement une erreur HTTP 500 « Erreur interne du serveur ». Plusieurs causes distinctes ont été identifiées et corrigées, et des améliorations de performance ont été apportées sur le chargement des dossiers.
Critères d'acceptance
| # | Critère | Couvert par |
|---|---|---|
| 1 | La sélection d'un dossier ne génère plus d'erreur 500 | Conversion des erreurs S3 et de parsing en exceptions HTTP explicites |
| 2 | Les fichiers compatibles sont correctement chargés et prévisualisés | Correction du débordement de pile et de l'encodage |
| 3 | Un message d'erreur explicite est affiché si le dossier est vide ou inaccessible | WorkflowDepotFileLoadException (400) avec nom du fichier |
Causes identifiées et correctifs
1. Débordement de pile (RangeError: Maximum call stack size exceeded)
Cause : Dans matrixToParsedFile, le calcul de la largeur des colonnes utilisait Math.max(...array.map(...)). Pour des fichiers CSV volumineux (ex. 22 Mo, plusieurs dizaines de milliers de lignes), l'opérateur de décomposition (...) passait trop d'arguments à Math.max, épuisant la pile d'appels JavaScript.
Correctif : Remplacement par un reduce() qui itère sans utiliser la pile.
typescript
// Avant — plantait sur les grands fichiers
const width = Math.max(rawHeaderRow.length, ...dataRows.map(r => r.length));
// Après
const width = dataRows.reduce(
(max, row) => (row.length > max ? row.length : max),
rawHeaderRow.length,
);2. Colonnes vides parasites (délimiteur final dans certains CSV)
Cause : Certains logiciels de paie ajoutent un ; en fin de chaque ligne CSV, produisant une colonne vide supplémentaire dans la ligne d'en-tête. Deux fichiers du même dossier pouvaient ainsi avoir 14 ou 15 colonnes selon le mois d'export, ce qui déclenchait l'erreur « structures incompatibles ».
Correctif : Suppression des cellules vides en fin de ligne d'en-tête avant le calcul de la largeur. La ligne d'en-tête est désormais l'autorité pour définir le schéma.
3. Mojibake sur les fichiers Latin-1
Cause : parseCsvMatrix décodait tous les buffers CSV en UTF-8. Les fichiers exportés par les logiciels de paie français utilisent ISO-8859-1 (Latin-1) : les caractères accentués (é, è, ê…) apparaissaient illisibles (ex. GÈnÈration au lieu de Génération).
Correctif : Introduction de decodeBufferToText() qui détecte l'encodage via chardet et décode via iconv-lite (deux dépendances transitives déjà présentes). Repli sur UTF-8 si l'encodage est ASCII ou UTF-8.
4. Erreur S3 non catchée → 500 générique
Cause : Si le téléchargement S3 d'un fichier échouait (réseau, permissions, bucket/path manquant), downloadFileBuffer levait une Error JavaScript standard, non reconnue par handleExceptions.ts comme une HttpException, ce qui produisait un 500 avec le message générique « Erreur interne du serveur ».
Correctif : Ajout d'un try/catch autour de downloadFileBuffer dans loadMergedFolderParsed. L'erreur S3 est loguée (niveau error) et convertie en WorkflowDepotFileLoadException (400) avec le nom du fichier concerné.
5. Race condition sur la sauvegarde simultanée du canvas (violation de contrainte uuid)
Cause : L'autosave (debounce 1 s) pouvait déclencher deux requêtes PUT /canvas concurrentes pour le même workflow. Les deux transactions exécutaient leur deleteMany avant que l'autre n'ait commité son createMany. Résultat : les deux tentaient d'insérer les mêmes UUID dans workflow_nodes, provoquant une Unique constraint failed on fields: (uuid) (409).
Correctif : La mise à jour des métadonnées du workflow (tx.workflows.update) est déplacée en début de transaction. PostgreSQL acquiert ainsi un verrou de ligne sur le workflow dès l'entrée, sérialisant les sauvegardes concurrentes : la seconde requête attend le commit de la première avant d'exécuter son deleteMany.
6. Valeurs de colonne amont inaccessibles sans bloc Destination
Cause : L'endpoint GET /upstream-column-values appelait resolveExecutionOrderForRun et executeRun sans passer requireDestination: false. Ces fonctions exigeaient la présence d'un bloc Destination, ce qui générait une erreur 400 dès que l'utilisateur tentait de consulter les valeurs d'une colonne sur un workflow incomplet.
Correctif : Ajout de { requireDestination: false } aux deux appels concernés dans WorkflowUpstreamColumnValuesService. La fonctionnalité a été introduite par BDD-142 mais n'avait pas été propagée à ce service.
7. Performance — chargement des dossiers dépôt
Cause : Le chargement d'un dossier de 6 fichiers × 20 Mo prenait ~47 s pour la prévisualisation et ~42 s pour l'exécution, car :
- Les fichiers S3 étaient téléchargés un par un (séquentiellement)
- Pour la prévisualisation, PapaParse analysait les 20 Mo complets de chaque fichier alors que seules 20 lignes étaient nécessaires
Correctifs :
- Téléchargements parallèles :
Promise.allremplace la bouclefor...ofdansloadMergedFolderParsed. Le temps de téléchargement passe de O(n × durée) à O(durée_max), soit ~42 s → ~13 s pour 6 fichiers. - Troncature CSV pour la prévisualisation :
parseCsvMatrixreçoit unmaxLinesoptionnel. QuandmaxDataRowsest défini (prévisualisation), seules lesheaderRowIndex + maxDataRowspremières lignes du texte décodé sont transmises à PapaParse. Pour 20 lignes d'aperçu, on passe de 20 Mo à quelques Ko par fichier, réduisant le traitement de ~33 s à quasi zéro.
Le temps de chargement total de la prévisualisation passe de ~47 s à ~13 s (limité par le téléchargement parallèle du fichier le plus lourd). L'exécution complète n'est pas affectée :
maxDataRowsn'est jamais passé lors d'un run réel.
Fichiers modifiés
| Fichier | Modification |
|---|---|
back/src/api/workflows/staging/workflow-depot-folder-parse.util.ts | reduce(), colonnes vides finales, décodage Latin-1, troncature CSV avant PapaParse |
back/src/api/workflows/staging/workflow-depot-folder-input.service.ts | Try/catch S3, téléchargements parallèles (Promise.all) |
back/src/api/workflows/workflows.service.ts | tx.workflows.update déplacé en début de transaction (verrou anti-race condition) |
back/src/api/workflows/staging/workflow-upstream-column-values.service.ts | requireDestination: false dans resolveExecutionOrderForRun et executeRun |
back/src/exceptions/workflow.exceptions.ts | Ajout de WorkflowDepotFileLoadException |
back/src/api/workflows/staging/workflow-depot-folder-parse.util.spec.ts | Tests : grands fichiers, Latin-1, délimiteur final |
back/src/api/workflows/staging/workflow-depot-folder-input.service.spec.ts | Test : échec téléchargement S3 |
back/src/api/workflows/staging/workflow-depot-folder-preview.service.spec.ts | Nouveau fichier — tests du service de prévisualisation |
back/src/api/workflows/staging/workflow-upstream-column-values.service.spec.ts | Mise à jour de l'assertion executeRun |
