Appearance
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ère | Couvert par |
|---|---|---|
| 1 | Le bloc texte est supprimé de la palette | WorkflowEditorPage.vue — retrait de l'entrée text du tableau palette |
| 2 | Un 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 |
| 3 | La description est optionnelle | Champ description String? en base + @IsOptional() dans le DTO |
| 4 | Au survol d'un nœud sur le canvas, la description est affichée dans un tooltip | WorkflowEditorPage.vue — <q-tooltip> conditionnel v-if="data.description" |
| 5 | Si aucune description n'est renseignée, aucun tooltip n'est affiché | v-if="data.description" — pas de tooltip si valeur falsy |
| 6 | La description est persistée en base | Colonne description TEXT dans workflow_nodes ; traitée dans saveWorkflowCanvas et duplicate |
| 7 | Les workflows existants contenant des blocs texte ne génèrent pas d'erreur | workflow-canvas.model.js — toFlowNode 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
| Fichier | Rôle |
|---|---|
back/prisma/schema.prisma | Ajout du champ description String? sur le modèle workflow_nodes |
sql/tool_22_workflow_node_description.sql | Migration SQL : ALTER TABLE workflow_nodes ADD COLUMN IF NOT EXISTS description TEXT |
back/src/api/workflows/dto/workflow.dto.ts | Ajout de description?: string | null (optionnel, max 1000 chars) dans WorkflowNodeDto |
back/src/api/workflows/workflows.service.ts | Persistance de description dans saveWorkflowCanvas et duplicate |
back/src/api/workflows/workflow.presenter.ts | Exposition de description dans la réponse canvas |
Frontend
| Fichier | Rôle |
|---|---|
front/src/pages/admin/workflows/workflow-canvas.model.js | Mapping de description dans toFlowNode (API → canvas) et toWorkflowNode (canvas → API) |
front/src/composables/useWorkflowActiveSourceBindings.js | Ajout du computed activeNodeDescription (lecture/écriture sur data.description) |
front/src/components/workflows/WorkflowNodeConfigModal.vue | Prop activeNodeDescription, emit update:activeNodeDescription, textarea dans la section Général |
front/src/pages/admin/workflows/WorkflowEditorPage.vue | Suppression du type text de la palette ; branchement de activeNodeDescription à la modal ; <q-tooltip> conditionnel sur les nœuds canvas |
front/src/pages/admin/workflows/workflow-canvas.sync.js | Correction de normalizeParentFlowNode : préservation de description depuis prevData lors des mises à jour Vue Flow partielles |
front/src/pages/admin/workflows/workflow-canvas.persistence.js | Correction de fingerprintNodesFromApi : inclusion de description dans l'empreinte (anti-autosave infini) |
front/src/composables/useWorkflowEditorLifecycle.js | Correction de onCreateWorkflow : fallback numéroté si le nom par défaut est déjà pris |
front/src/composables/useWorkflowEditorAutosave.js | Correction du watcher isNodeConfigModalOpen : guard saveStatus==='saving' pour éviter le double PUT |
front/src/components/workflows/WorkflowNodeConfigModal.vue | Ajout d'un watch sur modelValue pour resynchroniser draftDescription à chaque ouverture |
SQL
| Fichier | Description |
|---|---|
sql/tool_22_workflow_node_description.sql | ALTER TABLE workflow_nodes ADD COLUMN IF NOT EXISTS description TEXT — idempotent, exécutable sans interruption |
Tests
| Fichier | Type | Ce qui est couvert |
|---|---|---|
front/src/__tests__/pages/admin/workflows/workflow-canvas.model.spec.js | Unit | Mapping description dans toFlowNode / toWorkflowNode |
front/src/__tests__/pages/admin/workflows/workflow-canvas.sync.spec.js | Unit | normalizeParentFlowNode préserve description depuis prevData (fix root cause) ; mergeWorkflowCanvasNodes stable sur fusions successives |
front/src/__tests__/pages/admin/workflows/workflow-canvas.persistence.spec.js | Unit | fingerprintNodesFromApi inclut description ; fingerprint différent si description change |
back/src/api/workflows/workflow-presenter.spec.ts | Unit | Exposition description dans la réponse canvas |
front/src/__tests__/composables/useWorkflowEditorLifecycle.spec.js | Unit | Fallback numéroté sur 409 ; ouverture éditeur après création |
front/src/__tests__/composables/useWorkflowEditorAutosave.spec.js | Unit | Pas de double autosave quand saveStatus==='saving' à la fermeture de la modale |
front/src/__tests__/components/workflows/WorkflowNodeConfigModal.draftDescription.spec.js | Unit | draftDescription réinitialisé à l'ouverture de la modale même si le node ID est inchangé |
front/e2e/pages/admin/admin-workflow-bdd-147.spec.js | E2E mock | Palette 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 baseCorrection 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 DBFix : normalizeParentFlowNode applique désormais la règle :
incoming.description !== undefined→ utilise la valeur incoming (peut être null)incoming.description === undefined→ conserveprevData.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 <q-tooltip> 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.
