Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/Model/Filter/QueuedJobsCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,55 @@ public function initialize(): void {

return true;
}
if ($status === 'pending') {
// Waiting to be picked up: never fetched, no failure, due,
// and not aborted. Mirrors the dashboard "Pending" card
// (totalPending minus running and retriable-failed).
$query->where([
'completed IS' => null,
'fetched IS' => null,
'failure_message IS' => null,
'AND' => [
[
'OR' => [
'notbefore <=' => new DateTime(),
'notbefore IS' => null,
],
],
[
'OR' => [
'status IS' => null,
'status !=' => QueuedJobsTable::STATUS_ABORTED,
],
],
],
]);

return true;
}
if ($status === 'running') {
// Picked up by a worker and not yet completed or failed.
$query->where([
'completed IS' => null,
'fetched IS NOT' => null,
'failure_message IS' => null,
]);

return true;
}
if ($status === 'failed') {
// Unfinished jobs with a recorded failure: still-retrying
// ones and terminally aborted ones alike.
$query->where(['completed IS' => null, 'failure_message IS NOT' => null]);

return true;
}
if ($status === 'aborted') {
// Terminally failed: retries exhausted, will never run again.
$query->where(['completed IS' => null, 'status' => QueuedJobsTable::STATUS_ABORTED]);

return true;
}

throw new NotImplementedException('Invalid status type');
},
Expand Down
14 changes: 3 additions & 11 deletions src/Model/Table/QueuedJobsTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,20 +327,12 @@ public function isQueued(string $reference, ?string $jobTask = null): bool {
* @return int
*/
public function getLength(?string $type = null): int {
$findConf = [
'conditions' => [
'completed IS' => null,
'OR' => [
'notbefore <=' => new DateTime(),
'notbefore IS' => null,
],
],
];
$conditions = $this->pendingConditions();
if ($type !== null) {
$findConf['conditions']['job_task'] = $type;
$conditions['job_task'] = $type;
}

return $this->find('all', ...$findConf)->count();
return $this->find('all', conditions: $conditions)->count();
}

/**
Expand Down
4 changes: 4 additions & 0 deletions templates/Admin/Queue/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
'count' => $scheduledJobs,
'icon' => 'calendar',
'color' => 'info',
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'scheduled']]),
]) ?>
</div>
<div class="col-md-3 col-sm-6">
Expand All @@ -204,6 +205,7 @@
'count' => $pendingJobs,
'icon' => 'clock',
'color' => 'warning',
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'pending']]),
]) ?>
</div>
<div class="col-md-3 col-sm-6">
Expand All @@ -212,6 +214,7 @@
'count' => $runningJobs,
'icon' => 'spinner',
'color' => 'primary',
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'running']]),
]) ?>
</div>
<div class="col-md-3 col-sm-6">
Expand All @@ -220,6 +223,7 @@
'count' => $failedJobs,
'icon' => 'times-circle',
'color' => 'danger',
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'failed']]),
]) ?>
</div>
</div>
Expand Down
33 changes: 33 additions & 0 deletions tests/TestCase/Controller/Admin/QueuedJobsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,39 @@ public function testIndexSearch() {
$this->assertNotContains('bar', $jobTasks);
}

/**
* The status filter supports running, failed and aborted in addition to
* completed/in_progress/scheduled, so the dashboard stat cards can link to
* the matching job list.
*
* @return void
*/
public function testIndexSearchByRunningFailedAborted() {
$this->createJob(['job_task' => 'waiting']);
$this->createJob(['job_task' => 'running', 'fetched' => new DateTime('-1 minute')]);
$this->createJob(['job_task' => 'failing', 'failure_message' => 'boom', 'attempts' => 1]);
$this->createJob(['job_task' => 'dead', 'failure_message' => 'boom', 'attempts' => 3, 'status' => 'aborted']);

$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'pending']]);
$this->assertResponseCode(200);
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
$this->assertSame(['waiting'], $tasks);

$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'running']]);
$this->assertResponseCode(200);
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
$this->assertSame(['running'], $tasks);

$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'failed']]);
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
sort($tasks);
$this->assertSame(['dead', 'failing'], $tasks);

$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'aborted']]);
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
$this->assertSame(['dead'], $tasks);
}

/**
* @return void
*/
Expand Down
20 changes: 20 additions & 0 deletions tests/TestCase/Model/Table/QueuedJobsTableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,26 @@ public function testGetPendingCountExcludesAborted() {
$this->assertNotContains('aborted-one', $statsRefs);
}

/**
* getLength() backs the dashboard "Pending Jobs (new/current)" card; it must
* exclude aborted jobs so the card total stays consistent with the pending
* list (getPendingStats()), which already excludes them.
*
* @return void
*/
public function testGetLengthExcludesAborted() {
$job = $this->QueuedJobs->newEntity([
'key' => 'key',
'job_task' => 'FooBar',
'reference' => 'len-one',
]);
$this->QueuedJobs->saveOrFail($job);
$this->assertSame(1, $this->QueuedJobs->getLength());

$this->QueuedJobs->markJobAborted($job);
$this->assertSame(0, $this->QueuedJobs->getLength());
}

/**
* Resetting an aborted job for rerun must clear its terminal status so it
* counts as pending again and gets picked up.
Expand Down
Loading