12. Automatiser
L’étape précédente a écrit une relance en Python. Elle marche, elle est testée, et elle a un défaut : pour changer le délai, le destinataire ou le texte, il faut un développeur et un redéploiement.
Kewos a un deuxième outil pour ça : le moteur de flux, dans Workflows. Un déclencheur,
des conditions, des actions, reliés sur une toile. Aucun code, et le client peut l’ouvrir et
le modifier lui-même.
Cette étape livre un flux avec le module, pour que l’automatisation existe dès l’installation au lieu d’être à rebâtir sur chaque instance.
Les deux ne servent pas à la même chose
Ce n’est pas « l’un remplace l’autre ». La frontière est nette et vaut la peine d’être apprise une fois :
| Le code de l’étape 11 | Un flux | |
|---|---|---|
| Ce qui le lance | l’horloge (tous les matins) | un événement : une fiche créée, modifiée, supprimée, ou un clic |
| Qui peut le changer | un développeur | l’utilisateur, dans l’écran Workflows |
| Ce qu’il sait faire | tout ce que Python sait faire | les blocs disponibles : condition, aiguillage, créer, modifier, transition, courriel, notification, agent IA |
| Où il tourne | dans la tâche planifiée | dans la file de travaux, en asynchrone |
Un flux n’a pas de déclencheur horaire. Les déclencheurs sont record.created,
record.updated, record.deleted et « manuel ». Balayer tous les emprunts chaque matin
reste donc le travail du code de l’étape 11. En revanche, réagir au moment où un exemplaire
est déclaré perdu, c’est exactement un flux.
Le fichier
Un module livre ses flux dans flows/, à côté de views/. Rien à appeler : le noyau lit ce
dossier au chargement du module, comme il lit onboarding.json.
modules/library/flows/lost_copy_alert.json :
{ "ref": "library.lost_copy_alert", "module": "library", "name": "Tell the desk when a copy is declared lost", "description": "A lost copy is a decision someone has to act on.", "nodes": [ { "node_key": "loan_changed", "kind": "trigger.record_updated", "name": "A loan changed", "config": { "models": ["library_loan"] }, "pos_x": 40, "pos_y": 120 }, { "node_key": "is_it_lost", "kind": "logic.condition", "name": "Is it lost?", "config": { "expression": { "==": [{ "field": "state" }, "lost"] } }, "pos_x": 320, "pos_y": 120 }, { "node_key": "tell_the_desk", "kind": "action.send_notification", "name": "Tell the desk", "config": { "recipients": [{ "type": "group", "id": "library_librarian" }], "title": "A copy was declared lost", "body": "Loan {{ trigger.record.id }} was marked lost." }, "pos_x": 620, "pos_y": 60 } ], "edges": [ { "from_node": "loan_changed", "to_node": "is_it_lost" }, { "from_node": "is_it_lost", "to_node": "tell_the_desk", "branch": "yes" } ]}Quatre choses méritent d’être lues lentement.
ref. C’est l’identité du flux. Le noyau l’installe une fois et ne le réécrit
jamais : après la première installation, le flux appartient au client. Renommez-le, changez
la condition, ajoutez une branche : une montée de version ne défera rien. C’est l’inverse
d’une vue, qui est réécrite à chaque démarrage.
Il arrive éteint. Un flux livré est en brouillon. Installer un module ne doit jamais se
mettre à envoyer des messages à quelqu’un sans que personne ne l’ait décidé. C’est un
interrupteur dans l’écran Workflows.
Le groupe se désigne par son code, pas par un identifiant numérique : library_librarian
existe sur toutes les instances, l’identifiant 3 ne veut rien dire ailleurs.
"branch": "yes". Une arête n’est suivie que si son libellé correspond exactement à
ce que le bloc a renvoyé. Une condition renvoie yes ou no. Écrire true, ce que tout le
monde écrit la première fois, ne produit aucune erreur : le flux s’arrête simplement
après la condition. Le noyau refuse maintenant ce fichier au chargement, avec le message qui
dit quelles branches le bloc renvoie vraiment. Ce garde-fou existe parce que l’exemple de
cette page l’avait, lui aussi, écrit true.
Ce que ça donne
Après kewos reconcile, le flux est là, sans avoir touché à l’interface :

Et il s’ouvre comme n’importe quel flux dessiné à la main. Le client peut ajouter un envoi de courriel, changer la condition, ou le débrancher :

Le faire tourner pour de vrai
Déclarez un exemplaire perdu depuis la fiche d’emprunt (le bouton Declare lost de l’étape 6). Puis regardez :
# The run, and each step it went through.psql -c "select id, flow_id, state from kws_flow_run order by id desc limit 1"psql -c "select node_key, state from kws_flow_run_step order by id" id | flow_id | state----+---------+--------- 1 | 1 | success
node_key | state---------------+--------- is_it_lost | success tell_the_desk | successEt la notification est arrivée, avec le {{ trigger.record.id }} résolu :
{"title": "A copy was declared lost", "body": "Loan 1 was marked lost."}Le piège qui ne dit rien
Un flux ne s’exécute pas dans la requête qui l’a déclenché. Il est mis dans la file de
travaux, et c’est le worker qui le déroule. Sans worker, le message reste dans la file :
aucune erreur, aucune ligne dans kws_flow_run, rien dans l’interface. Le flux a l’air de ne
pas fonctionner alors qu’il attend.
En écrivant cette page, la première exécution n’a rien produit exactement pour cette raison. La vérification tient en une ligne :
redis-cli LLEN dramatiq:default # > 0 with no worker runningkewos worker # start one, and the queue drainsCe que le client peut faire ensuite
Rien de ce qui suit ne demande de développeur :
- ajouter un
action.send_mailau membre à côté de la notification ; - remplacer la condition par un aiguillage sur l’état et traiter
returnedautrement ; - brancher un
ai.agentqui rédige le message ; - désactiver le flux pendant l’été.
C’est le point de la manœuvre : ce que vous livrez est un point de départ, pas une règle gravée.
Et ensuite
Le module agit seul et réagit aux événements. Reste ce qu’il doit savoir faire des fichiers : Les fichiers.