3
0
Fork 0
mirror of https://github.com/farmOS/farmOS.git synced 2024-02-23 11:37:38 +01:00

Merge branch '2.x-location_api_caching' into 2.x

This commit is contained in:
Michael Stenta 2020-12-30 08:10:21 -05:00
commit ee6e485525
3 changed files with 193 additions and 24 deletions

View file

@ -8,6 +8,6 @@ services:
log.presave:
class: Drupal\farm_location\EventSubscriber\LogPresaveEventSubscriber
arguments:
[ '@log.location', '@asset.location' ]
[ '@log.location', '@asset.location', '@cache_tags.invalidator' ]
tags:
- { name: 'event_subscriber' }

View file

@ -2,6 +2,7 @@
namespace Drupal\farm_location\EventSubscriber;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\farm_location\AssetLocationInterface;
use Drupal\farm_location\LogLocationInterface;
use Drupal\farm_location\Traits\WktTrait;
@ -30,6 +31,13 @@ class LogPresaveEventSubscriber implements EventSubscriberInterface {
*/
private AssetLocationInterface $assetLocation;
/**
* Cache tag invalidator service.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
*/
private CacheTagsInvalidatorInterface $cacheTagsInvalidator;
/**
* LogPresaveEventSubscriber Constructor.
*
@ -37,10 +45,13 @@ class LogPresaveEventSubscriber implements EventSubscriberInterface {
* Log location service.
* @param \Drupal\farm_location\AssetLocationInterface $asset_locaiton
* Asset location service.
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
* Cache tag invalidator service.
*/
public function __construct(LogLocationInterface $log_location, AssetLocationInterface $asset_locaiton) {
public function __construct(LogLocationInterface $log_location, AssetLocationInterface $asset_locaiton, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
$this->logLocation = $log_location;
$this->assetLocation = $asset_locaiton;
$this->cacheTagsInvalidator = $cache_tags_invalidator;
}
/**
@ -66,6 +77,21 @@ class LogPresaveEventSubscriber implements EventSubscriberInterface {
// Get the log entity from the event.
$log = $event->log;
// Populate the log geometry from the location geometry.
$this->populateGeometryFromLocation($log);
// Invalidate asset caches when assets are moved.
$this->invalidateAssetCacheOnMovement($log);
}
/**
* Populate a log's geometry based on its location.
*
* @param \Drupal\log\Entity\LogInterface $log
* The Log entity.
*/
protected function populateGeometryFromLocation(LogInterface $log): void {
// If the log does not reference any location assets, we will have nothing
// to copy from, so do nothing.
if (!$this->logLocation->hasLocation($log)) {
@ -95,8 +121,16 @@ class LogPresaveEventSubscriber implements EventSubscriberInterface {
}
}
// Populate the log geometry from the location geometry.
$this->populateGeometryFromLocation($log);
// Load location assets referenced by the log.
$assets = $this->logLocation->getLocation($log);
// Get the combined location asset geometry.
$wkt = $this->getCombinedAssetGeometry($assets);
// If the WKT is not empty, set the log geometry.
if (!empty($wkt)) {
$this->logLocation->setGeometry($log, $wkt);
}
}
/**
@ -132,26 +166,6 @@ class LogPresaveEventSubscriber implements EventSubscriberInterface {
return $log_geometry != $location_geometry;
}
/**
* Populate a log's geometry based on its location.
*
* @param \Drupal\log\Entity\LogInterface $log
* The Log entity.
*/
protected function populateGeometryFromLocation(LogInterface $log): void {
// Load location assets referenced by the log.
$assets = $this->logLocation->getLocation($log);
// Get the combined location asset geometry.
$wkt = $this->getCombinedAssetGeometry($assets);
// If the WKT is not empty, set the log geometry.
if (!empty($wkt)) {
$this->logLocation->setGeometry($log, $wkt);
}
}
/**
* Load a combined set of location asset geometries.
*
@ -177,4 +191,52 @@ class LogPresaveEventSubscriber implements EventSubscriberInterface {
return $wkt;
}
/**
* Invalidate asset caches when assets are moved.
*
* @param \Drupal\log\Entity\LogInterface $log
* The Log entity.
*/
protected function invalidateAssetCacheOnMovement(LogInterface $log): void {
// Keep track if we need to invalidate the cache of referenced assets so
// the computed 'location' and 'geometry' fields are updated.
$update_asset_cache = FALSE;
// If the log is a 'done' movement log, invalidate the cache.
if ($log->get('status')->value == 'done' && $log->get('is_movement')->value) {
$update_asset_cache = TRUE;
}
// If updating an existing 'done' movement log, invalidate the cache.
// This catches any movement logs changing from done to pending.
if (!empty($log->original) && $log->original->get('status')->value == 'done' && $log->original->get('is_movement')->value) {
$update_asset_cache = TRUE;
}
// If an update is not necessary, bail.
if (!$update_asset_cache) {
return;
}
// Build a list of cache tags.
// @todo Only invalidate cache if the movement log changes the asset's current location. This might be different for each asset.
$tags = [];
// Include assets that were previously referenced.
if (!empty($log->original)) {
foreach ($log->original->get('asset')->referencedEntities() as $asset) {
array_push($tags, ...$asset->getCacheTags());
}
}
// Include assets currently referenced by the log.
foreach ($log->get('asset')->referencedEntities() as $asset) {
array_push($tags, ...$asset->getCacheTags());
}
// Invalidate the cache tags.
$this->cacheTagsInvalidator->invalidateTags($tags);
}
}

View file

@ -2,9 +2,14 @@
namespace Drupal\Tests\farm_entity\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\farm_location\Traits\WktTrait;
use Drupal\Tests\farm\Functional\FarmBrowserTestBase;
use Drupal\Tests\jsonapi\Functional\JsonApiRequestTestTrait;
use GuzzleHttp\RequestOptions;
/**
* Tests for farmOS location logic.
@ -15,6 +20,7 @@ class LocationTest extends FarmBrowserTestBase {
use StringTranslationTrait;
use WktTrait;
use JsonApiRequestTestTrait;
/**
* Test user.
@ -50,6 +56,7 @@ class LocationTest extends FarmBrowserTestBase {
protected static $modules = [
'farm_location',
'farm_location_test',
'farm_api',
];
/**
@ -145,4 +152,104 @@ class LocationTest extends FarmBrowserTestBase {
$this->assertSession()->fieldExists('intrinsic_geometry[0][value]');
}
/**
* Test retrieving asset geometry and location fields via API.
*/
public function testApiLocation() {
$location_uuid = $this->location->uuid();
$geometry = $this->location->get('intrinsic_geometry')->value;
$this->assertApiAssetLocationEquals($location_uuid, $geometry);
// Test that removing the log movement flag removes the asset location.
$this->log->is_movement = FALSE;
$this->log->save();
$this->assertApiAssetLocationEquals(NULL, NULL);
// Test that setting the log movement flag sets the asset location.
$this->log->is_movement = TRUE;
$this->log->save();
$this->assertApiAssetLocationEquals($location_uuid, $geometry);
// Test that changing the log status to pending removes the asset location.
$this->log->status = 'pending';
$this->log->save();
$this->assertApiAssetLocationEquals(NULL, NULL);
// Test that changing the log status to done sets the asset location.
$this->log->status = 'done';
$this->log->save();
$this->assertApiAssetLocationEquals($location_uuid, $geometry);
// Test that removing the asset from the log removes the asset location.
$this->log->asset = [];
$this->log->save();
$this->assertApiAssetLocationEquals(NULL, NULL);
// Test that adding the asset to the log sets the asset location.
$this->log->asset = ['target_id' => $this->asset->id()];
$this->log->save();
$this->assertApiAssetLocationEquals($location_uuid, $geometry);
}
/**
* Helper function to test asset location returned by the API.
*
* @param string|null $location_uuid
* The expected location asset uuid.
* @param string|null $geometry
* The expected geometry.
*/
private function assertApiAssetLocationEquals($location_uuid, $geometry) {
// Fetch the asset from the API.
$response = $this->requestApiEntity($this->asset);
// Test that the location field is included in the response.
$this->assertArrayHasKey('data', $response['data']['relationships']['location']);
// If no uuid is provided, test that the asset has no location.
if (empty($location_uuid)) {
$this->assertEquals(0, count($response['data']['relationships']['location']['data']));
}
// Ia a location_uuid is provided, test that the location matches.
if (!empty($location_uuid)) {
$this->assertEquals(1, count($response['data']['relationships']['location']['data']));
$this->assertArrayHasKey('id', $response['data']['relationships']['location']['data'][0]);
$this->assertEquals($location_uuid, $response['data']['relationships']['location']['data'][0]['id']);
}
// Test that the geometry field is included in the response.
$this->assertArrayHasKey('geometry', $response['data']['attributes']);
// If no geometry is provided, test that the asset has no geometry.
if (empty($geometry)) {
$this->assertEquals(NULL, $response['data']['attributes']['geometry']);
}
// Ia a geometry is provided, test that the geometry matches.
if (!empty($geometry)) {
$this->assertArrayHasKey('value', $response['data']['attributes']['geometry']);
$this->assertEquals($geometry, $response['data']['attributes']['geometry']['value']);
}
}
/**
* Helper function to request an entity from the API.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to request.
*
* @return array
* The json-decoded response.
*/
private function requestApiEntity(EntityInterface $entity) {
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$asset_uri = "base://api/{$entity->getEntityType()->id()}/{$entity->bundle()}/{$entity->uuid()}";
$response = $this->request('GET', Url::fromUri($asset_uri), $request_options);
return Json::decode((string) $response->getBody());
}
}