Skip to content

Commit 349b072

Browse files
committed
Link dashboard stat cards to filtered job lists; align getLength with aborted exclusion
- getLength() now reuses pendingConditions() so it excludes terminally aborted jobs, matching getPendingCount()/getPendingStats(). This fixes the admin "Pending Jobs (new/current)" card header showing a count that the (aborted-aware) pending list below it did not contain. - Status search filter gains pending, running, failed and aborted values (alongside completed/in_progress/scheduled), each matching the corresponding dashboard stat-card count. - Dashboard Scheduled/Pending/Running/Failed stat cards now link to the job index filtered by the matching status, so they are clickable and land on exactly the jobs they count.
1 parent b27c180 commit 349b072

5 files changed

Lines changed: 109 additions & 11 deletions

File tree

src/Model/Filter/QueuedJobsCollection.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,55 @@ public function initialize(): void {
5454

5555
return true;
5656
}
57+
if ($status === 'pending') {
58+
// Waiting to be picked up: never fetched, no failure, due,
59+
// and not aborted. Mirrors the dashboard "Pending" card
60+
// (totalPending minus running and retriable-failed).
61+
$query->where([
62+
'completed IS' => null,
63+
'fetched IS' => null,
64+
'failure_message IS' => null,
65+
'AND' => [
66+
[
67+
'OR' => [
68+
'notbefore <=' => new DateTime(),
69+
'notbefore IS' => null,
70+
],
71+
],
72+
[
73+
'OR' => [
74+
'status IS' => null,
75+
'status !=' => QueuedJobsTable::STATUS_ABORTED,
76+
],
77+
],
78+
],
79+
]);
80+
81+
return true;
82+
}
83+
if ($status === 'running') {
84+
// Picked up by a worker and not yet completed or failed.
85+
$query->where([
86+
'completed IS' => null,
87+
'fetched IS NOT' => null,
88+
'failure_message IS' => null,
89+
]);
90+
91+
return true;
92+
}
93+
if ($status === 'failed') {
94+
// Unfinished jobs with a recorded failure: still-retrying
95+
// ones and terminally aborted ones alike.
96+
$query->where(['completed IS' => null, 'failure_message IS NOT' => null]);
97+
98+
return true;
99+
}
100+
if ($status === 'aborted') {
101+
// Terminally failed: retries exhausted, will never run again.
102+
$query->where(['completed IS' => null, 'status' => QueuedJobsTable::STATUS_ABORTED]);
103+
104+
return true;
105+
}
57106

58107
throw new NotImplementedException('Invalid status type');
59108
},

src/Model/Table/QueuedJobsTable.php

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -327,20 +327,12 @@ public function isQueued(string $reference, ?string $jobTask = null): bool {
327327
* @return int
328328
*/
329329
public function getLength(?string $type = null): int {
330-
$findConf = [
331-
'conditions' => [
332-
'completed IS' => null,
333-
'OR' => [
334-
'notbefore <=' => new DateTime(),
335-
'notbefore IS' => null,
336-
],
337-
],
338-
];
330+
$conditions = $this->pendingConditions();
339331
if ($type !== null) {
340-
$findConf['conditions']['job_task'] = $type;
332+
$conditions['job_task'] = $type;
341333
}
342334

343-
return $this->find('all', ...$findConf)->count();
335+
return $this->find('all', conditions: $conditions)->count();
344336
}
345337

346338
/**

templates/Admin/Queue/index.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@
196196
'count' => $scheduledJobs,
197197
'icon' => 'calendar',
198198
'color' => 'info',
199+
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'scheduled']]),
199200
]) ?>
200201
</div>
201202
<div class="col-md-3 col-sm-6">
@@ -204,6 +205,7 @@
204205
'count' => $pendingJobs,
205206
'icon' => 'clock',
206207
'color' => 'warning',
208+
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'pending']]),
207209
]) ?>
208210
</div>
209211
<div class="col-md-3 col-sm-6">
@@ -212,6 +214,7 @@
212214
'count' => $runningJobs,
213215
'icon' => 'spinner',
214216
'color' => 'primary',
217+
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'running']]),
215218
]) ?>
216219
</div>
217220
<div class="col-md-3 col-sm-6">
@@ -220,6 +223,7 @@
220223
'count' => $failedJobs,
221224
'icon' => 'times-circle',
222225
'color' => 'danger',
226+
'link' => $this->Url->build(['action' => 'index', 'controller' => 'QueuedJobs', '?' => ['status' => 'failed']]),
223227
]) ?>
224228
</div>
225229
</div>

tests/TestCase/Controller/Admin/QueuedJobsControllerTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,39 @@ public function testIndexSearch() {
262262
$this->assertNotContains('bar', $jobTasks);
263263
}
264264

265+
/**
266+
* The status filter supports running, failed and aborted in addition to
267+
* completed/in_progress/scheduled, so the dashboard stat cards can link to
268+
* the matching job list.
269+
*
270+
* @return void
271+
*/
272+
public function testIndexSearchByRunningFailedAborted() {
273+
$this->createJob(['job_task' => 'waiting']);
274+
$this->createJob(['job_task' => 'running', 'fetched' => new DateTime('-1 minute')]);
275+
$this->createJob(['job_task' => 'failing', 'failure_message' => 'boom', 'attempts' => 1]);
276+
$this->createJob(['job_task' => 'dead', 'failure_message' => 'boom', 'attempts' => 3, 'status' => 'aborted']);
277+
278+
$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'pending']]);
279+
$this->assertResponseCode(200);
280+
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
281+
$this->assertSame(['waiting'], $tasks);
282+
283+
$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'running']]);
284+
$this->assertResponseCode(200);
285+
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
286+
$this->assertSame(['running'], $tasks);
287+
288+
$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'failed']]);
289+
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
290+
sort($tasks);
291+
$this->assertSame(['dead', 'failing'], $tasks);
292+
293+
$this->get(['prefix' => 'Admin', 'plugin' => 'Queue', 'controller' => 'QueuedJobs', 'action' => 'index', '?' => ['status' => 'aborted']]);
294+
$tasks = collection($this->viewVariable('queuedJobs'))->extract('job_task')->toList();
295+
$this->assertSame(['dead'], $tasks);
296+
}
297+
265298
/**
266299
* @return void
267300
*/

tests/TestCase/Model/Table/QueuedJobsTableTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,26 @@ public function testGetPendingCountExcludesAborted() {
903903
$this->assertNotContains('aborted-one', $statsRefs);
904904
}
905905

906+
/**
907+
* getLength() backs the dashboard "Pending Jobs (new/current)" card; it must
908+
* exclude aborted jobs so the card total stays consistent with the pending
909+
* list (getPendingStats()), which already excludes them.
910+
*
911+
* @return void
912+
*/
913+
public function testGetLengthExcludesAborted() {
914+
$job = $this->QueuedJobs->newEntity([
915+
'key' => 'key',
916+
'job_task' => 'FooBar',
917+
'reference' => 'len-one',
918+
]);
919+
$this->QueuedJobs->saveOrFail($job);
920+
$this->assertSame(1, $this->QueuedJobs->getLength());
921+
922+
$this->QueuedJobs->markJobAborted($job);
923+
$this->assertSame(0, $this->QueuedJobs->getLength());
924+
}
925+
906926
/**
907927
* Resetting an aborted job for rerun must clear its terminal status so it
908928
* counts as pending again and gets picked up.

0 commit comments

Comments
 (0)