Compare commits
11
Commits
v0.2.0
...
latest-main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28b4edc811 | ||
|
|
0e66319a06 | ||
|
|
43648f3728 | ||
|
|
ce749e63a4 | ||
|
|
b203ba35f8 | ||
|
|
ef973af4b5 | ||
|
|
a95d5e2d8a | ||
|
|
2d2f3ca19c | ||
|
|
a6dde35249 | ||
|
|
7ca728e79c | ||
|
|
be6faa7b42 |
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: gitea-runner-server03
|
runs-on: gitea-runner-server03
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.2', '8.3', '8.4']
|
php-version: ['8.4', '8.5']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: '8.3'
|
php-version: '8.4'
|
||||||
extensions: sqlite, pdo_sqlite
|
extensions: sqlite, pdo_sqlite
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ There is no meaningful way to test the Deck integration itself outside a real Ne
|
|||||||
|
|
||||||
### Release process
|
### Release process
|
||||||
|
|
||||||
|
**Versioning scheme (from 34.0.0 on): `<nextcloud-major>.<minor>.<patch>`.** The leading number tracks the minimum/maximum Nextcloud major version declared in `appinfo/info.xml`'s `<dependencies><nextcloud .../>` (currently `min-version="34" max-version="34"`), not this app's own feature history - so it only changes when the supported Nextcloud major changes (e.g. dropping/adding NC 34 support bumps to `35.0.0`), and resets `minor`/`patch` to `0.0` at that point. Ordinary fixes/features within the same supported NC major bump `minor`/`patch` as usual (semver-ish, without a leading `0.`). Versions before `34.0.0` (`0.1.x`/`0.2.x`) predate this scheme.
|
||||||
|
|
||||||
1. Bump `<version>` in `appinfo/info.xml` and `version` in `package.json` (keep them in sync).
|
1. Bump `<version>` in `appinfo/info.xml` and `version` in `package.json` (keep them in sync).
|
||||||
2. Commit, push to `main`, confirm the run of `.gitea/workflows/build-main.yml` is green.
|
2. Commit, push to `main`, confirm the run of `.gitea/workflows/build-main.yml` is green.
|
||||||
3. `git tag vX.Y.Z && git push origin vX.Y.Z` — this triggers the tag branch of the same workflow, which re-runs the checks, builds the frontend, runs `make appstore`, and attaches `workflow_deck_automation.tar.gz` as the asset on a normal (non-pre-) release for that tag.
|
3. `git tag vX.Y.Z && git push origin vX.Y.Z` — this triggers the tag branch of the same workflow, which re-runs the checks, builds the frontend, runs `make appstore`, and attaches `workflow_deck_automation.tar.gz` as the asset on a normal (non-pre-) release for that tag.
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ This app lets every user configure their own Deck automation workflows from Pers
|
|||||||
|
|
||||||
A background job periodically evaluates all enabled workflows and moves cards whose due date has passed, using Deck's own internal PHP classes only (no HTTP/OCS calls).
|
A background job periodically evaluates all enabled workflows and moves cards whose due date has passed, using Deck's own internal PHP classes only (no HTTP/OCS calls).
|
||||||
]]></description>
|
]]></description>
|
||||||
<version>0.2.0</version>
|
<version>34.0.2</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author mail="patrick@niebel.ing">Patrick Niebeling</author>
|
<author mail="patrick@niebel.ing">Patrick Niebeling</author>
|
||||||
<namespace>WorkflowDeckAutomation</namespace>
|
<namespace>WorkflowDeckAutomation</namespace>
|
||||||
@@ -36,7 +36,7 @@ A background job periodically evaluates all enabled workflows and moves cards wh
|
|||||||
<category>tools</category>
|
<category>tools</category>
|
||||||
<bugs>https://github.com/pniebeling/workflow_deck_automation/issues</bugs>
|
<bugs>https://github.com/pniebeling/workflow_deck_automation/issues</bugs>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<php min-version="8.2" max-version="8.4"/>
|
<php min-version="8.4" max-version="8.5"/>
|
||||||
<database min-version="9.4">pgsql</database>
|
<database min-version="9.4">pgsql</database>
|
||||||
<database>sqlite</database>
|
<database>sqlite</database>
|
||||||
<database min-version="8.0">mysql</database>
|
<database min-version="8.0">mysql</database>
|
||||||
|
|||||||
@@ -483,7 +483,13 @@ class DeckIntegrationService {
|
|||||||
$cardService = $this->resolve(CardService::class);
|
$cardService = $this->resolve(CardService::class);
|
||||||
|
|
||||||
$cards = $cardMapper->findAllByStack($stackId);
|
$cards = $cardMapper->findAllByStack($stackId);
|
||||||
$cards = $cardService->enrichCards($cards);
|
// enrichCards() mutates each $cards entry in place (labels, assigned users, ...) but
|
||||||
|
// *returns* CardDetails wrappers whose own fields (duedate, stackId, ...) were never
|
||||||
|
// copied from the wrapped card - only its overridden jsonSerialize() reads through to
|
||||||
|
// them. Capturing that return value here made every card's getDuedate()/getDaysUntilDue()
|
||||||
|
// resolve to null, so isOverdue() was always false and no workflow ever matched a card.
|
||||||
|
// Keep using the original, now-enriched $cards instead.
|
||||||
|
$cardService->enrichCards($cards);
|
||||||
|
|
||||||
return array_values(array_filter($cards, static function (Card $card) {
|
return array_values(array_filter($cards, static function (Card $card) {
|
||||||
if (method_exists($card, 'getDeletedAt') && (int)$card->getDeletedAt() > 0) {
|
if (method_exists($card, 'getDeletedAt') && (int)$card->getDeletedAt() > 0) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\WorkflowDeckAutomation\Service;
|
namespace OCA\WorkflowDeckAutomation\Service;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
use OCA\Deck\Db\Card;
|
use OCA\Deck\Db\Card;
|
||||||
use OCA\WorkflowDeckAutomation\Db\Workflow;
|
use OCA\WorkflowDeckAutomation\Db\Workflow;
|
||||||
use OCA\WorkflowDeckAutomation\Db\WorkflowMapper;
|
use OCA\WorkflowDeckAutomation\Db\WorkflowMapper;
|
||||||
@@ -165,8 +166,10 @@ class WorkflowRunner {
|
|||||||
// and the workflow wants a mail — this is decoration, not a check.
|
// and the workflow wants a mail — this is decoration, not a check.
|
||||||
$targets = null;
|
$targets = null;
|
||||||
|
|
||||||
|
$now = $this->timeFactory->getDateTime();
|
||||||
|
|
||||||
foreach ($cards as $card) {
|
foreach ($cards as $card) {
|
||||||
if (!self::isOverdue($card->getDaysUntilDue())) {
|
if (!self::isOverdue($card->getDuedate(), $now)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +179,14 @@ class WorkflowRunner {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->logger->info('Card {card} ("{title}") matches workflow {id}; moving to stack {target}.', [
|
||||||
|
'app' => 'workflow_deck_automation',
|
||||||
|
'card' => $card->getId(),
|
||||||
|
'title' => $card->getTitle(),
|
||||||
|
'id' => $workflow->getId(),
|
||||||
|
'target' => $workflow->getTargetStackId(),
|
||||||
|
]);
|
||||||
|
|
||||||
if (!$this->moveCard($workflow, $card)) {
|
if (!$this->moveCard($workflow, $card)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -254,6 +265,14 @@ class WorkflowRunner {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->logger->info('Moved card {card} ("{title}") to stack {target} for workflow {id}.', [
|
||||||
|
'app' => 'workflow_deck_automation',
|
||||||
|
'card' => $card->getId(),
|
||||||
|
'title' => $card->getTitle(),
|
||||||
|
'target' => $workflow->getTargetStackId(),
|
||||||
|
'id' => $workflow->getId(),
|
||||||
|
]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,8 +322,17 @@ class WorkflowRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isOverdue(?int $daysUntilDue): bool {
|
/**
|
||||||
return $daysUntilDue !== null && $daysUntilDue < 0;
|
* Compares the card's actual due timestamp against $now instead of Deck's own
|
||||||
|
* Card::getDaysUntilDue(), which truncates both sides to midnight before diffing
|
||||||
|
* (DateInterval's %a is a whole-day count) and therefore reports 0 - not overdue -
|
||||||
|
* for any card whose due time has already passed today. That silently held back
|
||||||
|
* every card for up to 24h after it actually fell due; comparing the raw datetime
|
||||||
|
* instead matches what Deck's own UI shows (e.g. "vor 2 Stunden") and has no
|
||||||
|
* day-rounding step to get wrong.
|
||||||
|
*/
|
||||||
|
public static function isOverdue(?DateTimeInterface $duedate, DateTimeInterface $now): bool {
|
||||||
|
return $duedate !== null && $duedate < $now;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,6 @@ class PersonalSection implements IIconSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string {
|
public function getIcon(): string {
|
||||||
return $this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg');
|
return $this->urlGenerator->imagePath(Application::APP_ID, 'app.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "workflow_deck_automation",
|
"name": "workflow_deck_automation",
|
||||||
"version": "0.2.0",
|
"version": "34.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Automates moving overdue Deck cards between stacks, configurable per user.",
|
"description": "Automates moving overdue Deck cards between stacks, configurable per user.",
|
||||||
|
|||||||
@@ -4,22 +4,35 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\WorkflowDeckAutomation\Tests\Unit\Service;
|
namespace OCA\WorkflowDeckAutomation\Tests\Unit\Service;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
use OCA\WorkflowDeckAutomation\Service\WorkflowRunner;
|
use OCA\WorkflowDeckAutomation\Service\WorkflowRunner;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class WorkflowRunnerFilterTest extends TestCase {
|
class WorkflowRunnerFilterTest extends TestCase {
|
||||||
public function testIsOverdueReturnsFalseWithoutDueDate(): void {
|
public function testIsOverdueReturnsFalseWithoutDueDate(): void {
|
||||||
$this->assertFalse(WorkflowRunner::isOverdue(null));
|
$this->assertFalse(WorkflowRunner::isOverdue(null, new DateTimeImmutable('2026-01-15 12:00:00')));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIsOverdueReturnsFalseForFutureDueDate(): void {
|
public function testIsOverdueReturnsFalseForFutureDueDate(): void {
|
||||||
$this->assertFalse(WorkflowRunner::isOverdue(0));
|
$now = new DateTimeImmutable('2026-01-15 12:00:00');
|
||||||
$this->assertFalse(WorkflowRunner::isOverdue(3));
|
$this->assertFalse(WorkflowRunner::isOverdue($now->modify('+1 hour'), $now));
|
||||||
|
$this->assertFalse(WorkflowRunner::isOverdue($now->modify('+3 days'), $now));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIsOverdueReturnsTrueForPastDueDate(): void {
|
public function testIsOverdueReturnsTrueForPastDueDate(): void {
|
||||||
$this->assertTrue(WorkflowRunner::isOverdue(-1));
|
$now = new DateTimeImmutable('2026-01-15 12:00:00');
|
||||||
$this->assertTrue(WorkflowRunner::isOverdue(-30));
|
$this->assertTrue(WorkflowRunner::isOverdue($now->modify('-1 hour'), $now));
|
||||||
|
$this->assertTrue(WorkflowRunner::isOverdue($now->modify('-30 days'), $now));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsOverdueReturnsTrueForADueTimeEarlierTheSameDay(): void {
|
||||||
|
// Regression: comparing whole calendar days (as Deck's own
|
||||||
|
// Card::getDaysUntilDue() does) reports a card due earlier today as
|
||||||
|
// "0 days until due", not overdue, until the calendar date rolls
|
||||||
|
// over - holding every card back for up to 24h after it actually
|
||||||
|
// fell due. isOverdue() must compare the real timestamps instead.
|
||||||
|
$now = new DateTimeImmutable('2026-01-15 12:00:00');
|
||||||
|
$this->assertTrue(WorkflowRunner::isOverdue(new DateTimeImmutable('2026-01-15 06:00:00'), $now));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNoFiltersMatchesAnyCard(): void {
|
public function testNoFiltersMatchesAnyCard(): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user