Skip to content

Commit c6a0600

Browse files
authored
Add ability to query JobExecution in start/end time ranges (#87)
* Add ability to query JobExecution in start/end time ranges * Add missing typehint to date query filters * Add where clause for date fields not null * Fixed query dates test sets * Fixed tests ids notation * Fixed checkstyle with named parameters * Fixed Doctrine DBAL storage tests with start time boundaries * Ensure TimeFilter boundaries are correct * Fixed Doctrine DBAL storage tests * Fixed checkstyle * Fixed Doctrine DBAL storage tests * Fix codecov upload
1 parent bd649ef commit c6a0600

File tree

9 files changed

+380
-16
lines changed

9 files changed

+380
-16
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ jobs:
8888
symfony-version: '7.0.*'
8989
coverage-mode: 'xdebug'
9090
- name: "Run tests with phpunit/phpunit"
91-
env:
92-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
9391
run: |
9492
vendor/bin/phpunit --testsuite=Code --coverage-clover coverage.xml
9593
- name: "Upload coverage to Codecov"
96-
uses: codecov/codecov-action@v1
94+
uses: codecov/codecov-action@v5
95+
env:
96+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,38 @@ public function query(Query $query): iterable
175175
$queryTypes['statuses'] = Connection::PARAM_INT_ARRAY;
176176
}
177177

178+
if ($query->startTime()) {
179+
$qb->andWhere($qb->expr()->isNotNull('start_time'));
180+
}
181+
$startDateFrom = $query->startTime()?->getFrom();
182+
if ($startDateFrom) {
183+
$qb->andWhere($qb->expr()->gte('start_time', ':startDateFrom'));
184+
$queryParameters['startDateFrom'] = $startDateFrom;
185+
$queryTypes['startDateFrom'] = Types::DATETIME_IMMUTABLE;
186+
}
187+
$startDateTo = $query->startTime()?->getTo();
188+
if ($startDateTo) {
189+
$qb->andWhere($qb->expr()->lte('start_time', ':startDateTo'));
190+
$queryParameters['startDateTo'] = $startDateTo;
191+
$queryTypes['startDateTo'] = Types::DATETIME_IMMUTABLE;
192+
}
193+
194+
if ($query->endTime()) {
195+
$qb->andWhere($qb->expr()->isNotNull('start_time'));
196+
}
197+
$endDateFrom = $query->endTime()?->getFrom();
198+
if ($endDateFrom) {
199+
$qb->andWhere($qb->expr()->gte('end_time', ':endDateFrom'));
200+
$queryParameters['endDateFrom'] = $endDateFrom;
201+
$queryTypes['endDateFrom'] = Types::DATETIME_IMMUTABLE;
202+
}
203+
$endDateTo = $query->endTime()?->getTo();
204+
if ($endDateTo) {
205+
$qb->andWhere($qb->expr()->lte('end_time', ':endDateTo'));
206+
$queryParameters['endDateTo'] = $endDateTo;
207+
$queryTypes['endDateTo'] = Types::DATETIME_IMMUTABLE;
208+
}
209+
178210
switch ($query->sort()) {
179211
case Query::SORT_BY_START_ASC:
180212
$qb->orderBy('start_time', 'asc');

src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,53 @@ public function queries(): Generator
357357
['import', '789'],
358358
],
359359
];
360+
yield 'Filter start time lower boundary' => [
361+
(new QueryBuilder())
362+
->startTime(new \DateTimeImmutable('2019-07-01T13:00:01+0200'), null),
363+
[
364+
['import', '456'],
365+
],
366+
];
367+
yield 'Filter start time upper boundary' => [
368+
(new QueryBuilder())
369+
->startTime(null, new \DateTimeImmutable('2019-06-30T22:00:00+0200')),
370+
[
371+
['import', '789'],
372+
],
373+
];
374+
yield 'Filter start time boundaries' => [
375+
(new QueryBuilder())
376+
->startTime(
377+
new \DateTimeImmutable('2019-07-01T13:00:01+0200'),
378+
new \DateTimeImmutable('2019-07-01T17:29:29+0200'),
379+
),
380+
[
381+
// none
382+
],
383+
];
384+
yield 'Filter end time lower boundary' => [
385+
(new QueryBuilder())
386+
->endTime(new \DateTimeImmutable('2019-07-01T13:30:01+0200'), null),
387+
[
388+
['import', '456'],
389+
],
390+
];
391+
yield 'Filter end time upper boundary' => [
392+
(new QueryBuilder())
393+
->endTime(null, new \DateTimeImmutable('2019-07-01T18:29:59+0200')),
394+
[
395+
['export', '123'],
396+
],
397+
];
398+
yield 'Filter end time boundaries' => [
399+
(new QueryBuilder())
400+
->endTime(
401+
new \DateTimeImmutable('2019-07-01T13:30:01+0200'),
402+
new \DateTimeImmutable('2019-07-01T18:29:59+0200'),
403+
),
404+
[
405+
],
406+
];
360407
}
361408

362409
public static function assertExecutionIds(array $ids, iterable $executions): void

src/batch/src/Storage/FilesystemJobExecutionStorage.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ public function query(Query $query): iterable
113113
continue;
114114
}
115115

116+
$startTime = $execution->getStartTime();
117+
$startDateFrom = $query->startTime()?->getFrom();
118+
if ($startDateFrom !== null && ($startTime === null || $startTime < $startDateFrom)) {
119+
continue;
120+
}
121+
$startDateTo = $query->startTime()?->getTo();
122+
if ($startDateTo !== null && ($startTime === null || $startTime > $startDateTo)) {
123+
continue;
124+
}
125+
126+
$endTime = $execution->getEndTime();
127+
$endDateFrom = $query->endTime()?->getFrom();
128+
if ($endDateFrom !== null && ($endTime === null || $endTime < $endDateFrom)) {
129+
continue;
130+
}
131+
$endDateTo = $query->endTime()?->getTo();
132+
if ($endDateTo !== null && ($endTime === null || $endTime > $endDateTo)) {
133+
continue;
134+
}
135+
116136
$candidates[] = $execution;
117137
}
118138

src/batch/src/Storage/Query.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ public function __construct(
3333
* @var int[]
3434
*/
3535
private array $statuses,
36+
private ?TimeFilter $startTime,
37+
private ?TimeFilter $endTime,
3638
private ?string $sort,
3739
private int $limit,
38-
private int $offset = 0,
40+
private int $offset,
3941
) {
4042
}
4143

@@ -63,6 +65,16 @@ public function statuses(): array
6365
return $this->statuses;
6466
}
6567

68+
public function startTime(): ?TimeFilter
69+
{
70+
return $this->startTime;
71+
}
72+
73+
public function endTime(): ?TimeFilter
74+
{
75+
return $this->endTime;
76+
}
77+
6678
public function sort(): ?string
6779
{
6880
return $this->sort;

src/batch/src/Storage/QueryBuilder.php

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Yokai\Batch\Storage;
66

7+
use DateTimeInterface;
78
use Yokai\Batch\BatchStatus;
89
use Yokai\Batch\Exception\UnexpectedValueException;
910

@@ -16,6 +17,8 @@
1617
* ->ids(['123', '456'])
1718
* ->jobs(['export', 'import'])
1819
* ->statuses([BatchStatus::RUNNING, BatchStatus::COMPLETED])
20+
* ->startTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30'))
21+
* ->endTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30'))
1922
* ->sort(Query::SORT_BY_END_DESC)
2023
* ->limit(6, 12)
2124
* ->getQuery();
@@ -26,6 +29,8 @@
2629
* $builder->ids(['123', '456']);
2730
* $builder->jobs(['export', 'import']);
2831
* $builder->statuses([BatchStatus::RUNNING, BatchStatus::COMPLETED]);
32+
* $builder->startTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30'));
33+
* $builder->endTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30'));
2934
* $builder->sort(Query::SORT_BY_END_DESC);
3035
* $builder->limit(6, 12);
3136
* $builder->getQuery();
@@ -63,6 +68,10 @@ final class QueryBuilder
6368
*/
6469
private array $statuses = [];
6570

71+
private ?TimeFilter $startTime = null;
72+
73+
private ?TimeFilter $endTime = null;
74+
6675
private ?string $sortBy = null;
6776

6877
private int $limit = 10;
@@ -126,6 +135,44 @@ public function statuses(array $statuses): self
126135
return $this;
127136
}
128137

138+
/**
139+
* Filter executions that started in a frame.
140+
* Both frame boundaries are optional.
141+
* Calling this method with both null boundaries result in removing the filter.
142+
*
143+
* @param DateTimeInterface|null $from Beginning of the time frame
144+
* @param DateTimeInterface|null $to End of the time frame
145+
*/
146+
public function startTime(?DateTimeInterface $from, ?DateTimeInterface $to): self
147+
{
148+
if ($from === null && $to === null) {
149+
$this->startTime = null;
150+
} else {
151+
$this->startTime = new TimeFilter($from, $to);
152+
}
153+
154+
return $this;
155+
}
156+
157+
/**
158+
* Filter executions that ended in a frame.
159+
* Both frame boundaries are optional.
160+
* Calling this method with both null boundaries result in removing the filter.
161+
*
162+
* @param DateTimeInterface|null $from Beginning of the time frame
163+
* @param DateTimeInterface|null $to End of the time frame
164+
*/
165+
public function endTime(?DateTimeInterface $from, ?DateTimeInterface $to): self
166+
{
167+
if ($from === null && $to === null) {
168+
$this->endTime = null;
169+
} else {
170+
$this->endTime = new TimeFilter($from, $to);
171+
}
172+
173+
return $this;
174+
}
175+
129176
/**
130177
* Sort executions.
131178
*
@@ -165,6 +212,15 @@ public function limit(int $limit, int $offset): self
165212
*/
166213
public function getQuery(): Query
167214
{
168-
return new Query($this->jobNames, $this->ids, $this->statuses, $this->sortBy, $this->limit, $this->offset);
215+
return new Query(
216+
$this->jobNames,
217+
$this->ids,
218+
$this->statuses,
219+
$this->startTime,
220+
$this->endTime,
221+
$this->sortBy,
222+
$this->limit,
223+
$this->offset
224+
);
169225
}
170226
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yokai\Batch\Storage;
6+
7+
use DateTimeInterface;
8+
use Yokai\Batch\Exception\UnexpectedValueException;
9+
10+
/**
11+
* DTO with optional time boundaries.
12+
*/
13+
final class TimeFilter
14+
{
15+
public function __construct(
16+
private ?DateTimeInterface $from,
17+
private ?DateTimeInterface $to,
18+
) {
19+
if ($from !== null && $to !== null && $from > $to) {
20+
throw new UnexpectedValueException('TimeFilter expect "from" boundary to be lower than "to" boundary.');
21+
}
22+
}
23+
24+
public function getFrom(): ?DateTimeInterface
25+
{
26+
return $this->from;
27+
}
28+
29+
public function getTo(): ?DateTimeInterface
30+
{
31+
return $this->to;
32+
}
33+
}

src/batch/tests/Storage/FilesystemJobExecutionStorageTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,62 @@ public function query(): \Generator
232232
['list', '20210910'],
233233
],
234234
];
235+
yield 'Filter start time lower boundary' => [
236+
(new QueryBuilder())
237+
->startTime(new \DateTimeImmutable('2021-09-20T10:35:48+0200'), null),
238+
[
239+
['export', '20210920'],
240+
['export', '20210922'],
241+
['list', '20210920'],
242+
],
243+
];
244+
yield 'Filter start time upper boundary' => [
245+
(new QueryBuilder())
246+
->startTime(null, new \DateTimeImmutable('2021-09-20T10:35:50+0200')),
247+
[
248+
['export', '20210920'],
249+
['list', '20210910'],
250+
['list', '20210915'],
251+
],
252+
];
253+
yield 'Filter start time boundaries' => [
254+
(new QueryBuilder())
255+
->startTime(
256+
new \DateTimeImmutable('2021-09-20T10:35:48+0200'),
257+
new \DateTimeImmutable('2021-09-20T10:35:50+0200'),
258+
),
259+
[
260+
['export', '20210920'],
261+
],
262+
];
263+
yield 'Filter end time lower boundary' => [
264+
(new QueryBuilder())
265+
->endTime(new \DateTimeImmutable('2021-09-20T10:35:48+0200'), null),
266+
[
267+
['export', '20210920'],
268+
['export', '20210922'],
269+
['list', '20210920'],
270+
],
271+
];
272+
yield 'Filter end time upper boundary' => [
273+
(new QueryBuilder())
274+
->endTime(null, new \DateTimeImmutable('2021-09-20T10:35:50+0200')),
275+
[
276+
['export', '20210920'],
277+
['list', '20210910'],
278+
['list', '20210915'],
279+
],
280+
];
281+
yield 'Filter end time boundaries' => [
282+
(new QueryBuilder())
283+
->endTime(
284+
new \DateTimeImmutable('2021-09-20T10:35:48+0200'),
285+
new \DateTimeImmutable('2021-09-20T10:35:50+0200'),
286+
),
287+
[
288+
['export', '20210920'],
289+
],
290+
];
235291
}
236292

237293
public function testRetrieveFilePathNotFound(): void

0 commit comments

Comments
 (0)