-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from City-of-Helsinki/UHF-162
UHF-162: Action to mass update remote entities
- Loading branch information
Showing
10 changed files
with
517 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
action.configuration.remote_entity:migration_update:*: | ||
type: action_configuration_default | ||
label: 'Update given remote entity' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Drupal\helfi_api_base\Plugin\Action; | ||
|
||
use Drupal\Core\Action\ActionBase; | ||
use Drupal\Core\Entity\EntityTypeManagerInterface; | ||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; | ||
use Drupal\Core\Session\AccountInterface; | ||
use Drupal\helfi_api_base\Entity\RemoteEntityBase; | ||
use Drupal\helfi_api_base\MigrateTrait; | ||
use Drupal\migrate\MigrateMessage; | ||
use Drupal\migrate\Plugin\MigrationInterface; | ||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface; | ||
use Drupal\migrate_tools\MigrateExecutable; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
|
||
/** | ||
* Updates the remote entity using associated migration. | ||
* | ||
* @Action( | ||
* id = "remote_entity:migration_update", | ||
* action_label = @Translation("Remote entity - Migration update"), | ||
* deriver = "Drupal\helfi_api_base\Plugin\Derivative\MigrationUpdateActionDerivative", | ||
* ) | ||
*/ | ||
class MigrationUpdateAction extends ActionBase implements ContainerFactoryPluginInterface { | ||
|
||
use MigrateTrait; | ||
|
||
/** | ||
* The migration plugin manager. | ||
* | ||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface | ||
*/ | ||
protected MigrationPluginManagerInterface $migrationPluginManager; | ||
|
||
/** | ||
* The entity type manager service. | ||
* | ||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface | ||
*/ | ||
protected EntityTypeManagerInterface $entityTypeManager; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public static function create( | ||
ContainerInterface $container, | ||
array $configuration, | ||
$plugin_id, | ||
$plugin_definition | ||
) { | ||
$instance = new static($configuration, $plugin_definition, $plugin_definition); | ||
$instance->migrationPluginManager = $container->get('plugin.manager.migration'); | ||
$instance->entityTypeManager = $container->get('entity_type.manager'); | ||
|
||
return $instance; | ||
} | ||
|
||
/** | ||
* The migration. | ||
* | ||
* @param \Drupal\helfi_api_base\Entity\RemoteEntityBase $entity | ||
* The entity. | ||
* | ||
* @return \Drupal\migrate\Plugin\MigrationInterface | ||
* The migration. | ||
*/ | ||
protected function getMigration(RemoteEntityBase $entity) : MigrationInterface { | ||
$definition = $this->entityTypeManager | ||
->getDefinition($this->getPluginDefinition()['type']) | ||
->getClass()::getMigration(); | ||
|
||
return $this->migrationPluginManager->createInstance($definition, [ | ||
'entity_ids' => [$entity->id()], | ||
]); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function execute($entity = NULL) { | ||
if (!$entity instanceof RemoteEntityBase) { | ||
throw new \InvalidArgumentException('Given entity is not instanceof RemoteEntityBase.'); | ||
} | ||
$this->setIsPartialMigrate(TRUE); | ||
$migration = $this->getMigration($entity); | ||
$keys = array_keys($migration->getSourcePlugin()->getIds()); | ||
$migration->getIdMap()->setUpdate(array_combine($keys, [$entity->id()])); | ||
|
||
$executable = new MigrateExecutable($migration, new MigrateMessage()); | ||
$executable->import(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function access( | ||
$object, | ||
AccountInterface $account = NULL, | ||
$return_as_object = FALSE | ||
) { | ||
/** @var \Drupal\helfi_api_base\Entity\RemoteEntityBase $object */ | ||
$access = $object->access('update', NULL, TRUE); | ||
|
||
return $return_as_object ? $access : $access->isAllowed(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function calculateDependencies() { | ||
$module_name = $this->entityTypeManager | ||
->getDefinition($this->getPluginDefinition()['type']) | ||
->getProvider(); | ||
return ['module' => [$module_name]]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Drupal\helfi_api_base\Plugin\Derivative; | ||
|
||
use Drupal\Component\Plugin\Derivative\DeriverBase; | ||
use Drupal\Core\Entity\EntityTypeInterface; | ||
use Drupal\Core\Entity\EntityTypeManagerInterface; | ||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; | ||
use Drupal\Core\StringTranslation\StringTranslationTrait; | ||
use Drupal\helfi_api_base\Entity\RemoteEntityBase; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
|
||
/** | ||
* Provides an action to update singular migration. | ||
*/ | ||
class MigrationUpdateActionDerivative extends DeriverBase implements ContainerDeriverInterface { | ||
|
||
use StringTranslationTrait; | ||
|
||
/** | ||
* The entity type manager. | ||
* | ||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface | ||
*/ | ||
protected EntityTypeManagerInterface $entityTypeManager; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public static function create(ContainerInterface $container, $base_plugin_id) { | ||
$instance = new static(); | ||
$instance->entityTypeManager = $container->get('entity_type.manager'); | ||
|
||
return $instance; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getDerivativeDefinitions($base_plugin_definition) { | ||
if (empty($this->derivatives)) { | ||
$definitions = []; | ||
|
||
$entity_types = array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entityType) { | ||
if (!$entityType->entityClassImplements(RemoteEntityBase::class)) { | ||
return FALSE; | ||
} | ||
return $entityType->getClass()::getMigration() ?? FALSE; | ||
}); | ||
|
||
foreach ($entity_types as $entity_type_id => $entity_type) { | ||
$definition = $base_plugin_definition; | ||
$definition['type'] = $entity_type_id; | ||
$definition['label'] = sprintf('%s %s', $base_plugin_definition['action_label'], $entity_type->getSingularLabel()); | ||
$definitions[$entity_type_id] = $definition; | ||
} | ||
$this->derivatives = $definitions; | ||
} | ||
|
||
return parent::getDerivativeDefinitions($base_plugin_definition); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Drupal\helfi_api_base\Plugin\migrate\source; | ||
|
||
use Drupal\Component\Utility\UrlHelper; | ||
use Drupal\helfi_api_base\MigrateTrait; | ||
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase; | ||
use Drupal\migrate\Plugin\MigrationInterface; | ||
use GuzzleHttp\ClientInterface; | ||
use GuzzleHttp\Exception\GuzzleException; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
|
||
/** | ||
* Provides a HTTP source plugin. | ||
*/ | ||
abstract class HttpSourcePluginBase extends SourcePluginBase { | ||
|
||
use MigrateTrait; | ||
|
||
/** | ||
* The number of ignored rows until we stop the migrate. | ||
* | ||
* This assumes that your API can be sorted in a way that the newest | ||
* changes are listed first. | ||
* | ||
* For this to have any effect 'track_changes' source setting must be set to | ||
* true and you must run the migrate with PARTIAL_MIGRATE=1 setting. | ||
* | ||
* @var int | ||
*/ | ||
protected const NUM_IGNORED_ROWS_BEFORE_STOPPING = 20; | ||
|
||
/** | ||
* The http client. | ||
* | ||
* @var \GuzzleHttp\ClientInterface | ||
*/ | ||
protected ClientInterface $httpClient; | ||
|
||
/** | ||
* An array of entity ids to import. | ||
* | ||
* @var array | ||
*/ | ||
protected array $entityIds = []; | ||
|
||
/** | ||
* Sends a HTTP request and returns response data as array. | ||
* | ||
* @param string $url | ||
* The url. | ||
* | ||
* @return array | ||
* The JSON returned by Ahjo service. | ||
*/ | ||
protected function getContent(string $url) : array { | ||
try { | ||
$content = (string) $this->httpClient->request('GET', $url)->getBody(); | ||
return \GuzzleHttp\json_decode($content, TRUE); | ||
} | ||
catch (GuzzleException $e) { | ||
} | ||
return []; | ||
} | ||
|
||
/** | ||
* Initializes the list iterator. | ||
* | ||
* @return \Iterator | ||
* The iterator. | ||
*/ | ||
abstract protected function initializeListIterator() : \Iterator; | ||
|
||
/** | ||
* Initializes iterator with set of entity IDs. | ||
* | ||
* @return \Iterator | ||
* The iterator. | ||
*/ | ||
protected function initializeSingleImportIterator() : \Iterator { | ||
foreach ($this->entityIds as $entityId) { | ||
yield $this->getContent($this->buildCanonicalUrl($entityId)); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function initializeIterator() { | ||
if ($this->entityIds) { | ||
return $this->initializeSingleImportIterator(); | ||
} | ||
return $this->initializeListIterator(); | ||
} | ||
|
||
/** | ||
* Builds a canonical url to individual entity. | ||
* | ||
* @param string $id | ||
* The entity ID. | ||
* | ||
* @return string | ||
* The url to canonical page of given entity. | ||
*/ | ||
protected function buildCanonicalUrl(string $id) : string { | ||
$urlParts = UrlHelper::parse($this->configuration['url']); | ||
|
||
return vsprintf('%s/%s/?%s', [ | ||
rtrim($urlParts['path'], '/'), | ||
$id, | ||
UrlHelper::buildQuery($urlParts['query']), | ||
]); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public static function create( | ||
ContainerInterface $container, | ||
array $configuration, | ||
$plugin_id, | ||
$plugin_definition, | ||
MigrationInterface $migration = NULL | ||
) { | ||
$instance = new static($configuration, $plugin_id, $plugin_definition, $migration); | ||
$instance->httpClient = $container->get('http_client'); | ||
|
||
if (!isset($configuration['url'])) { | ||
throw new \InvalidArgumentException('The "url" configuration missing.'); | ||
} | ||
|
||
// Allow certain entity IDs to be updated. | ||
if (isset($migration->entity_ids)) { | ||
$instance->entityIds = $migration->entity_ids; | ||
} | ||
return $instance; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
|
||
declare(strict_types = 1); | ||
|
||
namespace Drupal\Tests\helfi_api_base\Functional; | ||
|
||
use Drupal\language\Entity\ConfigurableLanguage; | ||
use Drupal\migrate\MigrateMessageInterface; | ||
use Drupal\Tests\BrowserTestBase; | ||
use Drupal\Tests\helfi_api_base\Traits\MigrationTestTrait; | ||
|
||
/** | ||
* Base class for multilingual migration tests. | ||
*/ | ||
abstract class MigrationTestBase extends BrowserTestBase implements MigrateMessageInterface { | ||
|
||
use MigrationTestTrait; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected static $modules = [ | ||
'migrate', | ||
'migrate_plus', | ||
'language', | ||
'content_translation', | ||
]; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function setUp() : void { | ||
parent::setUp(); | ||
|
||
foreach (['fi', 'sv'] as $langcode) { | ||
ConfigurableLanguage::createFromLangcode($langcode)->save(); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.