Commit Graph
12 Commits
Author SHA1 Message Date
Patrick Niebeling 9e1a15bab0 Refuse workflows that move cards in a loop
Build package / xml-lint (push) Successful in 1m4s
Build package / php-lint (8.5) (push) Successful in 4m0s
Build package / unit-tests (push) Successful in 4m13s
Build package / php-lint (8.4) (push) Successful in 4m34s
Build package / package (push) Successful in 1m38s
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().
2026-10-01 13:47:32 +02:00
Patrick Niebeling 8268f1068d Let workflows move cards by start date instead of due date
Build package / xml-lint (push) Successful in 3m32s
Build package / unit-tests (push) Successful in 4m49s
Build package / php-lint (8.5) (push) Successful in 4m52s
Build package / php-lint (8.4) (push) Successful in 4m56s
Build package / package (push) Successful in 2m28s
Each workflow now has a trigger date (due or start): the runner compares
either the card's due date or its start date against now, read straight
from Deck's Card entity. Mails and Nextcloud notifications say whether
the card was overdue or reached its start date. Cards without the chosen
date stay put.

Deck only has a start date since 1.18 and Entity getters are magic, so
DeckIntegrationService::supportsStartdate() checks the property; without
it the form rejects start-date workflows and the runner skips them
without disabling them.

WorkflowRunner::isOverdue() becomes hasPassed(), as it now covers both
dates. Existing workflows keep the due date (migration Version1005).
Bumps to 35.4.0 so the migration runs.
2026-10-01 13:19:32 +02:00
Patrick Niebeling 7612344726 Send moved-card notices as Nextcloud notifications too
Build package / xml-lint (push) Successful in 33s
Build package / php-lint (8.5) (push) Successful in 1m48s
Build package / php-lint (8.4) (push) Successful in 1m50s
Build package / unit-tests (push) Successful in 1m52s
Build package / package (push) Successful in 1m38s
Each workflow now has a notify channel (mail, notification or both) next
to its recipient mode. Notifications go out per card through
OCP\Notification\IManager, so they show up in the bell and as push on the
mobile and desktop clients. Without the notifications app nothing is sent.
The one-off "workflow disabled" notice is sent as a notification as well.

Existing workflows keep mail (migration Version1004). Bumps to 35.3.0 so
the migration runs.
2026-09-30 12:31:01 +02:00
Patrick Niebeling 1ffc0765ea Switch the app to German as its only language
The app is used internally only and its settings page was German
already. Mails, controller error messages (shown in the form), the
settings section name and the mail sender are now written in German
directly instead of going through IL10N without any translation files.
Nextcloud's own mail footer is requested in German as well.
2026-09-30 10:39:12 +02:00
Patrick Niebeling 578b235f08 Notify board members about moved cards according to their Deck setting
Build package / unit-tests (push) Failing after 20s
Build package / xml-lint (push) Successful in 46s
Build package / php-lint (8.4) (push) Successful in 1m30s
Build package / php-lint (8.5) (push) Successful in 1m36s
Build package / package (push) Skipped
Replaces the yes/no notify_email flag with notify_mode:
- off:   no mail
- owner: only the board owner, as before (migrated from notify_email=1)
- board: everyone with access to the board, filtered by their own Deck
         "Notifications" setting for it (board:<id>:notify-due: off /
         assigned / all), mirroring Deck's NotificationHelper rules

Recipients are resolved via PermissionService::findUsers() (owner, ACL,
groups and circles expanded), AssignmentMapper::isUserAssigned() and
IConfig, none of which reads Deck's frozen $userId. If they cannot be
resolved, no mail is sent rather than one to the wrong people.

Moved cards are collected over the whole run and each recipient gets a
single digest mail afterwards, in their own language.

Bumps to 35.2.0 so the migrations run.
2026-09-30 10:31:45 +02:00
Patrick Niebeling 0904292c73 Remove the assigned-user and label filters
Build package / php-lint (8.4) (push) Successful in 1m33s
Build package / php-lint (8.5) (push) Successful in 49s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 38s
Build package / package (push) Successful in 1m44s
Workflows now move every overdue card of their source stack. A new
migration drops filter_user_ids and filter_label_ids; workflows that
had a filter keep running unfiltered. The version goes to 35.1.0 so
the migration runs on update.

Removed along with the filters: the labels and participants endpoints
and their Deck lookups, the dead-filter warnings and markers, and
CardService::enrichCards() in the card read. enrichCards() only added
labels, assignees and comment counts, and ran a permission check
against Deck's frozen uid.
2026-09-30 09:55:54 +02:00
Patrick Niebeling e84123654e Tie workflows to the board owner instead of their creator
Build package / php-lint (8.4) (push) Successful in 2m22s
Build package / php-lint (8.5) (push) Successful in 57s
Build package / xml-lint (push) Successful in 18s
Build package / unit-tests (push) Successful in 1m5s
Build package / package (push) Successful in 1m21s
A workflow now belongs to whoever owns its board. The settings page
lists, edits and deletes workflows by board ownership alone, using
Deck's BoardMapper::findAllByOwner(), a single owner query that never
returns shared boards and carries no frozen uid. A transferred board's
workflows therefore show up for the new owner immediately.

The background job still needs one user to run as and mail, so it
looks up the board owner before every run and stores it in user_id
when it changed. Archived boards stay listed so their workflows keep
a name, but are no longer offered or accepted as a target.
2026-09-30 09:38:11 +02:00
Patrick Niebeling ada16cb101 Only allow workflows on owned boards, and show the board in the table
Build package / php-lint (8.4) (push) Successful in 1m7s
Build package / php-lint (8.5) (push) Successful in 56s
Build package / xml-lint (push) Successful in 17s
Build package / unit-tests (push) Successful in 42s
Build package / package (push) Successful in 1m4s
The board list now only contains boards the current user owns, which
also makes the controller's board validation enforce the owner rule.

The workflow table gains a Board column (Board, Titel, Von Stapel,
Nach Stapel, Filter, E-Mail, Aktiv) and is sorted alphabetically by
those columns in that order.
2026-09-30 09:16:09 +02:00
gnilebein 28b4edc811 revert 0e66319a06
Build package / php-lint (8.4) (push) Successful in 43s
Build package / php-lint (8.5) (push) Successful in 40s
Build package / xml-lint (push) Successful in 12s
Build package / unit-tests (push) Successful in 38s
Build package / package (push) Successful in 52s
revert Let each workflow watch the due date or the start date

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.
2026-08-26 07:10:55 +02:00
Patrick Niebeling 0e66319a06 Let each workflow watch the due date or the start date
Build package / php-lint (8.4) (push) Successful in 39s
Build package / php-lint (8.5) (push) Successful in 43s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Failing after 39s
Build package / package (push) Skipped
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.
2026-08-25 15:28:30 +02:00
Patrick Niebeling 0eff367268 Handle workflows whose board or stack was deleted
Build package / php-lint (8.2) (push) Successful in 49s
Build package / php-lint (8.3) (push) Successful in 45s
Build package / php-lint (8.4) (push) Successful in 44s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 45s
Build package / package (push) Successful in 1m3s
Deleting a board or stack left the workflow row pointing at nothing: Deck
keeps the orphaned cards readable until its DeleteCron purges them, so the
run kept failing on the move every few minutes, silently and forever.

- The controller now rejects boards and stacks that do not exist (or are not
  the user's) on create and update, so no new broken row can be stored.
- The settings list marks affected workflows in red instead of showing a row
  that looks healthy.
- The background job disables such a workflow and mails its owner once. The
  mail ignores the notifyEmail flag: that one is about moved cards, this is a
  notice that the automation stopped. It stays a one-off because a disabled
  workflow is no longer picked up.

The check deliberately goes through StackService::findAll() rather than
BoardService::getUserBoards(): Deck injects the current user into BoardService
as a string frozen at construction, so in the job -- one process, many users,
a cached BoardService -- it would answer for the wrong user or for none, and
every workflow on the instance would have been disabled. findStackIds()
returns null only for a genuinely missing or forbidden board and throws for
anything else, so a Deck outage skips the workflow instead of killing it.
2026-08-13 15:47:55 +02:00
Patrick NiebelingandClaude Sonnet 5 300acde27e Add Deck workflow automation Nextcloud app
Lint info.xml / xml-lint (push) Successful in 30s
Lint PHP / php-lint (8.2) (push) Successful in 55s
Lint PHP / php-lint (8.3) (push) Successful in 44s
Lint PHP / php-lint (8.4) (push) Successful in 41s
PHPUnit / unit-tests (push) Failing after 41s
Ports the standalone due-date cron script into a proper Nextcloud 34
app with a personal-settings UI, per-user workflow configuration
(source/target stack, assigned-user and label filters, email
notification), a TimedJob background runner, and Gitea CI pipelines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 10:13:05 +02:00