mirror of
https://github.com/farmOS/farmOS.git
synced 2024-02-23 11:37:38 +01:00
Issue #3217183: Query a list of assets in a group
This commit is contained in:
commit
2c0cd65b77
|
@ -81,6 +81,9 @@ 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.
|
||||
|
||||
`getGroupMembers($group, $recurse)` - Get assets that are members of a group,
|
||||
optionally recursing into child groups.
|
||||
|
||||
**Example usage:**
|
||||
|
||||
```php
|
||||
|
|
|
@ -2,4 +2,4 @@ services:
|
|||
group.membership:
|
||||
class: Drupal\farm_group\GroupMembership
|
||||
arguments:
|
||||
[ '@farm.log_query', '@entity_type.manager', '@datetime.time' ]
|
||||
[ '@farm.log_query', '@entity_type.manager', '@datetime.time', '@database' ]
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Drupal\farm_group;
|
|||
|
||||
use Drupal\asset\Entity\AssetInterface;
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\farm_log\LogQueryFactoryInterface;
|
||||
use Drupal\log\Entity\LogInterface;
|
||||
|
@ -42,6 +43,13 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
*/
|
||||
protected $time;
|
||||
|
||||
/**
|
||||
* The database service.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
|
@ -51,11 +59,14 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
* Entity type manager.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database service.
|
||||
*/
|
||||
public function __construct(LogQueryFactoryInterface $log_query_factory, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
|
||||
public function __construct(LogQueryFactoryInterface $log_query_factory, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time, Connection $database) {
|
||||
$this->logQueryFactory = $log_query_factory;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->time = $time;
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,7 +76,8 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
return new static(
|
||||
$container->get('farm.log_query'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('datetime.time')
|
||||
$container->get('datetime.time'),
|
||||
$container->get('database')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -130,4 +142,72 @@ class GroupMembership implements GroupMembershipInterface {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroupMembers(AssetInterface $group, bool $recurse = TRUE): array {
|
||||
if (empty($group->id())) {
|
||||
return [];
|
||||
}
|
||||
$query = "
|
||||
-- Select asset IDs from the asset base table.
|
||||
SELECT a.id
|
||||
FROM {asset} a
|
||||
|
||||
-- Inner join logs that reference the assets.
|
||||
INNER JOIN {asset_field_data} afd ON afd.id = a.id
|
||||
INNER JOIN {log__asset} la ON a.id = la.asset_target_id AND la.deleted = 0
|
||||
INNER JOIN {log_field_data} lfd ON lfd.id = la.entity_id
|
||||
|
||||
-- Inner join group assets referenced by the logs.
|
||||
INNER JOIN {log__group} lg ON lg.entity_id = lfd.id AND lg.deleted = 0
|
||||
|
||||
-- Left join ANY future group assignment logs for the same asset.
|
||||
-- In the WHERE clause we'll exclude all records that have future logs,
|
||||
-- leaving only the 'current' log entry.
|
||||
LEFT JOIN (
|
||||
{log_field_data} lfd2
|
||||
INNER JOIN {log__asset} la2 ON la2.entity_id = lfd2.id AND la2.deleted = 0
|
||||
) ON lfd2.is_group_assignment = 1 AND la2.asset_target_id = a.id
|
||||
|
||||
-- Future log entries have either a higher timestamp, or an equal timestamp and higher log ID.
|
||||
AND (lfd2.timestamp > lfd.timestamp OR (lfd2.timestamp = lfd.timestamp AND lfd2.id > lfd.id))
|
||||
|
||||
-- Don't include future logs beyond the given timestamp.
|
||||
-- These conditions should match the values in the WHERE clause.
|
||||
AND (lfd2.status = 'done') AND (lfd2.timestamp <= :timestamp)
|
||||
|
||||
-- Limit results to completed membership assignment logs to the desired
|
||||
-- group that took place before the given timestamp.
|
||||
WHERE (lfd.is_group_assignment = 1) AND (lfd.status = 'done') AND (lfd.timestamp <= :timestamp) AND (lg.group_target_id = :group_id)
|
||||
|
||||
-- Exclude records with future log entries.
|
||||
AND lfd2.id IS NULL";
|
||||
$args = [
|
||||
':timestamp' => $this->time->getRequestTime(),
|
||||
':group_id' => $group->id(),
|
||||
];
|
||||
$result = $this->database->query($query, $args)->fetchAll();
|
||||
$asset_ids = [];
|
||||
foreach ($result as $row) {
|
||||
if (!empty($row->id)) {
|
||||
$asset_ids[] = $row->id;
|
||||
}
|
||||
}
|
||||
if (empty($asset_ids)) {
|
||||
return [];
|
||||
}
|
||||
$asset_ids = array_unique($asset_ids);
|
||||
/** @var \Drupal\asset\Entity\AssetInterface[] $assets */
|
||||
$assets = $this->entityTypeManager->getStorage('asset')->loadMultiple($asset_ids);
|
||||
if ($recurse) {
|
||||
foreach ($assets as $asset) {
|
||||
if ($asset->bundle() == 'group') {
|
||||
$assets = array_merge($assets, $this->getGroupMembers($asset));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $assets;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,4 +43,18 @@ interface GroupMembershipInterface {
|
|||
*/
|
||||
public function getGroupAssignmentLog(AssetInterface $asset): ?LogInterface;
|
||||
|
||||
/**
|
||||
* Get assets that are members of a group.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $group
|
||||
* The Asset entity.
|
||||
* @param bool $recurse
|
||||
* Boolean: whether or not to recurse and load members of sub-groups.
|
||||
* Defaults to TRUE.
|
||||
*
|
||||
* @return array
|
||||
* Returns an array of assets.
|
||||
*/
|
||||
public function getGroupMembers(AssetInterface $group, bool $recurse = TRUE): array;
|
||||
|
||||
}
|
||||
|
|
|
@ -81,11 +81,7 @@ class GroupTest extends KernelTestBase {
|
|||
]);
|
||||
$animal->save();
|
||||
|
||||
// When an asset has no group assignment logs, it has no group membership.
|
||||
$this->assertFalse($this->groupMembership->hasGroup($animal), 'New assets do not have group membership.');
|
||||
$this->assertEmpty($this->groupMembership->getGroup($animal), 'New assets do not reference any groups.');
|
||||
|
||||
// Create a group asset.
|
||||
// Create group assets.
|
||||
/** @var \Drupal\asset\Entity\AssetInterface $first_group */
|
||||
$first_group = Asset::create([
|
||||
'type' => 'group',
|
||||
|
@ -93,6 +89,19 @@ class GroupTest extends KernelTestBase {
|
|||
'status' => 'active',
|
||||
]);
|
||||
$first_group->save();
|
||||
/** @var \Drupal\asset\Entity\AssetInterface $second_group */
|
||||
$second_group = Asset::create([
|
||||
'type' => 'group',
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 'active',
|
||||
]);
|
||||
$second_group->save();
|
||||
|
||||
// When an asset has no group assignment logs, it has no group membership.
|
||||
$this->assertFalse($this->groupMembership->hasGroup($animal), 'New assets do not have group membership.');
|
||||
$this->assertEmpty($this->groupMembership->getGroup($animal), 'New assets do not reference any groups.');
|
||||
$this->assertEmpty($this->groupMembership->getGroupMembers($first_group), 'New groups have no members.');
|
||||
$this->assertEmpty($this->groupMembership->getGroupMembers($second_group), 'New groups have no members.');
|
||||
|
||||
// Create a "done" log that assigns the animal to the group.
|
||||
/** @var \Drupal\log\Entity\LogInterface $first_log */
|
||||
|
@ -108,15 +117,8 @@ class GroupTest extends KernelTestBase {
|
|||
// When an asset has a done group assignment logs, it has group membership.
|
||||
$this->assertTrue($this->groupMembership->hasGroup($animal), 'Asset with group assignment has group membership.');
|
||||
$this->assertEquals($first_group->id(), $this->groupMembership->getGroup($animal)[0]->id(), 'Asset with group assignment is in the assigned group.');
|
||||
|
||||
// Create a second group asset.
|
||||
/** @var \Drupal\asset\Entity\AssetInterface $second_group */
|
||||
$second_group = Asset::create([
|
||||
'type' => 'group',
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 'active',
|
||||
]);
|
||||
$second_group->save();
|
||||
$this->assertEquals(1, count($this->groupMembership->getGroupMembers($first_group)), 'When an asset becomes a group member, the group has one member.');
|
||||
$this->assertEmpty($this->groupMembership->getGroupMembers($second_group), 'When an asset becomes a group member, other groups are unaffected.');
|
||||
|
||||
// Create a "pending" log that assigns the animal to the second group.
|
||||
/** @var \Drupal\log\Entity\LogInterface $second_log */
|
||||
|
@ -132,11 +134,13 @@ class GroupTest extends KernelTestBase {
|
|||
// When an asset has a pending group assignment logs, it still has the same
|
||||
// group membership as before.
|
||||
$this->assertEquals($first_group->id(), $this->groupMembership->getGroup($animal)[0]->id(), 'Pending group assignment logs do not affect membership.');
|
||||
$this->assertEmpty($this->groupMembership->getGroupMembers($second_group), 'Groups with only pending membership have zero members.');
|
||||
|
||||
// When the log is marked as "done", the asset's membership is updated.
|
||||
$second_log->status = 'done';
|
||||
$second_log->save();
|
||||
$this->assertEquals($second_group->id(), $this->groupMembership->getGroup($animal)[0]->id(), 'A second group assignment log updates group membership.');
|
||||
$this->assertEquals(1, count($this->groupMembership->getGroupMembers($second_group)), 'Completed group assignment logs add group members.');
|
||||
|
||||
// Create a third "done" log in the future.
|
||||
/** @var \Drupal\log\Entity\LogInterface $third_log */
|
||||
|
@ -153,6 +157,7 @@ class GroupTest extends KernelTestBase {
|
|||
// When an asset has a "done" group assignment log in the future, the asset
|
||||
// group membership remains the same as the previous "done" movement log.
|
||||
$this->assertEquals($second_group->id(), $this->groupMembership->getGroup($animal)[0]->id(), 'A third group assignment log in the future does not update group membership.');
|
||||
$this->assertEquals(1, count($this->groupMembership->getGroupMembers($second_group)), 'Future group assignment logs do not affect members.');
|
||||
|
||||
// Create a fourth log with no group reference.
|
||||
/** @var \Drupal\log\Entity\LogInterface $fourth_log */
|
||||
|
@ -169,6 +174,8 @@ class GroupTest extends KernelTestBase {
|
|||
// effectively "unsets" the asset's group membership.
|
||||
$this->assertFalse($this->groupMembership->hasGroup($animal), 'Asset group membership can be unset.');
|
||||
$this->assertEmpty($this->groupMembership->getGroup($animal), 'Unset group membership does not reference any groups.');
|
||||
$this->assertEquals(0, count($this->groupMembership->getGroupMembers($first_group)), 'Unset group membership unsets group members.');
|
||||
$this->assertEquals(0, count($this->groupMembership->getGroupMembers($second_group)), 'Unset group membership unsets group members.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue