MDL-58218 calendar: correct offset using last seen event

Part of MDL-55611 epic.
This commit is contained in:
Ryan Wyllie 2017-03-15 02:50:53 +00:00 committed by Damyon Wiese
parent 0a2b761c1a
commit 5a0974c748
3 changed files with 363 additions and 20 deletions

View File

@ -45,6 +45,9 @@ use core_calendar\local\interfaces\event_vault_interface;
*/
class event_vault implements event_vault_interface {
/**
* @var event_factory_interface $factory Factory for creating events.
*/
private $factory;
/**
@ -75,6 +78,10 @@ class event_vault implements event_vault_interface {
/**
* Retrieve an array of events for the given user and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
*
* @param \stdClass $user The user for whom the events belong
* @param int|null $timesortfrom Events with timesort from this value (inclusive)
* @param int|null $timesortto Events with timesort until this value (inclusive)
@ -101,22 +108,42 @@ class event_vault implements event_vault_interface {
throw new limit_invalid_parameter_exception("Limit must be between 1 and 50 (inclusive)");
}
$lastseentimesort = null;
$params = ['type' => CALENDAR_EVENT_TYPE_ACTION];
$where = ['type = :type'];
if (!is_null($afterevent)) {
$lastseentimesort = $afterevent->get_times()->get_sort_time()->getTimestamp();
}
if ($timesortfrom) {
$where[] = 'timesort >= :timesortfrom';
$params['timesortfrom'] = $timesortfrom;
if ($lastseentimesort && $lastseentimesort >= $timesortfrom) {
$where[] = '((timesort = :timesortfrom1 AND id > :timesortfromid) '.
'OR timesort > :timesortfrom2)';
$params['timesortfromid'] = $afterevent->get_id();
$params['timesortfrom1'] = $lastseentimesort;
$params['timesortfrom2'] = $lastseentimesort;
} else {
$where[] = 'timesort >= :timesortfrom';
$params['timesortfrom'] = $timesortfrom;
}
}
if ($timesortto) {
$where[] = 'timesort <= :timesortto';
$params['timesortto'] = $timesortto;
}
if (!is_null($afterevent)) {
$where[] = 'id > :id';
$params['id'] = $afterevent->get_id();
if ($lastseentimesort && $lastseentimesort > $timesortto) {
// The last seen event from this set is after the time sort range which
// means all events in this range have been seen, so we can just return
// early here.
return [];
} else if ($lastseentimesort && $lastseentimesort == $timesortto) {
$where[] = '((timesort = :timesortto1 AND id > :timesorttoid) OR timesort < :timesortto2)';
$params['timesorttoid'] = $afterevent->get_id();
$params['timesortto1'] = $timesortto;
$params['timesortto2'] = $timesortto;
} else {
$where[] = 'timesort <= :timesortto';
$params['timesortto'] = $timesortto;
}
}
$sql = sprintf("SELECT * FROM {event} WHERE %s ORDER BY timesort ASC, id ASC",
@ -150,8 +177,12 @@ class event_vault implements event_vault_interface {
/**
* Retrieve an array of events for the given user filtered by the course and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
*
* @param \stdClass $user The user for whom the events belong
* @param array $courses The courses to filter by
* @param \stdClass $course The course to filter by
* @param int|null $timesortfrom Events with timesort from this value (inclusive)
* @param int|null $timesortto Events with timesort until this value (inclusive)
* @param event_interface|null $afterevent Only return events after this one
@ -172,6 +203,7 @@ class event_vault implements event_vault_interface {
throw new limit_invalid_parameter_exception("Limit must be between 1 and 50 (inclusive)");
}
$lastseentimesort = null;
$params = [
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course->id,
@ -181,24 +213,42 @@ class event_vault implements event_vault_interface {
'courseid = :courseid',
];
if (!is_null($afterevent)) {
$lastseentimesort = $afterevent->get_times()->get_sort_time()->getTimestamp();
}
if ($timesortfrom) {
$where[] = 'timesort >= :timesortfrom';
$params['timesortfrom'] = $timesortfrom;
if ($lastseentimesort && $lastseentimesort >= $timesortfrom) {
$where[] = '((timesort = :timesortfrom1 AND id > :timesortfromid) '.
'OR timesort > :timesortfrom2)';
$params['timesortfromid'] = $afterevent->get_id();
$params['timesortfrom1'] = $lastseentimesort;
$params['timesortfrom2'] = $lastseentimesort;
} else {
$where[] = 'timesort >= :timesortfrom';
$params['timesortfrom'] = $timesortfrom;
}
}
if ($timesortto) {
$where[] = 'timesort <= :timesortto';
$params['timesortto'] = $timesortto;
}
if (!is_null($afterevent)) {
$where[] = 'id > :id';
$params['id'] = $afterevent->get_id();
if ($lastseentimesort && $lastseentimesort > $timesortto) {
// The last seen event from this set is after the time sort range which
// means all events in this range have been seen, so we can just return
// early here.
return [];
} else if ($lastseentimesort && $lastseentimesort == $timesortto) {
$where[] = '((timesort = :timesortto1 AND id > :timesorttoid) OR timesort < :timesortto2)';
$params['timesorttoid'] = $afterevent->get_id();
$params['timesortto1'] = $timesortto;
$params['timesortto2'] = $timesortto;
} else {
$where[] = 'timesort <= :timesortto';
$params['timesortto'] = $timesortto;
}
}
$wheresql = implode(' AND ', $where);
$sql = sprintf("SELECT * FROM {event} WHERE %s ORDER BY timesort ASC, id ASC", $wheresql);
$offset = 0;
$events = [];
// We need to continue to pull records from the database until we reach

View File

@ -42,6 +42,9 @@ interface event_vault_interface {
/**
* Retrieve an array of events for the given user and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
* @param \stdClass $user The user for whom the events belong
* @param int $timesortfrom Events with timesort from this value (inclusive)
* @param int $timesortto Events with timesort until this value (inclusive)
@ -56,4 +59,28 @@ interface event_vault_interface {
event_interface $afterevent,
$limitnum
);
/**
* Retrieve an array of events for the given user filtered by the course and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
*
* @param \stdClass $user The user for whom the events belong
* @param \stdClass $course The course to filter by
* @param int|null $timesortfrom Events with timesort from this value (inclusive)
* @param int|null $timesortto Events with timesort until this value (inclusive)
* @param event_interface|null $afterevent Only return events after this one
* @param int $limitnum Return at most this number of events
* @return action_event_interface
*/
public function get_action_events_by_course(
\stdClass $user,
\stdClass $course,
$timesortfrom,
$timesortto,
event_interface $afterevent,
$limitnum
);
}

View File

@ -277,6 +277,129 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
$this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
}
/**
* Test that get_action_events_by_timesort returns events between the
* provided timesort values and after the last seen event when one is
* provided. This should work even when the event ids aren't ordered the
* same as the timesort order.
*/
public function test_get_action_events_by_timesort_non_consecutive_ids() {
$this->resetAfterTest(true);
$this->setAdminuser();
$user = $this->getDataGenerator()->create_user();
$factory = new action_event_test_factory();
$vault = new event_vault($factory);
/**
* The events should be ordered by timesort as follows:
*
* 1 event 1
* 2 event 1
* 1 event 2
* 2 event 2
* 1 event 3
* 2 event 3
* 1 event 4
* 2 event 4
* 1 event 5
* 2 event 5
* 1 event 6
* 2 event 6
* 1 event 7
* 2 event 7
* 1 event 8
* 2 event 8
* 1 event 9
* 2 event 9
* 1 event 10
* 2 event 10
*/
$records = [];
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('1 event %d', $i),
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION
]);
}
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('2 event %d', $i),
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION
]);
}
/**
* Expected result set:
*
* 2 event 4
* 1 event 5
* 2 event 5
* 1 event 6
* 2 event 6
* 1 event 7
* 2 event 7
* 1 event 8
* 2 event 8
*/
$aftereventid = $records[3]->id;
$afterevent = $vault->get_event_by_id($aftereventid);
// Offset results by event with name "1 event 4" which has the same timesort
// value as the lower boundary of this query (3). Confirm that the given
// $afterevent is used to ignore events with the same timesortfrom values.
$events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
$this->assertCount(9, $events);
$this->assertEquals('2 event 4', $events[0]->get_name());
$this->assertEquals('2 event 8', $events[8]->get_name());
/**
* Expected result set:
*
* 2 event 4
* 1 event 5
*/
$events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent, 2);
$this->assertCount(2, $events);
$this->assertEquals('2 event 4', $events[0]->get_name());
$this->assertEquals('1 event 5', $events[1]->get_name());
/**
* Expected result set:
*
* 2 event 8
*/
$aftereventid = $records[7]->id;
$afterevent = $vault->get_event_by_id($aftereventid);
// Offset results by event with name "1 event 8" which has the same timesort
// value as the upper boundary of this query (8). Confirm that the given
// $afterevent is used to ignore events with the same timesortto values.
$events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
$this->assertCount(1, $events);
$this->assertEquals('2 event 8', $events[0]->get_name());
/**
* Expected empty result set.
*/
$aftereventid = $records[18]->id;
$afterevent = $vault->get_event_by_id($aftereventid);
// Offset results by event with name "2 event 9" which has a timesort
// value larger than the upper boundary of this query (9 > 8). Confirm
// that the given $afterevent is used for filtering events.
$events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
$this->assertEmpty($events);
}
/**
* Test that get_action_events_by_course returns events after the
* provided timesort value.
@ -611,4 +734,147 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
$this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
$this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
}
/**
* Test that get_action_events_by_course returns events between the
* provided timesort values and after the last seen event when one is
* provided. This should work even when the event ids aren't ordered the
* same as the timesort order.
*/
public function test_get_action_events_by_course_non_consecutive_ids() {
$this->resetAfterTest(true);
$this->setAdminuser();
$user = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$factory = new action_event_test_factory();
$vault = new event_vault($factory);
$this->setAdminuser();
$this->getDataGenerator()->enrol_user($user->id, $course1->id);
$this->getDataGenerator()->enrol_user($user->id, $course2->id);
/**
* The events should be ordered by timesort as follows:
*
* 1 event 1
* 2 event 1
* 1 event 2
* 2 event 2
* 1 event 3
* 2 event 3
* 1 event 4
* 2 event 4
* 1 event 5
* 2 event 5
* 1 event 6
* 2 event 6
* 1 event 7
* 2 event 7
* 1 event 8
* 2 event 8
* 1 event 9
* 2 event 9
* 1 event 10
* 2 event 10
*/
$records = [];
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('1 event %d', $i),
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
]);
}
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('2 event %d', $i),
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
]);
}
// Create events for the other course.
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('3 event %d', $i),
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course2->id,
]);
}
/**
* Expected result set:
*
* 2 event 4
* 1 event 5
* 2 event 5
* 1 event 6
* 2 event 6
* 1 event 7
* 2 event 7
* 1 event 8
* 2 event 8
*/
$aftereventid = $records[3]->id;
$afterevent = $vault->get_event_by_id($aftereventid);
// Offset results by event with name "1 event 4" which has the same timesort
// value as the lower boundary of this query (3). Confirm that the given
// $afterevent is used to ignore events with the same timesortfrom values.
$events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
$this->assertCount(9, $events);
$this->assertEquals('2 event 4', $events[0]->get_name());
$this->assertEquals('2 event 8', $events[8]->get_name());
/**
* Expected result set:
*
* 2 event 4
* 1 event 5
*/
$events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent, 2);
$this->assertCount(2, $events);
$this->assertEquals('2 event 4', $events[0]->get_name());
$this->assertEquals('1 event 5', $events[1]->get_name());
/**
* Expected result set:
*
* 2 event 8
*/
$aftereventid = $records[7]->id;
$afterevent = $vault->get_event_by_id($aftereventid);
// Offset results by event with name "1 event 8" which has the same timesort
// value as the upper boundary of this query (8). Confirm that the given
// $afterevent is used to ignore events with the same timesortto values.
$events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
$this->assertCount(1, $events);
$this->assertEquals('2 event 8', $events[0]->get_name());
/**
* Expected empty result set.
*/
$aftereventid = $records[18]->id;
$afterevent = $vault->get_event_by_id($aftereventid);
// Offset results by event with name "2 event 9" which has a timesort
// value larger than the upper boundary of this query (9 > 8). Confirm
// that the given $afterevent is used for filtering events.
$events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
$this->assertEmpty($events);
}
}