From b956a19615988c4b4a57265862637c9a5e5960d4 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 14 Jun 2024 10:34:49 -0400 Subject: [PATCH] Add an asset.logs service for retrieving logs that reference an asset #850 --- CHANGELOG.md | 1 + docs/development/module/services.md | 26 ++++++ modules/core/log/farm_log.services.yml | 4 + modules/core/log/src/AssetLogs.php | 93 +++++++++++++++++++ modules/core/log/src/AssetLogsInterface.php | 42 +++++++++ modules/core/log/tests/src/Kernel/LogTest.php | 48 ++++++++++ 6 files changed, 214 insertions(+) create mode 100644 modules/core/log/src/AssetLogs.php create mode 100644 modules/core/log/src/AssetLogsInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 195c5c0dc2..801fdd3436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Add an "Export Quantity CSV" bulk action to Log Views](https://github.com/farmOS/farmOS/pull/861) - [Allow modules to alter dashboard panes #868](https://github.com/farmOS/farmOS/pull/868) - [Add geometry/location fields to CSV importers #815](https://github.com/farmOS/farmOS/pull/815) +- [Add an asset.logs service for retrieving logs that reference an asset #850](https://github.com/farmOS/farmOS/pull/850) ### Changed diff --git a/docs/development/module/services.md b/docs/development/module/services.md index adcf1dfe1f..29261d49e1 100644 --- a/docs/development/module/services.md +++ b/docs/development/module/services.md @@ -4,6 +4,32 @@ farmOS provides some [services](https://symfony.com/doc/current/service_containe that encapsulate common logic like querying logs and getting an asset's current location. Some of these services are documented here. +## Asset logs service + +**Service name**: `asset.logs` + +The asset logs service provides methods for retrieving logs that reference +assets. + +**Methods**: + +`getLogs($asset, $log_type = NULL, $access_check = TRUE)` - Load a list of logs +that reference an asset, optionally filtered by log type. Access checking is +performed by default but can be optionally disabled. Returns a list of log +entities. + +`getFirstLog($asset, $log_type = NULL, $access_check = TRUE)` - Load the first +log that references an asset, optionally filtered by log type. Access checking +is performed by default but can be optionally disabled. Returns a log entity, or +`NULL` if no logs were found. + +**Example usage**: + +```php +// Get all observation logs that reference an asset. +$observation_logs = \Drupal::service('asset.logs')->getLogs($asset, 'observation'); +``` + ## Asset location service **Service name**: `asset.location` diff --git a/modules/core/log/farm_log.services.yml b/modules/core/log/farm_log.services.yml index 1c8262cc20..f8208431c4 100644 --- a/modules/core/log/farm_log.services.yml +++ b/modules/core/log/farm_log.services.yml @@ -1,4 +1,8 @@ services: + asset.logs: + class: Drupal\farm_log\AssetLogs + arguments: + [ '@entity_type.manager', '@farm.log_query' ] farm.log_query: class: Drupal\farm_log\LogQueryFactory arguments: diff --git a/modules/core/log/src/AssetLogs.php b/modules/core/log/src/AssetLogs.php new file mode 100644 index 0000000000..2bf6c9f60a --- /dev/null +++ b/modules/core/log/src/AssetLogs.php @@ -0,0 +1,93 @@ +entityTypeManager = $entity_type_manager; + $this->logQueryFactory = $log_query_factory; + } + + /** + * {@inheritdoc} + */ + public function getLogs(AssetInterface $asset, string $log_type = NULL, bool $access_check = TRUE): array { + $log_ids = $this->query($asset, $log_type, $access_check)->execute(); + if (empty($log_ids)) { + return []; + } + return $this->entityTypeManager->getStorage('log')->loadMultiple($log_ids); + } + + /** + * {@inheritdoc} + */ + public function getFirstLog(AssetInterface $asset, string $log_type = NULL, bool $access_check = TRUE) { + $log_ids = $this->query($asset, $log_type, $access_check, 1)->execute(); + if (empty($log_ids)) { + return NULL; + } + return $this->entityTypeManager->getStorage('log')->load(reset($log_ids)); + } + + /** + * Build a log query. + * + * @param \Drupal\asset\Entity\AssetInterface $asset + * The asset entity. + * @param string|null $log_type + * Optionally filter by log type. + * @param bool $access_check + * Whether to check log entity access. + * @param int|null $limit + * The number of logs to return. + * + * @return \Drupal\Core\Entity\Query\QueryInterface + * A query object. + */ + protected function query(AssetInterface $asset, string $log_type = NULL, bool $access_check = TRUE, int $limit = NULL) { + $options = [ + 'asset' => $asset, + 'direction' => 'ASC', + ]; + if (!empty($limit)) { + $options['limit'] = $limit; + } + $query = $this->logQueryFactory->getQuery($options); + if (!empty($log_type)) { + $query->condition('type', $log_type); + } + $query->accessCheck($access_check); + return $query; + } + +} diff --git a/modules/core/log/src/AssetLogsInterface.php b/modules/core/log/src/AssetLogsInterface.php new file mode 100644 index 0000000000..f880b789ae --- /dev/null +++ b/modules/core/log/src/AssetLogsInterface.php @@ -0,0 +1,42 @@ +logQueryFactory = \Drupal::service('farm.log_query'); + $this->assetLogs = \Drupal::service('asset.logs'); $this->installEntitySchema('asset'); $this->installEntitySchema('log'); $this->installEntitySchema('user'); @@ -123,6 +131,46 @@ public function testLogQueryFactory() { $this->assertEquals(1, count($log_ids), 'Log query results can be limited.'); } + /** + * Test asset.logs service. + */ + public function testAssetLogsService() { + + // Get asset and log storage. + $asset_storage = \Drupal::service('entity_type.manager')->getStorage('asset'); + $log_storage = \Drupal::service('entity_type.manager')->getStorage('log'); + + // Create one asset and two logs of different types that reference it. + $asset = $asset_storage->create(['type' => 'test']); + $asset->save(); + $timestamp = time(); + $foo_log = $log_storage->create([ + 'timestamp' => $timestamp + 1, + 'type' => 'foo', + 'asset' => [$asset], + ]); + $foo_log->save(); + $bar_log = $log_storage->create([ + 'timestamp' => $timestamp, + 'type' => 'bar', + 'asset' => [$asset], + ]); + $bar_log->save(); + + // Test that the asset.logs service returns both logs. + $logs = $this->assetLogs->getLogs($asset); + $this->assertCount(2, $logs); + + // Test that logs can be filtered by type. + $logs = $this->assetLogs->getLogs($asset, 'bar'); + $this->assertCount(1, $logs); + $this->assertEquals($bar_log->id(), reset($logs)->id()); + + // Test that we can get the first log. + $first_log = $this->assetLogs->getFirstLog($asset); + $this->assertEquals($bar_log->id(), $first_log->id()); + } + /** * Test log status workflow. */