Skip to content
Snippets Groups Projects
Unverified Commit f6c8715c authored by pcate's avatar pcate Committed by Lucas Hedding
Browse files

Issue #3104105 by egruel, phma, PCate, GaëlG, oxyc, J_Drupal, heddn, vasike:...

Issue #3104105 by egruel, phma, PCate, GaëlG, oxyc, J_Drupal, heddn, vasike: sync option doesnt work with track_changes
parent 0813c0fb
Branches
No related tags found
No related merge requests found
...@@ -15,6 +15,10 @@ use Drupal\migrate_tools\Form\MigrationEditForm; ...@@ -15,6 +15,10 @@ use Drupal\migrate_tools\Form\MigrationEditForm;
use Drupal\migrate_tools\Form\MigrationGroupAddForm; use Drupal\migrate_tools\Form\MigrationGroupAddForm;
use Drupal\migrate_tools\Form\MigrationGroupDeleteForm; use Drupal\migrate_tools\Form\MigrationGroupDeleteForm;
use Drupal\migrate_tools\Form\MigrationGroupEditForm; use Drupal\migrate_tools\Form\MigrationGroupEditForm;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_tools\EventSubscriber\MigrationImportSync;
/** /**
* Implements hook_entity_type_build(). * Implements hook_entity_type_build().
...@@ -70,3 +74,24 @@ function migrate_tools_migration_plugins_alter(array &$migrations): void { ...@@ -70,3 +74,24 @@ function migrate_tools_migration_plugins_alter(array &$migrations): void {
\Drupal::service('migrate_tools.shared_config_include_handler')->include($migrations[$id]); \Drupal::service('migrate_tools.shared_config_include_handler')->include($migrations[$id]);
} }
} }
/**
* Implements hook_migrate_prepare_row().
*
* @see \Drupal\migrate_tools\EventSubscriber\MigrationImportSync
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase::next()
*/
function migrate_tools_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
if (!empty($migration->syncSource)) {
// Keep track of all source rows here, as SourcePluginBase::next() might
// skip some rows, and we need them all to detect missing items in source to
// delete in destination.
$source_id_values = \Drupal::state()->get('migrate_tools_sync', []);
$source_id_values[] = $row->getSourceIdValues();
\Drupal::state()->set('migrate_tools_sync', $source_id_values);
}
}
...@@ -19,6 +19,7 @@ services: ...@@ -19,6 +19,7 @@ services:
- { name: event_subscriber } - { name: event_subscriber }
arguments: arguments:
- '@event_dispatcher' - '@event_dispatcher'
- '@state'
plugin.manager.migrate_shared_config: plugin.manager.migrate_shared_config:
class: Drupal\migrate_tools\MigrateSharedConfigPluginManager class: Drupal\migrate_tools\MigrateSharedConfigPluginManager
......
...@@ -4,6 +4,7 @@ declare(strict_types = 1); ...@@ -4,6 +4,7 @@ declare(strict_types = 1);
namespace Drupal\migrate_tools\EventSubscriber; namespace Drupal\migrate_tools\EventSubscriber;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Event\MigrateEvents; use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\Event\MigrateImportEvent; use Drupal\migrate\Event\MigrateImportEvent;
use Drupal\migrate\Event\MigrateRollbackEvent; use Drupal\migrate\Event\MigrateRollbackEvent;
...@@ -20,14 +21,25 @@ class MigrationImportSync implements EventSubscriberInterface { ...@@ -20,14 +21,25 @@ class MigrationImportSync implements EventSubscriberInterface {
protected EventDispatcherInterface $dispatcher; protected EventDispatcherInterface $dispatcher;
/**
* The state key/value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected StateInterface $state;
/** /**
* MigrationImportSync constructor. * MigrationImportSync constructor.
* *
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* The event dispatcher. * The event dispatcher.
* @param Drupal\Core\State\StateInterface $state
* The Key/Value Store to use for tracking synced source rows.
*/ */
public function __construct(EventDispatcherInterface $dispatcher) { public function __construct(EventDispatcherInterface $dispatcher, StateInterface $state) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->state = $state;
$this->state->set('migrate_tools_sync', []);
} }
/** /**
...@@ -48,21 +60,37 @@ class MigrationImportSync implements EventSubscriberInterface { ...@@ -48,21 +60,37 @@ class MigrationImportSync implements EventSubscriberInterface {
public function sync(MigrateImportEvent $event): void { public function sync(MigrateImportEvent $event): void {
$migration = $event->getMigration(); $migration = $event->getMigration();
if (!empty($migration->syncSource)) { if (!empty($migration->syncSource)) {
$id_map = $migration->getIdMap();
$id_map->prepareUpdate(); // Loop through the source to register existing source ids.
// @see migrate_tools_migrate_prepare_row().
// Clone so that any generators aren't initialized prematurely. // Clone so that any generators aren't initialized prematurely.
$source = clone $migration->getSourcePlugin(); $source = clone $migration->getSourcePlugin();
$source->rewind(); $source->rewind();
$source_id_values = [];
while ($source->valid()) { while ($source->valid()) {
$source_id_values[] = $source->current()->getSourceIdValues();
$source->next(); $source->next();
} }
$source_id_values = $this->state->get('migrate_tools_sync', []);
$id_map = $migration->getIdMap();
$id_map->rewind(); $id_map->rewind();
$destination = $migration->getDestinationPlugin(); $destination = $migration->getDestinationPlugin();
while ($id_map->valid()) { while ($id_map->valid()) {
$map_source_id = $id_map->currentSource(); $map_source_id = $id_map->currentSource();
foreach ($source->getIds() as $id_key => $id_config) {
if ($id_config['type'] === 'string') {
$map_source_id[$id_key] = (string) $map_source_id[$id_key];
}
elseif ($id_config['type'] === 'integer') {
$map_source_id[$id_key] = (int) $map_source_id[$id_key];
}
}
$destination_ids = $id_map->currentDestination(); $destination_ids = $id_map->currentDestination();
if ($destination_ids !== NULL && $destination_ids !== [] && !in_array($map_source_id, $source_id_values, TRUE)) { if ($destination_ids !== NULL && $destination_ids !== [] && !in_array($map_source_id, $source_id_values, TRUE)) {
$this->dispatchRowDeleteEvent(MigrateEvents::PRE_ROW_DELETE, $migration, $destination_ids); $this->dispatchRowDeleteEvent(MigrateEvents::PRE_ROW_DELETE, $migration, $destination_ids);
if (class_exists(MigratePlusEvents::class)) { if (class_exists(MigratePlusEvents::class)) {
......
...@@ -83,7 +83,7 @@ EOD; ...@@ -83,7 +83,7 @@ EOD;
file_put_contents('public://test.csv', $this->sourceData); file_put_contents('public://test.csv', $this->sourceData);
// Execute sync migration. // Execute sync migration.
$this->drush('mim', ['csv_source_test'], ['sync' => NULL]); $this->drush('mim', ['csv_source_test'], ['sync' => NULL, 'update' => NULL]);
$this->assertStringContainsString('1/4', $this->getErrorOutput()); $this->assertStringContainsString('1/4', $this->getErrorOutput());
$this->assertStringContainsString('25% [notice] Rolled back 1 item - done with \'csv_source_test\'', $this->getErrorOutput()); $this->assertStringContainsString('25% [notice] Rolled back 1 item - done with \'csv_source_test\'', $this->getErrorOutput());
$this->assertStringContainsString('4/4', $this->getErrorOutput()); $this->assertStringContainsString('4/4', $this->getErrorOutput());
...@@ -94,6 +94,21 @@ EOD; ...@@ -94,6 +94,21 @@ EOD;
drupal_flush_all_caches(); drupal_flush_all_caches();
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load('genre')); $this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load('genre'));
// Remove one vocab and replace with another (reverse previous change).
$this->sourceData = str_replace('fruit,Fruit,Fruit description,1,0', 'genre,Genre,Genre description,1,0', $this->sourceData);
file_put_contents('public://test.csv', $this->sourceData);
// Execute sync migration without update enforced.
$this->drush('mim', ['csv_source_test'], ['sync' => NULL]);
$this->assertStringContainsString('1/4', $this->getErrorOutput());
$this->assertStringContainsString('25% [notice] Rolled back 1 item - done with \'csv_source_test\'', $this->getErrorOutput());
$this->assertStringNotContainsString('3 updated', $this->getErrorOutput());
$this->assertStringContainsString('[notice] Processed 1 item (1 created, 0 updated, 0 failed, 0 ignored) - done with \'csv_source_test\'', $this->getErrorOutput());
$this->assertEquals(4, \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->getQuery()->accessCheck(TRUE)->count()->execute());
// Flush cache so recently deleted vocabulary actually goes away.
drupal_flush_all_caches();
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load('fruit'));
/** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map */ /** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map */
$id_map = $this->container->get('plugin.manager.migration')->createInstance('csv_source_test')->getIdMap(); $id_map = $this->container->get('plugin.manager.migration')->createInstance('csv_source_test')->getIdMap();
$this->assertCount(4, $id_map); $this->assertCount(4, $id_map);
......
...@@ -183,7 +183,7 @@ EOT; ...@@ -183,7 +183,7 @@ EOT;
} }
/** /**
* Tests synced import. * Tests synced import with and without update enforced.
*/ */
public function testSyncImport(): void { public function testSyncImport(): void {
$this->drush('mim', ['fruit_terms']); $this->drush('mim', ['fruit_terms']);
...@@ -200,7 +200,7 @@ EOT; ...@@ -200,7 +200,7 @@ EOT;
$this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->set('source', $source)->save(); $this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->set('source', $source)->save();
// Flush cache so the recently changed migration can be refreshed. // Flush cache so the recently changed migration can be refreshed.
drupal_flush_all_caches(); drupal_flush_all_caches();
$this->drush('mim', ['fruit_terms'], ['sync' => NULL]); $this->drush('mim', ['fruit_terms'], ['sync' => NULL, 'update' => NULL]);
$this->assertStringContainsString('1/3', $this->getErrorOutput()); $this->assertStringContainsString('1/3', $this->getErrorOutput());
$this->assertStringContainsString('4/4', $this->getErrorOutput()); $this->assertStringContainsString('4/4', $this->getErrorOutput());
$this->assertStringContainsString('[notice] Processed 3 items (1 created, 2 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'', $this->getErrorOutput()); $this->assertStringContainsString('[notice] Processed 3 items (1 created, 2 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'', $this->getErrorOutput());
...@@ -208,6 +208,19 @@ EOT; ...@@ -208,6 +208,19 @@ EOT;
$this->assertEquals(3, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->accessCheck(TRUE)->count()->execute()); $this->assertEquals(3, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->accessCheck(TRUE)->count()->execute());
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(2)); $this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(2));
unset($source['data_rows'][2]);
$source['data_rows'][] = ['name' => 'Pear'];
$this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->set('source', $source)->save();
// Flush cache so the recently changed migration can be refreshed.
drupal_flush_all_caches();
$this->drush('mim', ['fruit_terms'], ['sync' => NULL]);
$this->assertStringContainsString('1/3', $this->getErrorOutput());
$this->assertStringContainsString('[notice] Processed 1 item (1 created, 0 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'', $this->getErrorOutput());
$this->assertStringNotContainsString('2 updated', $this->getErrorOutput());
$this->assertStringNotContainsString('5', $this->getErrorOutput());
$this->assertEquals(3, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->accessCheck(TRUE)->count()->execute());
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(3));
/** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map */ /** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map */
$id_map = $this->container->get('plugin.manager.migration')->createInstance('fruit_terms')->getIdMap(); $id_map = $this->container->get('plugin.manager.migration')->createInstance('fruit_terms')->getIdMap();
$this->assertCount(3, $id_map); $this->assertCount(3, $id_map);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment