Skip to content

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èreCouvert par
1La sélection d'un dossier ne génère plus d'erreur 500Conversion des erreurs S3 et de parsing en exceptions HTTP explicites
2Les fichiers compatibles sont correctement chargés et prévisualisésCorrection du débordement de pile et de l'encodage
3Un message d'erreur explicite est affiché si le dossier est vide ou inaccessibleWorkflowDepotFileLoadException (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.all remplace la boucle for...of dans loadMergedFolderParsed. 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 : parseCsvMatrix reçoit un maxLines optionnel. Quand maxDataRows est défini (prévisualisation), seules les headerRowIndex + maxDataRows premiè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 : maxDataRows n'est jamais passé lors d'un run réel.

Fichiers modifiés

FichierModification
back/src/api/workflows/staging/workflow-depot-folder-parse.util.tsreduce(), colonnes vides finales, décodage Latin-1, troncature CSV avant PapaParse
back/src/api/workflows/staging/workflow-depot-folder-input.service.tsTry/catch S3, téléchargements parallèles (Promise.all)
back/src/api/workflows/workflows.service.tstx.workflows.update déplacé en début de transaction (verrou anti-race condition)
back/src/api/workflows/staging/workflow-upstream-column-values.service.tsrequireDestination: false dans resolveExecutionOrderForRun et executeRun
back/src/exceptions/workflow.exceptions.tsAjout de WorkflowDepotFileLoadException
back/src/api/workflows/staging/workflow-depot-folder-parse.util.spec.tsTests : grands fichiers, Latin-1, délimiteur final
back/src/api/workflows/staging/workflow-depot-folder-input.service.spec.tsTest : échec téléchargement S3
back/src/api/workflows/staging/workflow-depot-folder-preview.service.spec.tsNouveau fichier — tests du service de prévisualisation
back/src/api/workflows/staging/workflow-upstream-column-values.service.spec.tsMise à jour de l'assertion executeRun