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:
@@ -43,4 +43,28 @@ class WorkflowRunnerFilterTest extends TestCase {
|
||||
// both match -> true
|
||||
$this->assertTrue(WorkflowRunner::cardMatchesFilters(['alice'], [1], ['alice'], [1]));
|
||||
}
|
||||
|
||||
public function testEmptyFiltersAreNeverDead(): void {
|
||||
// "no restriction" must not be reported as a broken filter, however
|
||||
// little the board has left to offer.
|
||||
$this->assertSame([], WorkflowRunner::findDeadFilters([], [], [], []));
|
||||
$this->assertSame([], WorkflowRunner::findDeadFilters([], [], ['alice'], [1]));
|
||||
}
|
||||
|
||||
public function testFilterSurvivesAsLongAsOneEntryRemains(): void {
|
||||
$this->assertSame([], WorkflowRunner::findDeadFilters(['alice', 'bob'], [1, 2], ['bob'], [2]));
|
||||
}
|
||||
|
||||
public function testFilterIsDeadWhenEveryEntryIsGone(): void {
|
||||
$this->assertSame(['label'], WorkflowRunner::findDeadFilters([], [1, 2], [], [3]));
|
||||
$this->assertSame(['user'], WorkflowRunner::findDeadFilters(['alice'], [], ['bob'], []));
|
||||
$this->assertSame(
|
||||
['label', 'user'],
|
||||
WorkflowRunner::findDeadFilters(['alice'], [1], ['bob'], [2]),
|
||||
);
|
||||
}
|
||||
|
||||
public function testAnEmptyBoardKillsOnlyTheFiltersThatAreSet(): void {
|
||||
$this->assertSame(['label'], WorkflowRunner::findDeadFilters([], [1], [], []));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user