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.
135 lines
4.6 KiB
PHP
135 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\WorkflowDeckAutomation\Service;
|
|
|
|
use OCA\Deck\Db\Card;
|
|
use OCA\WorkflowDeckAutomation\AppInfo\Application;
|
|
use OCA\WorkflowDeckAutomation\Db\Workflow;
|
|
use OCP\App\IAppManager;
|
|
use OCP\AppFramework\Utility\ITimeFactory;
|
|
use OCP\IUser;
|
|
use OCP\Notification\IManager;
|
|
use Psr\Log\LoggerInterface;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Nextcloud notifications (the bell, and from there push to the mobile and
|
|
* desktop clients) — the counterpart to NotificationMailer. Only the raw
|
|
* values are stored here; Notification\Notifier turns them into text when a
|
|
* client fetches the notification.
|
|
*
|
|
* Without the notifications app nothing is sent and nothing is logged as an
|
|
* error: the app is optional, and IManager would just drop the notification.
|
|
*/
|
|
class NotificationPusher {
|
|
public const SUBJECT_CARD_MOVED = 'card_moved';
|
|
public const SUBJECT_WORKFLOW_DISABLED = 'workflow_disabled';
|
|
|
|
public const OBJECT_CARD = 'card';
|
|
public const OBJECT_WORKFLOW = 'workflow';
|
|
|
|
private const NOTIFICATIONS_APP = 'notifications';
|
|
|
|
public function __construct(
|
|
private IManager $notificationManager,
|
|
private IAppManager $appManager,
|
|
private ITimeFactory $timeFactory,
|
|
private LoggerInterface $logger,
|
|
) {
|
|
}
|
|
|
|
public function isAvailableFor(IUser $user): bool {
|
|
try {
|
|
return $this->appManager->isEnabledForUser(self::NOTIFICATIONS_APP, $user);
|
|
} catch (Throwable $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* One notification per card, unlike the mail: each one links its card
|
|
* and can be dismissed on its own. An older notification about the same
|
|
* card is removed first, so a card that comes back and falls due again
|
|
* is not listed twice.
|
|
*
|
|
* @param non-empty-list<array{workflow: Workflow, card: Card, targets: array{board: string, sourceStack: string, targetStack: string}}> $entries
|
|
*/
|
|
public function sendCardsMovedNotification(IUser $recipient, array $entries): void {
|
|
if (!$this->isAvailableFor($recipient)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($entries as $entry) {
|
|
$workflow = $entry['workflow'];
|
|
$card = $entry['card'];
|
|
$targets = $entry['targets'];
|
|
|
|
try {
|
|
$this->dismiss($recipient, self::OBJECT_CARD, (string)$card->getId(), self::SUBJECT_CARD_MOVED);
|
|
|
|
$notification = $this->notificationManager->createNotification();
|
|
$notification->setApp(Application::APP_ID)
|
|
->setUser($recipient->getUID())
|
|
->setDateTime($this->timeFactory->getDateTime())
|
|
->setObject(self::OBJECT_CARD, (string)$card->getId())
|
|
->setSubject(self::SUBJECT_CARD_MOVED, [
|
|
'cardTitle' => (string)$card->getTitle(),
|
|
'boardId' => $workflow->getBoardId(),
|
|
'boardTitle' => $targets['board'],
|
|
'sourceStackId' => $workflow->getSourceStackId(),
|
|
'sourceStackTitle' => $targets['sourceStack'],
|
|
'targetStackId' => $workflow->getTargetStackId(),
|
|
'targetStackTitle' => $targets['targetStack'],
|
|
'triggerDate' => (string)$workflow->getTriggerDate(),
|
|
]);
|
|
$this->notificationManager->notify($notification);
|
|
} catch (Throwable $e) {
|
|
$this->logger->error('Could not notify ' . $recipient->getUID() . ' about moved card ' . $card->getId() . ': ' . $e->getMessage(), [
|
|
'app' => Application::APP_ID,
|
|
'exception' => $e,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Companion to NotificationMailer::sendWorkflowDisabledNotification().
|
|
*
|
|
* @param WorkflowRunner::TARGET_* $brokenTarget
|
|
*/
|
|
public function sendWorkflowDisabledNotification(IUser $user, Workflow $workflow, string $brokenTarget): void {
|
|
if (!$this->isAvailableFor($user)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$notification = $this->notificationManager->createNotification();
|
|
$notification->setApp(Application::APP_ID)
|
|
->setUser($user->getUID())
|
|
->setDateTime($this->timeFactory->getDateTime())
|
|
->setObject(self::OBJECT_WORKFLOW, (string)$workflow->getId())
|
|
->setSubject(self::SUBJECT_WORKFLOW_DISABLED, [
|
|
'workflowTitle' => (string)$workflow->getTitle(),
|
|
'brokenTarget' => $brokenTarget,
|
|
]);
|
|
$this->notificationManager->notify($notification);
|
|
} catch (Throwable $e) {
|
|
$this->logger->error('Could not notify ' . $user->getUID() . ' about disabled workflow ' . $workflow->getId() . ': ' . $e->getMessage(), [
|
|
'app' => Application::APP_ID,
|
|
'exception' => $e,
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function dismiss(IUser $user, string $objectType, string $objectId, string $subject): void {
|
|
$notification = $this->notificationManager->createNotification();
|
|
$notification->setApp(Application::APP_ID)
|
|
->setUser($user->getUID())
|
|
->setObject($objectType, $objectId)
|
|
->setSubject($subject);
|
|
$this->notificationManager->markProcessed($notification);
|
|
}
|
|
}
|