Skip to content

Commit 0cc55f9

Browse files
committed
TimeGrid: Let entry providers calculate repeating occurrences
By transferring this responsibility, case specific optimizations are possible.
1 parent bb87a0f commit 0cc55f9

File tree

4 files changed

+71
-127
lines changed

4 files changed

+71
-127
lines changed

library/Notifications/Widget/Calendar.php

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use Icinga\Module\Notifications\Widget\TimeGrid\BaseGrid;
1414
use Icinga\Module\Notifications\Widget\TimeGrid\EntryProvider;
1515
use Icinga\Module\Notifications\Widget\TimeGrid\GridStep;
16-
use Icinga\Module\Notifications\Widget\TimeGrid\Util;
1716
use IntlDateFormatter;
1817
use ipl\Html\Attributes;
1918
use ipl\Html\BaseHtmlElement;
@@ -169,37 +168,7 @@ public function addEntry(Entry $entry): self
169168

170169
public function getEntries(): Traversable
171170
{
172-
foreach ($this->entries as $entry) {
173-
$rrule = $entry->getRecurrenceRule();
174-
$start = $entry->getStart();
175-
$end = $entry->getEnd();
176-
177-
if ($rrule) {
178-
$grid = $this->getGrid();
179-
$length = $start->diff($end);
180-
181-
$visibleHours = Util::diffHours($start, $grid->getGridEnd());
182-
$limit = (int) ceil($visibleHours / (Util::diffHours($start, $end) ?: 0.5));
183-
if ($limit > $visibleHours) {
184-
$limit = $visibleHours;
185-
}
186-
187-
$recurrenceStart = (clone $grid->getGridStart())->sub($length);
188-
foreach ($rrule->getNextRecurrences($recurrenceStart, $limit) as $recurrence) {
189-
$recurrenceEnd = (clone $recurrence)->add($length);
190-
$occurrence = (new Entry($entry->getId()))
191-
->setDescription($entry->getDescription())
192-
->setStart($recurrence)
193-
->setEnd($recurrenceEnd)
194-
->setUrl($entry->getUrl())
195-
->setAttendee($entry->getAttendee());
196-
197-
yield $occurrence;
198-
}
199-
} else {
200-
yield $entry;
201-
}
202-
}
171+
yield from $this->entries;
203172
}
204173

205174
protected function assemble()

library/Notifications/Widget/TimeGrid/Entry.php

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use DateTime;
88
use ipl\Html\BaseHtmlElement;
99
use ipl\I18n\Translation;
10-
use ipl\Scheduler\RRule;
1110
use ipl\Web\Url;
1211
use ipl\Web\Widget\Link;
1312

@@ -59,9 +58,6 @@ abstract class Entry extends BaseHtmlElement
5958
/** @var ?ContinuationType The continuation type */
6059
protected $continuationType;
6160

62-
/** @var ?string The recurrence rule */
63-
protected $rrule;
64-
6561
/** @var Url The URL to show this entry */
6662
protected $url;
6763

@@ -181,35 +177,6 @@ public function getContinuationType(): ?string
181177
return $this->continuationType;
182178
}
183179

184-
/**
185-
* Set the recurrence rule of this entry
186-
*
187-
* @param ?string $rrule
188-
*
189-
* @return $this
190-
*/
191-
public function setRecurrenceRule(?string $rrule): self
192-
{
193-
$this->rrule = $rrule;
194-
195-
return $this;
196-
}
197-
198-
/**
199-
* Get the recurrence rule of this entry
200-
*
201-
* @return ?RRule
202-
*/
203-
public function getRecurrenceRule(): ?RRule
204-
{
205-
if ($this->rrule !== null) {
206-
return (new RRule($this->rrule))
207-
->startAt($this->getStart());
208-
}
209-
210-
return null;
211-
}
212-
213180
/**
214181
* Set the URL to show this entry
215182
*

library/Notifications/Widget/Timeline.php

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use DateTime;
99
use Icinga\Module\Notifications\Common\Links;
1010
use Icinga\Module\Notifications\Forms\MoveRotationForm;
11-
use Icinga\Module\Notifications\Forms\RotationConfigForm;
1211
use Icinga\Module\Notifications\Widget\TimeGrid\DynamicGrid;
1312
use Icinga\Module\Notifications\Widget\TimeGrid\EntryProvider;
1413
use Icinga\Module\Notifications\Widget\TimeGrid\GridStep;
@@ -148,53 +147,13 @@ public function getEntries(): Traversable
148147
$occupiedCells = [];
149148
$resultPosition = $maxPriority + 1;
150149
foreach ($rotations as $rotation) {
151-
$actualHandoff = null;
152-
if (RotationConfigForm::EXPERIMENTAL_OVERRIDES) {
153-
$actualHandoff = $rotation->getActualHandoff();
154-
}
155-
156150
$rotationPosition = $maxPriority - $rotation->getPriority();
157-
foreach ($rotation->fetchTimeperiodEntries($this->start) as $entry) {
158-
$rrule = $entry->getRecurrenceRule();
159-
$start = $entry->getStart();
160-
$end = $entry->getEnd();
161-
151+
foreach ($rotation->fetchTimeperiodEntries($this->start, $this->getGrid()->getGridEnd()) as $entry) {
162152
$entry->setPosition($rotationPosition);
163153

164-
if ($rrule) {
165-
$grid = $this->getGrid();
166-
// TODO: Calculate the nearest start possible, where the rotations restarts
167-
$length = $start->diff($end);
168-
169-
$limit = 31; // Mandatory, 1 otherwise, TODO: the rotation should provide a suitable limit
170-
$recurrenceStart = (clone $grid->getGridStart())->sub($length);
171-
foreach ($rrule->getNextRecurrences($recurrenceStart, $limit) as $recurrence) {
172-
$recurrenceEnd = (clone $recurrence)->add($length);
173-
174-
if ($recurrence < $actualHandoff && $recurrenceEnd > $actualHandoff) {
175-
$recurrence = $actualHandoff;
176-
}
177-
178-
$occurrence = (new Entry($entry->getId()))
179-
->setStart($recurrence)
180-
->setEnd($recurrenceEnd)
181-
->setUrl($entry->getUrl())
182-
->setMember($entry->getMember())
183-
->setPosition($entry->getPosition());
154+
yield $entry;
184155

185-
yield $occurrence;
186-
187-
$occupiedCells += $getDesiredCells($occurrence);
188-
}
189-
} else {
190-
if ($start < $actualHandoff && $end > $actualHandoff) {
191-
$entry->setStart($actualHandoff);
192-
}
193-
194-
yield $entry;
195-
196-
$occupiedCells += $getDesiredCells($entry);
197-
}
156+
$occupiedCells += $getDesiredCells($entry);
198157
}
199158
}
200159

library/Notifications/Widget/Timeline/Rotation.php

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44

55
namespace Icinga\Module\Notifications\Widget\Timeline;
66

7+
use DateInterval;
78
use DateTime;
89
use Generator;
910
use Icinga\Module\Notifications\Common\Links;
11+
use Icinga\Module\Notifications\Forms\RotationConfigForm;
12+
use ipl\Scheduler\RRule;
1013
use ipl\Stdlib\Filter;
14+
use Recurr\Frequency;
15+
use Recurr\Rule;
1116

1217
class Rotation
1318
{
@@ -54,25 +59,21 @@ public function getPriority(): int
5459
return $this->model->priority;
5560
}
5661

57-
/**
58-
* Get the actual handoff of the rotation
59-
*
60-
* @return DateTime
61-
*/
62-
public function getActualHandoff(): DateTime
63-
{
64-
return $this->model->actual_handoff;
65-
}
66-
6762
/**
6863
* Get the next occurrence of the rotation
6964
*
7065
* @param DateTime $after The date after which to yield occurrences
66+
* @param DateTime $until The date up to which to yield occurrences
7167
*
7268
* @return Generator<mixed, Entry>
7369
*/
74-
public function fetchTimeperiodEntries(DateTime $after): Generator
70+
public function fetchTimeperiodEntries(DateTime $after, DateTime $until): Generator
7571
{
72+
$actualHandoff = null;
73+
if (RotationConfigForm::EXPERIMENTAL_OVERRIDES) {
74+
$actualHandoff = $this->model->actual_handoff;
75+
}
76+
7677
$entries = $this->model->timeperiod->timeperiod_entry
7778
->with(['member.contact', 'member.contactgroup'])
7879
->filter(Filter::all(
@@ -93,14 +94,62 @@ public function fetchTimeperiodEntries(DateTime $after): Generator
9394
$member->setIcon('users');
9495
}
9596

96-
$entry = new Entry($timeperiodEntry->id);
97-
$entry->setMember($member);
98-
$entry->setStart($timeperiodEntry->start_time);
99-
$entry->setEnd($timeperiodEntry->end_time);
100-
$entry->setRecurrenceRule($timeperiodEntry->rrule);
101-
$entry->setUrl(Links::rotationSettings($this->model->id, $this->model->schedule_id));
97+
if ($timeperiodEntry->rrule) {
98+
$recurrRule = new Rule($timeperiodEntry->rrule);
99+
$limitMultiplier = 1;
100+
$interval = $recurrRule->getInterval(); // Frequency::DAILY
101+
if ($recurrRule->getFreq() === Frequency::WEEKLY) {
102+
$interval *= 7;
103+
if ($recurrRule->getByDay()) {
104+
$limitMultiplier = count($recurrRule->getByDay());
105+
}
106+
} // TODO: Yearly? (Those unoptimized single occurrences)
107+
108+
$before = (clone $after)->setTime(
109+
(int) $timeperiodEntry->start_time->format('H'),
110+
(int) $timeperiodEntry->start_time->format('i')
111+
);
102112

103-
yield $entry;
113+
if ($timeperiodEntry->start_time < $before) {
114+
$daysSinceLatestHandoff = $timeperiodEntry->start_time->diff($before)->days % $interval;
115+
$firstHandoff = (clone $before)->sub(new DateInterval(sprintf('P%dD', $daysSinceLatestHandoff)));
116+
} else {
117+
$firstHandoff = $timeperiodEntry->start_time;
118+
}
119+
120+
$rrule = new RRule($timeperiodEntry->rrule);
121+
$rrule->startAt($firstHandoff);
122+
123+
$length = $timeperiodEntry->start_time->diff($timeperiodEntry->end_time);
124+
$limit = (((int) ceil($after->diff($until)->days / $interval)) + 1) * $limitMultiplier;
125+
foreach ($rrule->getNextRecurrences($firstHandoff, $limit) as $recurrence) {
126+
$recurrenceEnd = (clone $recurrence)->add($length);
127+
if ($recurrence < $actualHandoff && $recurrenceEnd > $actualHandoff) {
128+
$recurrence = $actualHandoff;
129+
}
130+
131+
if ($recurrence >= $until || $recurrenceEnd <= $after) {
132+
// This shouldn't happen often, that's why such entries are just ignored
133+
continue;
134+
}
135+
136+
$occurrence = (new Entry($timeperiodEntry->id))
137+
->setMember($member)
138+
->setStart($recurrence)
139+
->setEnd($recurrenceEnd)
140+
->setUrl(Links::rotationSettings($this->model->id, $this->model->schedule_id));
141+
142+
yield $occurrence;
143+
}
144+
} else {
145+
$entry = (new Entry($timeperiodEntry->id))
146+
->setMember($member)
147+
->setStart($timeperiodEntry->start_time)
148+
->setEnd($timeperiodEntry->end_time)
149+
->setUrl(Links::rotationSettings($this->model->id, $this->model->schedule_id));
150+
151+
yield $entry;
152+
}
104153
}
105154
}
106155
}

0 commit comments

Comments
 (0)