Every enabled workflow is an edge "source stack -> target stack"; LoopDetector finds circles in that graph, a workflow whose source is its own target being the shortest. Without a check, two workflows A -> B and B -> A move the same cards back and forth on every run, even within a single run, with a notice for each move. The trigger date is ignored on purpose: by start date one way and by due date the other, a card with both dates passed still circles. Chains stay allowed. The controller rejects saving an enabled workflow that would close a loop and names the workflows involved. Workflows stored before this check are skipped by the runner (logged, not disabled) and flagged in the settings list via a new `loop` field from index().
7.9 KiB
Entwicklung
Informationen für Entwickler und Admins, die tiefer einsteigen. Die Nutzer-Doku steht in der README. Ausführliche Hintergründe zu den Stolperfallen, also warum etwas genau so gebaut ist, stehen in CLAUDE.md.
Aufbau
- Keine HTTP/OCS-Aufrufe gegen Deck. Lesen (Boards, Stapel, Karten, Board-Mitglieder) und das Verschieben laufen ausschließlich über Decks interne PHP-Klassen im selben Prozess. Sämtlicher Deck-Zugriff ist in
lib/Service/DeckIntegrationService.phpgebündelt.OCA\Deck\*ist keine stabile öffentliche API. Jeder Zugriff ist deshalb abgesichert:class_exists()-Checks, Prüfung ob Deck aktiviert ist, try/catch mit Logging.- Nach größeren Deck-Updates lohnt sich ein Blick ins Nextcloud-Log.
- Hintergrundjob:
RunWorkflowsJobist ein einfacherJobohne Intervall. Er läuft bei jedem Cron-Durchlauf und ruftWorkflowRunnerauf. - Rechte-Kontext: Der Runner arbeitet die Workflows nach Eigentümer gruppiert ab und setzt Deck jeweils auf diesen Nutzer, per
DeckIntegrationService::beginUserContext(). Decks Berechtigungsprüfungen lesen nämlich nicht die Session, sondern eine pro Prozess festgelegte Nutzer-ID. - E-Mail:
NotificationMailerverschickt über NextcloudsIMailer. Die Empfänger ermitteltDeckIntegrationService::findNotificationRecipients(), die Entscheidungsregel steckt inNotificationPolicy. - Nextcloud-Benachrichtigungen:
NotificationPusherlegt sie überOCP\Notification\IManageran, eine pro Karte, an dieselben Empfänger wie die Mail. Er speichert nur Rohwerte. Den deutschen Text bautNotifier, registriert inApplication::register(). Ist die Appnotificationsfür einen Empfänger nicht aktiv, wird nichts angelegt. Push aufs Handy übernimmt die Notifications-App selbst. - Oberfläche:
src/PersonalSettings.vue(Vue 3,@nextcloud/vue9) spricht übersrc/api.jsmit dem OCS-ControllerWorkflowController.
Datenmodell
Die Tabelle wfda_workflows enthält eine Zeile pro Workflow:
| Spalte | Bedeutung |
|---|---|
board_id, source_stack_id, target_stack_id |
Deck-IDs |
user_id |
Board-Eigentümer, in dessen Namen der Job läuft. Wird bei jedem Lauf aus Deck aktualisiert. |
trigger_date |
due oder start: Fälligkeits- oder Startdatum löst das Verschieben aus |
notify_mode |
off, owner oder board (wer) |
notify_channel |
mail, notification oder both (wie) |
enabled, last_run |
aktiv? / letzter Lauf |
Migrationen in lib/Migration/:
Version1000…legt die Tabelle an.Version1001…entfernt die früheren Filter-Spalten.Version1002…undVersion1003…ersetzennotify_emaildurchnotify_mode. Ein gesetztes Häkchen wird dabei zuowner.Version1004…fügtnotify_channelhinzu. Bestehende Workflows bekommenmail.Version1005…fügttrigger_datehinzu. Bestehende Workflows bekommendue.
Migrationen laufen nur, wenn die App-Version steigt.
Lokales Setup
Voraussetzungen: PHP 8.4+, Composer, Node.js 24 mit npm.
composer install
npm install # nicht `npm ci`: Lockfiles werden bewusst nicht committet
npm run build # Produktions-Build
npm run watch # Entwicklung mit automatischem Rebuild
Tests und Linting
composer run lint # php -l über lib/ und tests/
composer run cs:check # nextcloud/coding-standard (cs:fix korrigiert)
composer run test:unit # PHPUnit
Die Unit-Tests decken nur reine Logik ab, ohne Deck-Installation:
- Datumsvergleich für Fälligkeits- und Startdatum (
WorkflowRunner::hasPassed()) - Auslöser und Begründungstext (
TriggerDate) - Schleifenerkennung (
LoopDetector) - Empfängerregel (
NotificationPolicy::shouldNotify()) - Benachrichtigungsweg (
NotificationPolicy::sendsMail()/sendsNotification())
Die Deck-Anbindung lässt sich nur auf einer echten Nextcloud-Instanz mit Deck testen:
# Job-ID und letzten Lauf anzeigen
php occ background-job:list --class 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
# Sofort ausführen, ohne auf Cron zu warten
php occ background-job:execute <id> --force-execute
Build, CI und Release
make appstore erzeugt build/artifacts/appstore/workflow_deck_automation.tar.gz.
Gebaut und veröffentlicht wird in .gitea/workflows/build-main.yml:
| Job | Zweck |
|---|---|
php-lint |
php -l mit PHP 8.4 und 8.5 |
xml-lint |
prüft appinfo/info.xml gegen das Appstore-Schema |
unit-tests |
PHPUnit |
package |
Frontend-Build und make appstore. Läuft nur, wenn die Checks grün sind. |
Das Paket wird als Release veröffentlicht:
- Bei jedem Push auf
mainals Pre-Releaselatest-main, das jedes Mal neu erzeugt wird. - Bei einem
v*-Tag als reguläres Release. - Bei Pull Requests wird nur gebaut, nicht veröffentlicht.
Versionen folgen dem Schema <Nextcloud-Major>.<minor>.<patch>. Die unterstützte Nextcloud-Version steht in appinfo/info.xml.
Ablauf für ein Release:
- Version in
appinfo/info.xmlundpackage.jsongleich anheben. - Auf
mainpushen und warten, bis die Pipeline grün ist. git tag -a vX.Y.Z -m "…" && git push origin vX.Y.Z.- Optional in den App Store: in Gitea unter Actions den Workflow „Publish to App Store“ von Hand starten und den Tag angeben.
App Store
.gitea/workflows/appstore.yml läuft nur von Hand (workflow_dispatch). Er baut nichts selbst. Er lädt den Tarball eines vorhandenen vX.Y.Z-Release ohne Anmeldung herunter und prüft, ob die Version in info.xml zum Tag passt. Dann signiert er das Archiv (SHA-512) und meldet die Download-URL samt Signatur an die App-Store-API. Der Store lädt den Tarball danach selbst von Gitea. Die Releases müssen deshalb öffentlich erreichbar sein, und ein Tag darf nach dem Store-Upload nicht mehr verschoben werden.
Benötigte Repository-Secrets:
| Secret | Inhalt |
|---|---|
APPSTORE_PRIVATE_KEY |
privater Schlüssel (PEM), zu dem Nextcloud das App-Zertifikat ausgestellt hat |
APPSTORE_TOKEN |
API-Token von https://apps.nextcloud.com/account/token |
Einmalig vorher: Zertifikat über nextcloud/app-certificate-requests beantragen und die App auf apps.nextcloud.com registrieren. Die App wird nicht per Code-Signing (appinfo/signature.json) signiert. Nextcloud prüft Apps, die nicht zum Core gehören, nur dann, wenn diese Datei vorhanden ist.
Einzelheiten, etwa zum Verschieben eines Tags nach einem fehlgeschlagenen Build, stehen in CLAUDE.md.
Fehlersuche: Der Job läuft nur nachts oder gar nicht
Nextcloud überspringt „nicht zeitkritische“ Jobs außerhalb des Wartungsfensters (maintenance_window_start in der config.php). Bis einschließlich v0.1.0 war dieser Job als nicht zeitkritisch markiert. Diese Markierung bleibt in der Spalte oc_jobs.time_sensitive stehen, auch nach App-Updates.
Symptom: Der Job läuft nur im 4-Stunden-Wartungsfenster, oder occ background-job:list zeigt als letzten Lauf 1970-01-01.
Einmalig korrigieren:
UPDATE oc_jobs SET time_sensitive = 0
WHERE class = 'OCA\\WorkflowDeckAutomation\\BackgroundJob\\RunWorkflowsJob';
Oder ohne Datenbankzugriff den Job löschen und durch Aus- und Einschalten der App neu registrieren:
php occ background-job:delete <id>
php occ app:disable workflow_deck_automation
php occ app:enable workflow_deck_automation
Die frühere config.php-Option workflow_deck_automation.interval (bis v0.1.0) wird ignoriert und kann entfernt werden.