Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions modules/migrate_preview/migrate_preview.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: 'Migrate preview'
description: 'Displays a preview of a migration.'
core: 8.x
type: module
package: 'Migration'
dependencies:
- drupal:migrate
- migrate_tools:migrate_tools
5 changes: 5 additions & 0 deletions modules/migrate_preview/migrate_preview.links.task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
entity.migration.overview_preview:
title: Preview
route_name: entity.migration.preview
parent_id: entity.migration.overview
weight: 2.5
14 changes: 14 additions & 0 deletions modules/migrate_preview/migrate_preview.routing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
entity.migration.preview:
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/preview'
defaults:
_controller: '\Drupal\migrate_preview\Controller\MigrationController::preview'
_title: 'Preview'
_migrate_group: true
requirements:
_permission: 'administer migrations'
options:
parameters:
migration:
type: entity:migration
migration_group:
type: entity:migration_group
195 changes: 195 additions & 0 deletions modules/migrate_preview/src/Controller/MigrationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php

namespace Drupal\migrate_preview\Controller;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate_plus\Entity\MigrationGroupInterface;
use Drupal\migrate_plus\Entity\MigrationInterface;
use Drupal\migrate_preview\MigratePreviewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Returns responses for migrate_tools migration view routes.
*/
class MigrationController extends ControllerBase implements ContainerInjectionInterface {

/**
* Plugin manager for migration plugins.
*
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
*/
protected $migrationPluginManager;

/**
* The current route match.
*
* @var \Drupal\Core\Routing\CurrentRouteMatch
*/
protected $currentRouteMatch;

/**
* Constructs a new MigrationController object.
*
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
* The plugin manager for config entity-based migrations.
* @param \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatch
* The current route match.
*/
public function __construct(MigrationPluginManagerInterface $migration_plugin_manager, CurrentRouteMatch $currentRouteMatch) {
$this->migrationPluginManager = $migration_plugin_manager;
$this->currentRouteMatch = $currentRouteMatch;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.migration'),
$container->get('current_route_match')
);
}

/**
* Displays a preview of the source.
*
* @param \Drupal\migrate_plus\Entity\MigrationGroupInterface $migration_group
* The migration group.
* @param \Drupal\migrate_plus\Entity\MigrationInterface $migration
* The $migration.
*
* @return array
* A render array as expected by drupal_render().
*/
public function preview(MigrationGroupInterface $migration_group, MigrationInterface $migration) {
$migration_plugin = $this->migrationPluginManager->createInstance($migration->id(), $migration->toArray());
$migrateMessage = new MigrateMessage();
$executable = new MigratePreviewExecutable($migration_plugin, $migrateMessage, [
'limit' => 10,
'update' => TRUE,
'force' => TRUE,
]);

// Compose header.
$header = [];
$source = $migration_plugin->getSourcePlugin();
foreach ($source->fields($migration_plugin) as $machine_name => $description) {
$header[$machine_name] = Xss::filterAdmin($description);
}

$migrate_rows = $executable->preview();
return [
'source' => $this->buildTable($header, $migrate_rows, 'source'),
'destination' => $this->buildTable($header, $migrate_rows, 'destination'),
];
}

/**
* Builds a table from the given result.
*
* @param array $headers
* The expected headers.
* @param \Drupal\migrate\Row[] $migrate_rows
* A list of migrate rows.
*
* @return array
* The rows for in the table.
*/
protected function buildTable(array $headers, array $migrate_rows, $method = 'source') {
if (empty($migrate_rows)) {
return [
'#plain_text' => $this->t('No data.'),
];
}

// Add keys from first item as additional headers.
switch ($method) {
case 'source':
$item = reset($migrate_rows)->getSource();
break;

case 'destination':
$item = reset($migrate_rows)->getRawDestination();
break;
}
$keys = array_merge(array_keys($headers), array_keys($item));
foreach (array_keys($item) as $key) {
if (!isset($headers[$key])) {
$headers[$key] = Xss::filterAdmin($key);
}
}

$rows = [];
$index = 0;
foreach ($migrate_rows as $migrate_row) {
switch ($method) {
case 'source':
$row = $migrate_row->getSource();
break;

case 'destination':
$row = $migrate_row->getRawDestination();
break;
}
$row += array_fill_keys($keys, NULL);

foreach ($keys as $column) {
$rows[$index][$column] = $this->buildValue($row[$column]);
}

$index++;
}

return [
'#type' => 'container',
'#attributes' => [
'style' => 'overflow: scroll;',
],
'table' => [
'#type' => 'table',
'#header' => $headers,
] + $rows,
];
}

protected function buildValue($value) {
$row_value = [
'#plain_text' => $value,
];

if (is_string($value) && strlen($value) > 255) {
$value = substr($value, 0, 255) . '...';
}

if (is_scalar($value)) {
$row_value['#plain_text'] = $value;
}
elseif (is_array($value)) {
foreach ($value as $value_index => &$subvalue) {
if (is_string($subvalue)) {
if (strlen($subvalue) > 255) {
$subvalue = substr($subvalue, 0, 255) . '...';
}
}
if (!is_scalar($subvalue)) {
$subvalue = print_r($subvalue, TRUE);
$value[$value_index] = $this->buildValue($subvalue)['#plain_text'];
}
}

$row_value = [
'#theme' => 'item_list',
'#items' => $value,
];
}

return $row_value;
}

}
128 changes: 128 additions & 0 deletions modules/migrate_preview/src/MigratePreviewExecutable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace Drupal\migrate_preview;

use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\Event\MigrateImportEvent;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_tools\MigrateExecutable as MigrateExecutableBase;

/**
* Defines a migrate executable for previewing.
*
* @todo prevent a migration from changing status.
*/
class MigratePreviewExecutable extends MigrateExecutableBase {

/**
* Returns the source.
*
* Makes sure source is initialized based on migration settings.
*
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
* The source.
*/
protected function getSource() {
if (!isset($this->source)) {
$this->source = $this->migration->getSourcePlugin();
}
return $this->source;
}

/**
* Get the ID map from the current migration.
*
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
* The ID map.
*/
protected function getIdMap() {
return $this->migration->getIdMap();
}

/**
*
*/
public function preview() {
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration, $this->message));

// Knock off migration if the requirements haven't been met.
try {
$this->migration->checkRequirements();
}
catch (RequirementsException $e) {
$this->message->display(
$this->t(
'Migration @id did not meet the requirements. @message @requirements',
[
'@id' => $this->migration->id(),
'@message' => $e->getMessage(),
'@requirements' => $e->getRequirementsString(),
]
),
'error'
);

return [];
}

$source = $this->getSource();
$id_map = $this->getIdMap();
$id_map->prepareUpdate();
$this->migration->set('requirements', []);

try {
$source->rewind();
}
catch (\Exception $e) {
$this->message->display(
$this->t('Migration failed with source plugin exception: @e', ['@e' => $e->getMessage()]), 'error');
return [];
}

$rows = [];
while ($source->valid()) {
$row = $source->current();
$this->sourceIdValues = $row->getSourceIdValues();

try {
$this->processRow($row);
$rows[] = $row;
$this->itemLimitCounter++;

if ($this->itemLimit && ($this->itemLimitCounter) >= $this->itemLimit) {
break;
}
}
catch (MigrateException $e) {
// @todo show message to user.
//$this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
//$this->saveMessage($e->getMessage(), $e->getLevel());
}
catch (MigrateSkipRowException $e) {
// @todo show message to user.
// if ($e->getSaveToMap()) {
// $id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
// }
// if ($message = trim($e->getMessage())) {
// $this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL);
// }
}

try {
$source->next();
}
catch (\Exception $e) {
$this->message->display(
$this->t('Migration failed with source plugin exception: @e',
['@e' => $e->getMessage()]), 'error');
return $rows;
}
}

return $rows;
}

}