Issue #3310286: Add $timestamp parameter to group.membership service methods
This commit is contained in:
parent
d154db811c
commit
7c9ef9a8bd
|
@ -168,17 +168,21 @@ access controls are respected.
|
|||
|
||||
**Methods**:
|
||||
|
||||
`hasGroup($asset)` - Check if an asset is a member of a group. Returns a
|
||||
boolean.
|
||||
`hasGroup($asset, $timestamp = NULL)` - Check if an asset is a member of a group,
|
||||
optionally at a given timestamp (defaults to current time). Returns a boolean.
|
||||
|
||||
`getGroup($asset)` - Get group assets that an asset is a member of. Returns an
|
||||
`getGroup($asset, $timestamp = NULL)` - Get group assets that an asset is a member
|
||||
of, optionally at a given timestamp (defaults to current time). Returns an
|
||||
array of asset entities.
|
||||
|
||||
`getGroupAssignmentLog($asset)` - Find the latest group assignment log that
|
||||
references an asset. Returns a log entity, or `NULL` if no logs were found.
|
||||
`getGroupAssignmentLog($asset, $timestamp = NULL)` - Find the latest group
|
||||
assignment log that references an asset, optionally at a given timestamp
|
||||
(defaults to current time). Returns a log entity, or `NULL` if no logs were
|
||||
found.
|
||||
|
||||
`getGroupMembers($groups, $recurse)` - Get assets that are members of groups,
|
||||
optionally recursing into child groups.
|
||||
`getGroupMembers($groups, $recurse = TRUE, $timestamp = NULL)` - Get assets that
|
||||
are members of groups, optionally recursing into child groups, and optionally
|
||||
at a given timestamp (defaults to current time).
|
||||
|
||||
**Example usage:**
|
||||
|
||||
|
|
|
@ -71,40 +71,53 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasGroup(AssetInterface $asset): bool {
|
||||
$log = $this->getGroupAssignmentLog($asset);
|
||||
public function hasGroup(AssetInterface $asset, $timestamp = NULL): bool {
|
||||
|
||||
// Load the group assignment log. Bail if empty.
|
||||
$log = $this->getGroupAssignmentLog($asset, $timestamp);
|
||||
if (empty($log)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Return emptiness of the group references.
|
||||
return !$log->get(static::LOG_FIELD_GROUP)->isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroup(AssetInterface $asset): array {
|
||||
$log = $this->getGroupAssignmentLog($asset);
|
||||
public function getGroup(AssetInterface $asset, $timestamp = NULL): array {
|
||||
|
||||
// Load the group assignment log. Bail if empty.
|
||||
$log = $this->getGroupAssignmentLog($asset, $timestamp);
|
||||
if (empty($log)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Return referenced entities.
|
||||
return $log->{static::LOG_FIELD_GROUP}->referencedEntities() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroupAssignmentLog(AssetInterface $asset): ?LogInterface {
|
||||
public function getGroupAssignmentLog(AssetInterface $asset, $timestamp = NULL): ?LogInterface {
|
||||
|
||||
// If the asset is new, no group assignment logs will reference it.
|
||||
if ($asset->isNew()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If $timestamp is NULL, use the current time.
|
||||
if (is_null($timestamp)) {
|
||||
$timestamp = $this->time->getRequestTime();
|
||||
}
|
||||
|
||||
// Query for group assignment logs that reference the asset.
|
||||
// We do not check access on the logs to ensure that none are filtered out.
|
||||
$options = [
|
||||
'asset' => $asset,
|
||||
'timestamp' => $this->time->getRequestTime(),
|
||||
'timestamp' => $timestamp,
|
||||
'status' => 'done',
|
||||
'limit' => 1,
|
||||
];
|
||||
|
@ -134,7 +147,7 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroupMembers(array $groups, bool $recurse = TRUE): array {
|
||||
public function getGroupMembers(array $groups, bool $recurse = TRUE, $timestamp = NULL): array {
|
||||
|
||||
// Get group ids.
|
||||
$group_ids = array_map(function (AssetInterface $group) {
|
||||
|
@ -146,6 +159,11 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
return [];
|
||||
}
|
||||
|
||||
// If $timestamp is NULL, use the current time.
|
||||
if (is_null($timestamp)) {
|
||||
$timestamp = $this->time->getRequestTime();
|
||||
}
|
||||
|
||||
// Build query for group members.
|
||||
$query = "
|
||||
-- Select asset IDs from the asset base table.
|
||||
|
@ -182,7 +200,7 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
-- Exclude records with future log entries.
|
||||
AND lfd2.id IS NULL";
|
||||
$args = [
|
||||
':timestamp' => $this->time->getRequestTime(),
|
||||
':timestamp' => $timestamp,
|
||||
':group_ids[]' => $group_ids,
|
||||
];
|
||||
$result = $this->database->query($query, $args)->fetchAll();
|
||||
|
@ -204,7 +222,7 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
return $asset->bundle() === 'group';
|
||||
});
|
||||
// Use array_replace so that numeric keys are preserved.
|
||||
$assets = array_replace($assets, $this->getGroupMembers($groups));
|
||||
$assets = array_replace($assets, $this->getGroupMembers($groups, $recurse, $timestamp));
|
||||
}
|
||||
return $assets;
|
||||
}
|
||||
|
|
|
@ -15,33 +15,42 @@ interface GroupMembershipInterface {
|
|||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The Asset entity.
|
||||
* @param int|null $timestamp
|
||||
* Include logs with a timestamp less than or equal to this.
|
||||
* If this is NULL (default), the current time will be used.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the asset is a member of a group, FALSE otherwise.
|
||||
*/
|
||||
public function hasGroup(AssetInterface $asset): bool;
|
||||
public function hasGroup(AssetInterface $asset, $timestamp = NULL): bool;
|
||||
|
||||
/**
|
||||
* Get group assets that an asset is a member of.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The Asset entity.
|
||||
* @param int|null $timestamp
|
||||
* Include logs with a timestamp less than or equal to this.
|
||||
* If this is NULL (default), the current time will be used.
|
||||
*
|
||||
* @return array
|
||||
* Returns an array of assets.
|
||||
*/
|
||||
public function getGroup(AssetInterface $asset): array;
|
||||
public function getGroup(AssetInterface $asset, $timestamp = NULL): array;
|
||||
|
||||
/**
|
||||
* Find the latest group assignment log that references an asset.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The asset entity.
|
||||
* @param int|null $timestamp
|
||||
* Include logs with a timestamp less than or equal to this.
|
||||
* If this is NULL (default), the current time will be used.
|
||||
*
|
||||
* @return \Drupal\log\Entity\LogInterface|null
|
||||
* A log entity, or NULL if no logs were found.
|
||||
*/
|
||||
public function getGroupAssignmentLog(AssetInterface $asset): ?LogInterface;
|
||||
public function getGroupAssignmentLog(AssetInterface $asset, $timestamp = NULL): ?LogInterface;
|
||||
|
||||
/**
|
||||
* Get assets that are members of groups.
|
||||
|
@ -51,10 +60,13 @@ interface GroupMembershipInterface {
|
|||
* @param bool $recurse
|
||||
* Boolean: whether or not to recurse and load members of sub-groups.
|
||||
* Defaults to TRUE.
|
||||
* @param int|null $timestamp
|
||||
* Include logs with a timestamp less than or equal to this.
|
||||
* If this is NULL (default), the current time will be used.
|
||||
*
|
||||
* @return \Drupal\asset\Entity\AssetInterface[]
|
||||
* An array of asset objects indexed by their IDs.
|
||||
*/
|
||||
public function getGroupMembers(array $groups, bool $recurse = TRUE): array;
|
||||
public function getGroupMembers(array $groups, bool $recurse = TRUE, $timestamp = NULL): array;
|
||||
|
||||
}
|
||||
|
|
|
@ -260,6 +260,95 @@ class GroupTest extends KernelTestBase {
|
|||
$this->assertCorrectAssets([$animal, $second_animal], $group_members, TRUE, 'Group members from multiple groups can be queried together.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test past/future group membership.
|
||||
*/
|
||||
public function testGroupMembershipTimestamp() {
|
||||
|
||||
// Create an asset.
|
||||
/** @var \Drupal\asset\Entity\AssetInterface $asset */
|
||||
$asset = Asset::create([
|
||||
'type' => 'animal',
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 'active',
|
||||
]);
|
||||
$asset->save();
|
||||
|
||||
// Create two group assets.
|
||||
/** @var \Drupal\asset\Entity\AssetInterface[] $groups */
|
||||
$group = Asset::create([
|
||||
'type' => 'group',
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 'active',
|
||||
]);
|
||||
$group->save();
|
||||
$groups[] = $group;
|
||||
$group = Asset::create([
|
||||
'type' => 'group',
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 'active',
|
||||
]);
|
||||
$group->save();
|
||||
$groups[] = $group;
|
||||
|
||||
// Create a series of timestamps, each 24 hours apart.
|
||||
$now = \Drupal::time()->getRequestTime();
|
||||
$timestamps = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$timestamps[$i] = $now + (86400 * $i);
|
||||
}
|
||||
|
||||
// Create a series of logs that assign the asset to each of the groups, and
|
||||
// a third that removes the asset from all groups.
|
||||
/** @var \Drupal\log\Entity\LogInterface[] $logs */
|
||||
$logs = [];
|
||||
$group_assignments = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$group_assignments[] = ['target_id' => $groups[$i]->id()];
|
||||
}
|
||||
$group_assignments[] = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$log = Log::create([
|
||||
'type' => 'test',
|
||||
'timestamp' => $timestamps[$i],
|
||||
'status' => 'done',
|
||||
'asset' => ['target_id' => $asset->id()],
|
||||
'is_group_assignment' => TRUE,
|
||||
'group' => $group_assignments[$i],
|
||||
]);
|
||||
$log->save();
|
||||
$logs[] = $log;
|
||||
}
|
||||
|
||||
// Confirm that the asset has no group membership before all logs.
|
||||
$timestamp = $now - 86400;
|
||||
$this->assertEquals(FALSE, $this->groupMembership->hasGroup($asset, $timestamp));
|
||||
$this->assertEquals([], $this->groupMembership->getGroup($asset, $timestamp));
|
||||
$this->assertNull($this->groupMembership->getGroupAssignmentLog($asset, $timestamp));
|
||||
$this->assertEquals([], $this->groupMembership->getGroupMembers($groups, TRUE, $timestamp));
|
||||
|
||||
// Confirm that the asset is where it should be after each log.
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$this->assertEquals(TRUE, $this->groupMembership->hasGroup($asset, $timestamps[$i]));
|
||||
$this->assertEquals($groups[$i]->id(), $this->groupMembership->getGroup($asset, $timestamps[$i])[0]->id());
|
||||
$this->assertEquals($logs[$i]->id(), $this->groupMembership->getGroupAssignmentLog($asset, $timestamps[$i])->id());
|
||||
$this->assertCorrectAssets([$asset], $this->groupMembership->getGroupMembers([$groups[$i]], TRUE, $timestamps[$i]), TRUE);
|
||||
}
|
||||
|
||||
// Confirm that the asset is still in the same group 1 second later.
|
||||
// This tests the <= operator.
|
||||
$this->assertEquals(TRUE, $this->groupMembership->hasGroup($asset, $timestamps[1] + 1));
|
||||
$this->assertEquals($groups[1]->id(), $this->groupMembership->getGroup($asset, $timestamps[1] + 1)[0]->id());
|
||||
$this->assertEquals($logs[1]->id(), $this->groupMembership->getGroupAssignmentLog($asset, $timestamps[1] + 1)->id());
|
||||
$this->assertCorrectAssets([$asset], $this->groupMembership->getGroupMembers([$groups[1]], TRUE, $timestamps[1] + 1), TRUE);
|
||||
|
||||
// Confirm that the asset has no group membership after the last log.
|
||||
$this->assertEquals(FALSE, $this->groupMembership->hasGroup($asset, $timestamps[2]));
|
||||
$this->assertEquals([], $this->groupMembership->getGroup($asset, $timestamps[2]));
|
||||
$this->assertEquals($logs[2]->id(), $this->groupMembership->getGroupAssignmentLog($asset, $timestamps[2])->id());
|
||||
$this->assertEquals([], $this->groupMembership->getGroupMembers($groups, TRUE, $timestamps[2]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test recursive asset group membership.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue