Surface dead filters, and name board and stacks in the moved-card mail
Build package / package (push) Successful in 55s
Build package / php-lint (8.2) (push) Successful in 41s
Build package / php-lint (8.3) (push) Successful in 34s
Build package / php-lint (8.4) (push) Successful in 40s
Build package / xml-lint (push) Successful in 11s
Build package / unit-tests (push) Successful in 42s
Build package / package (push) Successful in 55s
Build package / php-lint (8.2) (push) Successful in 41s
Build package / php-lint (8.3) (push) Successful in 34s
Build package / php-lint (8.4) (push) Successful in 40s
Build package / xml-lint (push) Successful in 11s
Build package / unit-tests (push) Successful in 42s
A filter whose entries have all been deleted from the board was the one failure mode of this app that was completely invisible: the workflow kept running, kept updating last_run and matched nothing, forever. It happens with deleted labels and just as easily when a filtered user loses board access, because Deck's BoardService::deleteAcl() calls assignedUsersMapper->deleteByParticipantOnBoard() and wipes that user's card assignments. Two low-stakes signals, no new column and no migration: - WorkflowRunner::warnAboutDeadFilters() logs a warning on every run. - workflowIssues in PersonalSettings.vue marks the row red. Deliberately no mail and no `enabled = false`. One deleted label out of three is harmless, and even a fully dead filter can be one board edit away from being live again. Both obey the rule the disable path already follows: only positive knowledge. DeckIntegrationService::findFilterOptions() therefore throws instead of degrading to [], unlike listLabels()/listParticipants(), where an empty list only means an empty dropdown. That also fixes an existing false positive of the same family: loadStacksFor() stored [] when the request failed, so a single failed fetch reported "Quell-Stapel und Ziel-Stapel nicht mehr vorhanden" for an untouched board. Failed requests now store null and are read as "unknown". The comparison itself lives in WorkflowRunner::findDeadFilters(), static and side-effect free like cardMatchesFilters(), with unit tests -- notably that an empty filter is never dead, which would otherwise flag every unfiltered workflow. Separately, the moved-card mail now names the board and both stacks. DeckIntegrationService::describeTargets() resolves the titles at most once per workflow run, and only when a card actually moved and the workflow wants a mail; unreadable titles degrade to #<id> rather than costing the user their notification.
This commit is contained in:
@@ -30,7 +30,11 @@ class NotificationMailer {
|
||||
) {
|
||||
}
|
||||
|
||||
public function sendCardMovedNotification(IUser $user, Workflow $workflow, Card $card): void {
|
||||
/**
|
||||
* @param array{board: string, sourceStack: string, targetStack: string} $targets
|
||||
* titles resolved by DeckIntegrationService::describeTargets()
|
||||
*/
|
||||
public function sendCardMovedNotification(IUser $user, Workflow $workflow, Card $card, array $targets): void {
|
||||
$email = $user->getEMailAddress();
|
||||
if ($email === null || $email === '') {
|
||||
$this->logger->info('Skipping notification for workflow ' . $workflow->getId() . ': user ' . $user->getUID() . ' has no email address', [
|
||||
@@ -55,6 +59,13 @@ class NotificationMailer {
|
||||
'The card "%1$s" was moved because it is overdue (workflow "%2$s").',
|
||||
[$card->getTitle(), $workflow->getTitle()],
|
||||
));
|
||||
// Single-argument addBodyText() runs htmlspecialchars() itself, so
|
||||
// these board- and stack-titles (user input) are safe as-is —
|
||||
// unlike the description below, which passes both parts.
|
||||
$template->addBodyText($this->l10n->t(
|
||||
'Board "%1$s": moved from "%2$s" to "%3$s".',
|
||||
[$targets['board'], $targets['sourceStack'], $targets['targetStack']],
|
||||
));
|
||||
$this->addCardDescription($template, $card);
|
||||
$template->addBodyButton($this->l10n->t('Open card'), $cardLink);
|
||||
$template->addFooter();
|
||||
|
||||
Reference in New Issue
Block a user