Deck 1.18 added a start date to cards (Card::$startdate), so a workflow
no longer has to mean "overdue". Each rule now carries a date_field
('due' | 'start'), chosen in the settings form next to board and stacks.
Existing rules keep the due date: the new column's default supplies it
for every stored row, so there is no backfill step, and
Workflow::getDateFieldOrDue() covers entities that were never near the
database as well as hand-edited values. The controller is the one place
that rejects an unknown value instead of normalising it - a client
asking for a rule it would not get should hear about it.
The probe for the field is property_exists(), not method_exists():
Deck's entities declare no getter as real code - getStartdate(),
getDuedate(), even getId() all go through Entity::__call(), which
method_exists() ignores by definition. A method_exists() guard would
have been false on every Deck version and would have turned every
start-date rule into a silent no-op.
assertDeckAvailable() now also refuses Deck older than 1.18.0. That
floor cannot live in appinfo/info.xml - neither the server's nor the app
store's schema has an app-to-app dependency element - but
<nextcloud min-version="34"> already implies it, since 1.18.x is the
only Deck release line published for Nextcloud 34.
isOverdue() becomes hasPassed(): one function for both dates, since the
comparison and the argument against Deck's day-truncating
getDaysUntilDue() are identical for either.
Also carries a pending NcSelect icon-alignment fix that was already in
the working tree.
Bump to 34.1.0.
155 lines
11 KiB
Markdown
155 lines
11 KiB
Markdown
# Deck Workflow-Automatisierung
|
||
|
||
Nextcloud-App für **Nextcloud Hub 26 Spring (Server 34.x)**, die Karten der [Deck](https://github.com/nextcloud/deck)-App automatisch von einem Stapel in einen anderen verschiebt, sobald ihr Fälligkeits- oder Startdatum erreicht ist, und optional eine E-Mail-Benachrichtigung verschickt.
|
||
|
||
Setzt **Deck ab 1.18.0** voraus (die Version, die das Startdatum eingeführt hat) — auf Nextcloud 34 ist das automatisch erfüllt, da 1.18.x die einzige dort freigegebene Deck-Reihe ist.
|
||
|
||
Ersetzt das früher genutzte eigenständige PHP-Cron-Skript durch eine vollwertige App mit Oberfläche in den **persönlichen Einstellungen** — jeder Nutzer kann dort seine eigenen Regeln ("Workflows") anlegen, ohne Admin-Rechte oder Server-Zugriff zu benötigen.
|
||
|
||
## Funktionsumfang
|
||
|
||
Pro Workflow lässt sich konfigurieren:
|
||
|
||
- Quell-Board und Quell-Stapel
|
||
- Ziel-Stapel, in den die Karten verschoben werden
|
||
- **auslösendes Datum: Fälligkeitsdatum oder Startdatum der Karte** (Karten ohne das gewählte Datum werden nie verschoben; Regeln aus älteren Versionen laufen unverändert auf dem Fälligkeitsdatum)
|
||
- optionaler Filter auf zugewiesene Benutzer (Karte muss **mindestens einem** der gewählten Benutzer zugewiesen sein)
|
||
- optionaler Filter auf Labels/Tags (Karte muss **mindestens eines** der gewählten Labels haben)
|
||
- Checkbox: E-Mail-Benachrichtigung an den Workflow-Besitzer, sobald eine Karte verschoben wurde
|
||
|
||
Ein Nutzer kann beliebig viele Workflows anlegen, bearbeiten, deaktivieren oder löschen.
|
||
|
||
## Architektur
|
||
|
||
- **Keine HTTP/OCS-Aufrufe gegen Deck.** Alles Lesen (Boards, Stapel, Karten, Labels, zugewiesene Benutzer) und das Verschieben von Karten läuft ausschließlich über Decks eigene interne PHP-Klassen (`OCA\Deck\Service\CardService`, `StackService`, `BoardService`, `OCA\Deck\Db\CardMapper`, …), aufgelöst per Dependency Injection direkt im selben PHP-Prozess. Sämtlicher Deck-Zugriff ist in [`lib/Service/DeckIntegrationService.php`](lib/Service/DeckIntegrationService.php) gebündelt.
|
||
- **Hintergrundjob statt Seitenaufruf.** [`lib/BackgroundJob/RunWorkflowsJob.php`](lib/BackgroundJob/RunWorkflowsJob.php) ist ein einfacher `Job` ohne eigenes Intervall: Er läuft bei jedem Cron-Durchlauf von Nextcloud (üblicherweise alle 5 Minuten) und ruft [`lib/Service/WorkflowRunner.php`](lib/Service/WorkflowRunner.php) auf.
|
||
- **Rechte-Kontext je Nutzer.** Ein Hintergrundjob hat standardmäßig keinen eingeloggten Nutzer, muss aber die Regeln mehrerer Nutzer in einem einzigen Lauf auswerten. Der `WorkflowRunner` "verkörpert" deshalb für die Dauer der jeweiligen Workflows kurzzeitig den entsprechenden Besitzer. Dazu gehört mehr als `IUserSession::setUser()`: Decks Berechtigungsprüfungen lesen die Session gar nicht, sondern eine Benutzer-ID, die beim Bau des Deck-Containers einmal pro Prozess eingefroren wird. `DeckIntegrationService::beginUserContext()` setzt diesen Wert für die Dauer der Verkörperung auf den Workflow-Besitzer und stellt ihn danach wieder her; schlägt das fehl, wird der Nutzer in diesem Lauf übersprungen, statt unter fremden Rechten zu arbeiten.
|
||
- **E-Mail** läuft über Nextclouds eigenen `IMailer` (nutzt also den in der Nextcloud-Administration hinterlegten Mailserver) und geht an die im Profil des Workflow-Besitzers hinterlegte Adresse.
|
||
|
||
### Wichtiger Hinweis zu Decks internen Klassen
|
||
|
||
`OCA\Deck\*` ist keine dokumentierte, stabile öffentliche API von Deck (keine `@since`-Markierungen, keine offizielle Zusicherung von Abwärtskompatibilität). Diese App verwendet sie trotzdem bewusst direkt, wie es die Vorgabe verlangt — jeder Zugriff läuft defensiv abgesichert über `DeckIntegrationService` (Prüfung, ob Deck aktiviert ist, `class_exists()`-Checks, try/catch mit Logging statt Absturz). Nach größeren Deck-Updates lohnt sich ein Blick ins Nextcloud-Log, falls Workflows plötzlich nicht mehr greifen.
|
||
|
||
## Installation
|
||
|
||
1. App in `apps/workflow_deck_automation` des Nextcloud-Servers ablegen (oder über den Appstore-Build aus der CI, siehe unten).
|
||
2. In der Nextcloud-Administration unter *Apps* aktivieren.
|
||
3. Die [Deck-App](https://apps.nextcloud.com/apps/deck) muss installiert und für die jeweiligen Nutzer aktiviert sein.
|
||
4. Sicherstellen, dass der [Hintergrundjob-Modus](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/background_jobs_configuration.html) auf *Cron (empfohlen)* steht, damit `RunWorkflowsJob` regelmäßig läuft.
|
||
|
||
## Benutzung
|
||
|
||
Jeder Nutzer findet die Einstellungen unter **Persönliche Einstellungen → Deck Workflow-Automatisierung**. Dort können neue Workflows angelegt, bestehende bearbeitet oder gelöscht werden. Die Dropdowns für Board/Stapel/Label/Benutzer werden live aus Deck geladen. Zur Auswahl stehen nur aktive Boards — archivierte und im Papierkorb liegende Boards sowie gelöschte Stapel blendet Deck bzw. die App aus.
|
||
|
||
### Gelöschte Boards und Stapel
|
||
|
||
Ein Workflow verweist per ID auf ein Board und zwei Stapel. Wird eines davon in Deck gelöscht, passiert Folgendes:
|
||
|
||
- **Beim Speichern** werden Board und Stapel geprüft; ein Workflow auf nicht mehr existierende Ziele lässt sich gar nicht erst anlegen.
|
||
- **In der Übersicht** wird ein betroffener Workflow rot markiert („Board nicht mehr vorhanden", „Quell-Stapel nicht mehr vorhanden" …).
|
||
- **Im Hintergrundjob** wird der Workflow automatisch deaktiviert und der Besitzer einmalig per E-Mail informiert — unabhängig davon, ob für den Workflow „E-Mail senden" aktiviert ist, denn hier geht es nicht um verschobene Karten, sondern darum, dass die Automatisierung nicht mehr läuft. Danach bleibt es still: Ein deaktivierter Workflow wird nicht erneut ausgewertet.
|
||
|
||
Ist Deck vorübergehend nicht erreichbar, wird **nicht** deaktiviert — der Job überspringt den Workflow und versucht es beim nächsten Lauf erneut. Archivierte Boards lösen die Deaktivierung ebenfalls nicht aus, sie sind nur in den Auswahlfeldern ausgeblendet.
|
||
|
||
### Gelöschte Labels und entfernte Benutzer
|
||
|
||
Filter sind ein anderer Fall: Sie werden **nicht** deaktiviert. Wird eines von drei ausgewählten Labels gelöscht, greifen die anderen beiden weiter — das abzuschalten wäre falsch.
|
||
|
||
Problematisch ist nur der Grenzfall, dass **alle** Einträge eines Filters verschwunden sind. Der Workflow läuft dann weiter, kann aber keine einzige Karte mehr treffen. Das passiert bei gelöschten Labels und ebenso, wenn ein gefilterter Benutzer die Board-Freigabe verliert — Deck entfernt dabei alle seine Kartenzuweisungen auf diesem Board.
|
||
|
||
Damit dieser Zustand nicht unsichtbar bleibt:
|
||
|
||
- **In der Übersicht** wird der Workflow rot markiert („Label-Filter trifft keine Karte mehr", „Benutzer-Filter trifft keine Karte mehr").
|
||
- **Im Hintergrundjob** landet bei jedem Lauf eine Warnung im `nextcloud.log`.
|
||
|
||
Keine E-Mail und keine Deaktivierung: Ein einziger Board-Klick kann den Filter wieder lebendig machen. Und wie bei Board und Stapel gilt auch hier — lässt sich das Board gerade nicht abfragen, wird nichts gemeldet statt etwas Falsches.
|
||
|
||
## Ausführungstakt
|
||
|
||
Der Hintergrundjob hat **kein eigenes Intervall** und ist auch nicht konfigurierbar: Er läuft bei **jedem** Cron-Durchlauf von Nextcloud. Der Takt ist damit exakt der des System-Crons — üblicherweise alle 5 Minuten, bei einem häufiger eingerichteten Cron entsprechend öfter.
|
||
|
||
> Bis einschließlich v0.1.0 gab es dafür die `config.php`-Option `workflow_deck_automation.interval`. Sie ist entfallen und wird ignoriert; ein vorhandener Eintrag kann aus der `config.php` entfernt werden.
|
||
|
||
### Der Job läuft nur nachts bzw. gar nicht
|
||
|
||
Nextcloud merkt sich in der Spalte `time_sensitive` der Tabelle `oc_jobs`, ob ein Job zeitkritisch ist, und überspringt „unkritische" Jobs außerhalb des Wartungsfensters. `JobList::setLastRun()` setzt diese Spalte nur in eine Richtung — von „zeitkritisch" auf „unkritisch", nie zurück. Wer eine ältere Version dieser App installiert hatte (bis einschließlich v0.1.0 war der Job `TIME_INSENSITIVE`), hat diesen Stempel noch in der Datenbank. Er wird weder durch ein App-Update noch durch `app:enable` zurückgesetzt.
|
||
|
||
Symptom: Ist in der `config.php` ein `maintenance_window_start` gesetzt, läuft der Job nur in diesem 4-Stunden-Fenster und den Rest des Tages gar nicht.
|
||
|
||
Einmalig korrigieren:
|
||
|
||
```sql
|
||
UPDATE oc_jobs SET time_sensitive = 0
|
||
WHERE class = 'OCA\\WorkflowDeckAutomation\\BackgroundJob\\RunWorkflowsJob';
|
||
```
|
||
|
||
Ohne Datenbankzugriff geht es auch über `occ`, indem der Job gelöscht und durch Aus-/Einschalten der App neu registriert wird:
|
||
|
||
```bash
|
||
php occ background-job:list --class 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
|
||
php occ background-job:delete <id>
|
||
php occ app:disable workflow_deck_automation
|
||
php occ app:enable workflow_deck_automation
|
||
```
|
||
|
||
Neuinstallationen sind nicht betroffen.
|
||
|
||
## Entwicklung
|
||
|
||
Voraussetzungen: PHP 8.2+, Composer, Node.js 24+, npm 11+.
|
||
|
||
```bash
|
||
composer install
|
||
npm install
|
||
npm run build # einmaliger Produktions-Build
|
||
npm run watch # Entwicklung mit automatischem Rebuild
|
||
```
|
||
|
||
### Tests & Linting
|
||
|
||
```bash
|
||
composer run lint # php -l über lib/ und tests/
|
||
composer run cs:check # nextcloud/coding-standard (php-cs-fixer)
|
||
composer run test:unit # PHPUnit — reine Filter-Logik, benötigt keine Deck-Installation
|
||
```
|
||
|
||
Ein echter End-to-End-Test (Karte anlegen, Workflow konfigurieren, Hintergrundjob auslösen, Verschiebung + Mail prüfen) lässt sich nur gegen eine echte Nextcloud-34-Instanz mit installierter Deck-App durchführen, z. B. per:
|
||
|
||
```bash
|
||
# Job-ID und letzten Lauf anzeigen
|
||
php occ background-job:list --class 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
|
||
|
||
# Sofort ausführen, ohne auf den nächsten Cron-Durchlauf zu warten
|
||
php occ background-job:execute <id> --force-execute
|
||
|
||
# Oder dauerhaft als Worker laufen lassen (Argument ist die Job-Klasse, keine App-ID)
|
||
php occ background-job:worker 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
|
||
```
|
||
|
||
### Release-Paket bauen
|
||
|
||
```bash
|
||
make appstore
|
||
```
|
||
|
||
Erzeugt `build/artifacts/appstore/workflow_deck_automation.tar.gz`.
|
||
|
||
## CI (Gitea Actions)
|
||
|
||
In [`.gitea/workflows/`](.gitea/workflows/):
|
||
|
||
| Workflow | Zweck |
|
||
| --- | --- |
|
||
| `lint-php.yml` | `php -l` über eine PHP-8.2–8.4-Matrix |
|
||
| `lint-info-xml.yml` | validiert `appinfo/info.xml` gegen das Appstore-XML-Schema |
|
||
| `phpunit.yml` | führt die PHPUnit-Tests aus (SQLite/rein logisch, kein DB-Service nötig) |
|
||
| `build-main.yml` | baut Frontend + Appstore-Archiv und veröffentlicht es als Release-Asset — bei Push auf `main` als rollendes Pre-Release `latest-main`, bei Push eines `v*`-Tags als reguläres Release |
|
||
|
||
## Datenmodell
|
||
|
||
Workflows werden in der Tabelle `wfda_workflows` gespeichert (siehe [`lib/Migration/Version1000Date20260813120000.php`](lib/Migration/Version1000Date20260813120000.php)): eine Zeile pro Workflow, mit `user_id`-Bezug, Board-/Stapel-IDs, JSON-kodierten Filterlisten sowie `enabled`/`notify_email`/`last_run`.
|
||
|
||
## Lizenz
|
||
|
||
AGPL-3.0-or-later
|