Skip to content

BDD-147 — Champ Description intégré aux nœuds

Contexte

Remplace le bloc texte indépendant par un champ Description directement intégré à chaque nœud du workflow. L'objectif est de permettre la documentation du pipeline sans ajouter de bloc supplémentaire sur le canvas, et de rendre cette information disponible au survol sans avoir à ouvrir la configuration.

Critères d'acceptance

#CritèreCouvert par
1Le bloc texte est supprimé de la paletteWorkflowEditorPage.vue — retrait de l'entrée text du tableau palette
2Un champ "Description" est ajouté dans la configuration de chaque nœud, juste en dessous du champ "Nom"WorkflowNodeConfigModal.vue<q-input> conditionnel dans la section Général
3La description est optionnelleChamp description String? en base + @IsOptional() dans le DTO
4Au survol d'un nœud sur le canvas, la description est affichée dans un tooltipWorkflowEditorPage.vue<q-tooltip> conditionnel v-if="data.description"
5Si aucune description n'est renseignée, aucun tooltip n'est affichév-if="data.description" — pas de tooltip si valeur falsy
6La description est persistée en baseColonne description TEXT dans workflow_nodes ; traitée dans saveWorkflowCanvas et duplicate
7Les workflows existants contenant des blocs texte ne génèrent pas d'erreurworkflow-canvas.model.jstoFlowNode et toWorkflowNode conservent la gestion du type text

Correctif : création de workflow quand un nom par défaut existe déjà

Symptôme : cliquer sur "Nouveau workflow" ne produisait aucune réaction visible quand un workflow nommé "Workflow sans nom" existait déjà. La requête POST /workflows retournait 409 ; le composable useWorkflowEditorLifecycle renvoyait null sans notification.

Cause : onCreateWorkflow traitait le résultat WORKFLOW_DUPLICATE_NAME_RESULT comme une sortie silencieuse (bug préexistant sur develop, découvert lors des tests de BDD-147).

Fix : en cas de 409, le composable tente automatiquement des noms numérotés ("Workflow sans nom 2", "Workflow sans nom 3"…, jusqu'à 99) jusqu'à trouver un nom disponible.

javascript
// useWorkflowEditorLifecycle.js
const BASE_NAME = 'Workflow sans nom'
let created = await workflowStore.createWorkflow(BASE_NAME)
if (isWorkflowDuplicateNameResult(created)) {
  let suffix = 2
  while (isWorkflowDuplicateNameResult(created) && suffix <= 99) {
    created = await workflowStore.createWorkflow(`${BASE_NAME} ${suffix++}`)
  }
}

Fichiers modifiés

Backend

FichierRôle
back/prisma/schema.prismaAjout du champ description String? sur le modèle workflow_nodes
sql/tool_22_workflow_node_description.sqlMigration SQL : ALTER TABLE workflow_nodes ADD COLUMN IF NOT EXISTS description TEXT
back/src/api/workflows/dto/workflow.dto.tsAjout de description?: string | null (optionnel, max 1000 chars) dans WorkflowNodeDto
back/src/api/workflows/workflows.service.tsPersistance de description dans saveWorkflowCanvas et duplicate
back/src/api/workflows/workflow.presenter.tsExposition de description dans la réponse canvas

Frontend

FichierRôle
front/src/pages/admin/workflows/workflow-canvas.model.jsMapping de description dans toFlowNode (API → canvas) et toWorkflowNode (canvas → API)
front/src/composables/useWorkflowActiveSourceBindings.jsAjout du computed activeNodeDescription (lecture/écriture sur data.description)
front/src/components/workflows/WorkflowNodeConfigModal.vueProp activeNodeDescription, emit update:activeNodeDescription, textarea dans la section Général
front/src/pages/admin/workflows/WorkflowEditorPage.vueSuppression du type text de la palette ; branchement de activeNodeDescription à la modal ; &lt;q-tooltip&gt; conditionnel sur les nœuds canvas
front/src/pages/admin/workflows/workflow-canvas.sync.jsCorrection de normalizeParentFlowNode : préservation de description depuis prevData lors des mises à jour Vue Flow partielles
front/src/pages/admin/workflows/workflow-canvas.persistence.jsCorrection de fingerprintNodesFromApi : inclusion de description dans l'empreinte (anti-autosave infini)
front/src/composables/useWorkflowEditorLifecycle.jsCorrection de onCreateWorkflow : fallback numéroté si le nom par défaut est déjà pris
front/src/composables/useWorkflowEditorAutosave.jsCorrection du watcher isNodeConfigModalOpen : guard saveStatus==='saving' pour éviter le double PUT
front/src/components/workflows/WorkflowNodeConfigModal.vueAjout d'un watch sur modelValue pour resynchroniser draftDescription à chaque ouverture

SQL

FichierDescription
sql/tool_22_workflow_node_description.sqlALTER TABLE workflow_nodes ADD COLUMN IF NOT EXISTS description TEXT — idempotent, exécutable sans interruption

Tests

FichierTypeCe qui est couvert
front/src/__tests__/pages/admin/workflows/workflow-canvas.model.spec.jsUnitMapping description dans toFlowNode / toWorkflowNode
front/src/__tests__/pages/admin/workflows/workflow-canvas.sync.spec.jsUnitnormalizeParentFlowNode préserve description depuis prevData (fix root cause) ; mergeWorkflowCanvasNodes stable sur fusions successives
front/src/__tests__/pages/admin/workflows/workflow-canvas.persistence.spec.jsUnitfingerprintNodesFromApi inclut description ; fingerprint différent si description change
back/src/api/workflows/workflow-presenter.spec.tsUnitExposition description dans la réponse canvas
front/src/__tests__/composables/useWorkflowEditorLifecycle.spec.jsUnitFallback numéroté sur 409 ; ouverture éditeur après création
front/src/__tests__/composables/useWorkflowEditorAutosave.spec.jsUnitPas de double autosave quand saveStatus==='saving' à la fermeture de la modale
front/src/__tests__/components/workflows/WorkflowNodeConfigModal.draftDescription.spec.jsUnitdraftDescription réinitialisé à l'ouverture de la modale même si le node ID est inchangé
front/e2e/pages/admin/admin-workflow-bdd-147.spec.jsE2E mockPalette sans texte, champ Description visible et positionné sous Nom, pré-rempli, vide si absent

Détails techniques

Stockage

La description est stockée au niveau du nœud (colonne description TEXT NULL dans workflow_nodes), indépendamment du champ configuration JSONB. Ce choix permet un accès direct sans déserialisation JSON et une cohérence avec le champ label.

Flux de données

API (GET /workflows/:uuid)
  → toFlowNode()                     : node.description → data.description
  → canvas                           : data.description (affiché en tooltip au survol)
  → WorkflowNodeConfigModal          : activeNodeDescription (textarea éditable)
  → useWorkflowActiveSourceBindings  : patch data.description dans flowNodes
  → toWorkflowNode()                 : data.description → payload.description
  → API (PUT .../canvas)             : description persisté en base

Correction du bug de perte de description (root cause)

Vue Flow émet @update:nodes lors de toute interaction sur le canvas (déplacement, sélection, redimensionnement). Ces événements transportent souvent des données partielles sans le champ description.

normalizeParentFlowNode dans workflow-canvas.sync.js reconstruisait data depuis zéro à chaque appel, ce qui effaçait silencieusement description avant que l'autosave l'envoie au backend :

Vue Flow update (drag) → onFlowNodesUpdate → normalizeParentFlowNode
  ↳ data.description = undefined  (absent de l'update partiel)
  ↳ autosave lit flowNodes → description = undefined → envoyé null en DB

Fix : normalizeParentFlowNode applique désormais la règle :

  • incoming.description !== undefined → utilise la valeur incoming (peut être null)
  • incoming.description === undefined → conserve prevData.description

La même logique est appliquée dans fingerprintNodesFromApi : l'absence de description dans l'empreinte provoquait un fingerprint toujours différent avant/après save, déclenchant un autosave infini.

Compatibilité

Les workflows existants contenant des nœuds text (bloc note) continuent de fonctionner : toFlowNode et toWorkflowNode conservent la gestion du type text. Le bloc est simplement retiré de la palette pour ne plus être créable.

Tooltip

Le &lt;q-tooltip&gt; Quasar est rendu uniquement si data.description est défini et non vide (v-if="data.description"). Il s'affiche après 300 ms de survol, ancré au-dessus du nœud.