Detect trashed and archived Deck entities, skip archived boards
Build package / xml-lint (push) Successful in 1m55s
Build package / unit-tests (push) Successful in 3m39s
Build package / php-lint (8.4) (push) Successful in 3m45s
Build package / php-lint (8.5) (push) Successful in 3m55s
Build package / package (push) Successful in 3m10s
Build package / xml-lint (push) Successful in 1m55s
Build package / unit-tests (push) Successful in 3m39s
Build package / php-lint (8.4) (push) Successful in 3m45s
Build package / php-lint (8.5) (push) Successful in 3m55s
Build package / package (push) Successful in 3m10s
Deck's Entity getters are magic (__call), so method_exists() never saw getDeletedAt() or getArchived(): trashed cards were moved, trashed boards were neither dropped from "my boards" nor detected by findStackIds(), and every board counted as not archived. DeckIntegrationService now checks the properties with property_exists() via isTrashed()/isArchived(). Workflows on archived boards stay enabled but are skipped by the runner (isBoardArchived(), read through BoardMapper) until the board is unarchived, and the settings list marks them. Archived boards are no longer offered in the board dropdown at all; editing a workflow on one clears board and stacks and says why.
This commit is contained in:
1 parent
9e1a15bab0
commit
4464e5ad3c
5 files changed
+109
-17
No files matched your search
@@ -172,6 +172,26 @@ class DeckIntegrationService {
|
||||
return class_exists(Card::class) && property_exists(Card::class, 'startdate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a Deck board or card sits in Deck's trash (`deleted_at > 0`).
|
||||
*
|
||||
* Read through property_exists(), never method_exists(): Entity getters
|
||||
* are magic (`__call`), so method_exists() is always false for them —
|
||||
* which is how trashed cards used to be moved and trashed boards went
|
||||
* unnoticed.
|
||||
*/
|
||||
private static function isTrashed(object $entity): bool {
|
||||
return property_exists($entity, 'deletedAt') && (int)$entity->getDeletedAt() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a Deck board is archived. Same magic-getter caveat as
|
||||
* isTrashed().
|
||||
*/
|
||||
private static function isArchived(object $entity): bool {
|
||||
return property_exists($entity, 'archived') && (bool)$entity->getArchived();
|
||||
}
|
||||
|
||||
/**
|
||||
* The boards `$userId` owns — the only boards workflows may live on, and
|
||||
* the boards whose workflows that user sees and manages.
|
||||
@@ -181,7 +201,8 @@ class DeckIntegrationService {
|
||||
* the frozen-uid state of BoardService::getUserBoards(). It does return
|
||||
* archived and trashed boards; trashed ones are dropped here, archived
|
||||
* ones are kept but flagged — their workflows still exist and must stay
|
||||
* visible, they just aren't offered as a new target.
|
||||
* visible, but the board is no target for a new or changed workflow, and
|
||||
* the runner skips it.
|
||||
*
|
||||
* Throws instead of degrading to `[]`: an empty list would read as "you
|
||||
* own no boards" and hide every workflow the user has.
|
||||
@@ -198,7 +219,7 @@ class DeckIntegrationService {
|
||||
|
||||
$result = [];
|
||||
foreach ($boards as $board) {
|
||||
if (method_exists($board, 'getDeletedAt') && (int)$board->getDeletedAt() > 0) {
|
||||
if (self::isTrashed($board)) {
|
||||
continue;
|
||||
}
|
||||
$id = (int)$board->getId();
|
||||
@@ -206,7 +227,7 @@ class DeckIntegrationService {
|
||||
$result[] = [
|
||||
'id' => $id,
|
||||
'title' => (is_string($title) && $title !== '') ? $title : ('#' . $id),
|
||||
'archived' => method_exists($board, 'getArchived') && (bool)$board->getArchived(),
|
||||
'archived' => self::isArchived($board),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -253,7 +274,7 @@ class DeckIntegrationService {
|
||||
public function findStackIds(int $boardId, string $userId): ?array {
|
||||
try {
|
||||
$board = $this->resolve(BoardMapper::class)->find($boardId);
|
||||
if (method_exists($board, 'getDeletedAt') && (int)$board->getDeletedAt() > 0) {
|
||||
if (self::isTrashed($board)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -283,6 +304,31 @@ class DeckIntegrationService {
|
||||
return array_map(static fn ($stack) => (int)$stack->getId(), $stacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the board is archived — workflows on archived boards stay
|
||||
* stored and enabled but move nothing until the board is unarchived.
|
||||
*
|
||||
* Read through BoardMapper like findBoardOwner(). `false` if the board
|
||||
* row is gone, so findBrokenTarget() still gets to disable the workflow;
|
||||
* every other failure throws.
|
||||
*
|
||||
* @throws DeckUnavailableException
|
||||
*/
|
||||
public function isBoardArchived(int $boardId): bool {
|
||||
try {
|
||||
$board = $this->resolve(BoardMapper::class)->find($boardId);
|
||||
} catch (DeckUnavailableException $e) {
|
||||
throw $e;
|
||||
} catch (Throwable $e) {
|
||||
if ($this->isMissing($e)) {
|
||||
return false;
|
||||
}
|
||||
throw new DeckUnavailableException('Could not read board ' . $boardId . ': ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return self::isArchived($board);
|
||||
}
|
||||
|
||||
/**
|
||||
* The uid of the board's current owner — the user a workflow on that
|
||||
* board belongs to. Workflows follow the board: when Deck transfers its
|
||||
@@ -462,7 +508,7 @@ class DeckIntegrationService {
|
||||
$cards = $this->resolve(CardMapper::class)->findAllByStack($stackId);
|
||||
|
||||
return array_values(array_filter($cards, static function (Card $card) {
|
||||
if (method_exists($card, 'getDeletedAt') && (int)$card->getDeletedAt() > 0) {
|
||||
if (self::isTrashed($card)) {
|
||||
return false;
|
||||
}
|
||||
return !$card->getArchived() && $card->getDone() === null;
|
||||
|
||||
@@ -229,6 +229,25 @@ class WorkflowRunner {
|
||||
return;
|
||||
}
|
||||
|
||||
// An archived board is intact, just shelved: the workflow is skipped,
|
||||
// not disabled, and resumes once the board is unarchived.
|
||||
try {
|
||||
$archived = $this->deckService->isBoardArchived($workflow->getBoardId());
|
||||
} catch (DeckUnavailableException $e) {
|
||||
$this->logger->warning('Could not check whether the board of workflow {id} is archived: ' . $e->getMessage(), [
|
||||
'app' => 'workflow_deck_automation',
|
||||
'id' => $workflow->getId(),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
if ($archived) {
|
||||
$this->logger->debug('Skipping workflow {id}: its board is archived', [
|
||||
'app' => 'workflow_deck_automation',
|
||||
'id' => $workflow->getId(),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$cards = $this->deckService->getActiveCardsInStack($workflow->getSourceStackId());
|
||||
} catch (DeckUnavailableException $e) {
|
||||
|
||||
Reference in new issue
Block a user