Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VACMS-136900: implement text field migration core logic #16227

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
services:
va_gov_live_field_migration.commands:
class: \Drupal\va_gov_live_field_migration\Commands\Commands
arguments: []
arguments:
- '@va_gov_live_field_migration.field_provider_resolver'
- '@va_gov_live_field_migration.migration_runner'
tags:
- { name: drush.command }
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Drupal\va_gov_live_field_migration\Annotation;

use Drupal\Component\Annotation\Plugin;

/**
* Defines the annotation object for field provider plugins.
*
* @see plugin_api
* @see \Drupal\va_gov_live_field_migration\FieldProvider\Plugin\FieldProviderPluginInterface
* @see \Drupal\va_gov_live_field_migration\FieldProvider\Plugin\FieldProviderPluginManager
*
* @Annotation
*/
class FieldProvider extends Plugin {

/**
* The plugin ID.
*
* @var string
*/
public $id;

/**
* The human-readable name of the field provider plugin.
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Drupal\va_gov_live_field_migration\Annotation;

use Drupal\Component\Annotation\Plugin;

/**
* Defines the annotation object for live field migration plugins.
*
* @see plugin_api
* @see \Drupal\va_gov_live_field_migration\Migration\Plugin\MigrationPluginInterface
* @see \Drupal\va_gov_live_field_migration\Migration\Plugin\MigrationPluginManager
*
* @Annotation
*/
class Migration extends Plugin {

/**
* The plugin ID.
*
* @var string
*/
public $id;

/**
* The human-readable name of the migration.
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Drupal\va_gov_live_field_migration\Commands;

use Drupal\va_gov_live_field_migration\FieldProvider\Resolver\ResolverInterface as FieldProviderResolverInterface;
use Drupal\va_gov_live_field_migration\Migration\Runner\RunnerInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;

/**
Expand All @@ -10,121 +13,117 @@
class Commands extends DrushCommands {

/**
* Perform an operation, such as migrating, rolling back, or verifying.
* The field provider resolver service.
*
* @param callable $operation
* The operation to perform.
* @var \Drupal\va_gov_live_field_migration\FieldProvider\Resolver\ResolverInterface
*/
public function performOperation(callable $operation) {
$startTime = microtime(TRUE);
try {
$operation();
}
catch (\Exception $exception) {
$this->output()->writeln('Error: ' . $exception->getMessage());
}
finally {
$elapsedTime = microtime(TRUE) - $startTime;
$peakMemoryUsage = memory_get_peak_usage();
$this->output()->writeln('Elapsed time: ' . number_format($elapsedTime, 2) . ' seconds');
$this->output()->writeln('Peak memory usage: ' . number_format($peakMemoryUsage / 1024 / 1024, 2) . ' MB');
}
protected $fieldProviderResolver;

/**
* The migration runner service.
*
* @var \Drupal\va_gov_live_field_migration\Migration\Runner\RunnerInterface
*/
protected $migrationRunner;

/**
* Commands constructor.
*
* @param \Drupal\va_gov_live_field_migration\FieldProvider\Resolver\ResolverInterface $fieldProviderResolver
* The field provider resolver service.
* @param \Drupal\va_gov_live_field_migration\Migration\Runner\RunnerInterface $migrationRunner
* The migration runner service.
*/
public function __construct(
FieldProviderResolverInterface $fieldProviderResolver,
RunnerInterface $migrationRunner
) {
$this->fieldProviderResolver = $fieldProviderResolver;
$this->migrationRunner = $migrationRunner;
}

/**
* Migrate a specific field on a specific content type.
* Migrate a specific field on a specific entity type.
*
* @param string $entityType
* The entity type.
* @param string $bundle
* The entity bundle or content type.
* @param string $fieldName
* The field name.
*
* @command va-gov-live-field-migration:migrate-field
* @aliases va-gov-live-field-migration-migrate-field
*/
public function migrateField(
#[CLI\Command(name: 'va-gov-live-field-migration:migrate', aliases: ['va-gov-live-field-migration-migrate'])]
#[CLI\Argument(name: 'entityType', description: 'The entity type')]
#[CLI\Argument(name: 'fieldName', description: 'The field name')]
public function migrate(
string $entityType,
string $bundle,
string $fieldName
) {
$this->performOperation(function () use ($entityType, $bundle, $fieldName) {
$this->output()->writeln('Migrating field ' . $fieldName . ' on ' . $entityType . ' ' . $bundle);
// Logic for the migration.
$this->output()->writeln('Migration successful.');
});
$migration = $this->migrationRunner->getMigration($entityType, $fieldName);
$this->migrationRunner->runMigration($migration, $entityType, $fieldName);
}

/**
* Rollback a specific field on a specific content type.
* Rollback a specific field on a specific entity type.
*
* @param string $entityType
* The entity type.
* @param string $bundle
* The entity bundle or content type.
* @param string $fieldName
* The field name.
*
* @command va-gov-live-field-migration:rollback-field
* @aliases va-gov-live-field-migration-rollback-field
*/
public function rollbackField(
#[CLI\Command(name: 'va-gov-live-field-migration:rollback', aliases: ['va-gov-live-field-migration-rollback'])]
#[CLI\Argument(name: 'entityType', description: 'The entity type')]
#[CLI\Argument(name: 'fieldName', description: 'The field name')]
public function rollback(
string $entityType,
string $bundle,
string $fieldName
) {
$this->performOperation(function () use ($entityType, $bundle, $fieldName) {
$this->output()->writeln('Rolling back field ' . $fieldName . ' on ' . $entityType . ' ' . $bundle);
// Logic for the rollback.
$this->output()->writeln('Rollback successful.');
});
$migration = $this->migrationRunner->getMigration($entityType, $fieldName);
$this->migrationRunner->rollbackMigration($migration, $entityType, $fieldName);
}

/**
* Verify a migration completed successfully.
*
* @param string $entityType
* The entity type.
* @param string $bundle
* The entity bundle or content type.
* @param string $fieldName
* The field name.
*
* @command va-gov-live-field-migration:verify
* @aliases va-gov-live-field-migration-verify
*/
#[CLI\Command(name: 'va-gov-live-field-migration:verify', aliases: ['va-gov-live-field-migration-verify'])]
#[CLI\Argument(name: 'entityType', description: 'The entity type')]
#[CLI\Argument(name: 'fieldName', description: 'The field name')]
public function verify(
string $entityType,
string $bundle,
string $fieldName
) {
$this->performOperation(function () use ($entityType, $bundle, $fieldName) {
$this->output()->writeln('Verifying field ' . $fieldName . ' on ' . $entityType . ' ' . $bundle);
// Logic for the verification.
$this->output()->writeln('Verification successful.');
});
$migration = $this->migrationRunner->getMigration($entityType, $fieldName);
$this->migrationRunner->verifyMigration($migration, $entityType, $fieldName);
}

/**
* Find fields that haven't been migrated yet.
*
* @param string $entityType
* The entity type.
* @param string $bundle
* The entity bundle or content type.
*
* @command va-gov-live-field-migration:find
* @aliases va-gov-live-field-migration-find
*/
public function find(
string $entityType,
string $bundle
) {
$this->performOperation(function () use ($entityType, $bundle) {
$this->output()->writeln('Finding fields on ' . $entityType . ' ' . $bundle);
// Logic for finding fields.
});
#[CLI\Command(name: 'va-gov-live-field-migration:find', aliases: ['va-gov-live-field-migration-find'])]
#[CLI\Option(name: 'field-provider', description: 'The field provider to use')]
#[CLI\Option(name: 'entity-type', description: 'The entity type to use')]
#[CLI\Option(name: 'bundle', description: 'The bundle to use')]
public function find($options = [
// Default to the issue 14995 field provider.
// @see https://github.com/department-of-veterans-affairs/va-gov-cms/issues/14995
'field-provider' => 'issue_14995',
'entity-type' => 'node',
'bundle' => NULL,
]) {
$fieldProvider = $options['field-provider'];
$entityType = $options['entity-type'];
$bundle = $options['bundle'];
$this->output()->writeln('Finding fields with field provider "' . $fieldProvider . '" on entity type "' . $entityType . '", bundle "' . ($bundle ?: 'NULL') . '"...');
$fields = $this->fieldProviderResolver
->getFieldProvider($fieldProvider)
->getFields($entityType, $bundle);
$this->output()->writeln('Found ' . count($fields) . ' fields.');
foreach ($fields as $field) {
$this->output()->writeln($field);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Drupal\va_gov_live_field_migration\Database;

use Drupal\Core\Database\Connection;

/**
* A database service to abstract database operations.
*/
class Database implements DatabaseInterface {

/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;

/**
* Constructor.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(
Connection $connection
) {
$this->connection = $connection;
}

/**
* {@inheritDoc}
*/
public function dropTable(string $table): void {
$this->connection->schema()->dropTable($table);
}

/**
* {@inheritDoc}
*/
public function getPrimaryFieldTableName(string $entityType, string $fieldName): string {
return "{$entityType}__{$fieldName}";
}

/**
* {@inheritDoc}
*/
public function getFieldRevisionTableName(string $entityType, string $fieldName): string {
return "{$entityType}__{$fieldName}";
}

/**
* {@inheritDoc}
*/
public function getBackupTableName(string $tableName): string {
// Table names are limited to 64 characters, but Drupal field tables can
// be longer than that. So we need to truncate the table name.
$tableName = substr($tableName, 0, 64 - strlen(self::BACKUP_TABLE_SUFFIX));
return "{$tableName}__backup";
}

/**
* {@inheritDoc}
*/
public function createTable(string $newTable, string $existingTable): void {
$this->connection->query("CREATE TABLE {$newTable} LIKE {$existingTable};");
}

/**
* {@inheritDoc}
*/
public function copyTable(string $sourceTable, string $destinationTable, bool $preserve = FALSE): void {
if (!$preserve) {
$this->dropTable($destinationTable);
$this->createTable($destinationTable, $sourceTable);
}
$this->connection->query("INSERT {$destinationTable} SELECT * FROM {$sourceTable};");
}

/**
* {@inheritDoc}
*/
public function backupPrimaryFieldTable(string $entityType, string $fieldName, bool $preserve = FALSE): void {
$primaryTable = $this->getPrimaryFieldTableName($entityType, $fieldName);
$backupTable = $this->getBackupTableName($primaryTable);
$this->copyTable($primaryTable, $backupTable, $preserve);
}

/**
* {@inheritDoc}
*/
public function backupFieldRevisionTable(string $entityType, string $fieldName, bool $preserve = FALSE): void {
$revisionTable = $this->getFieldRevisionTableName($entityType, $fieldName);
$backupTable = $this->getBackupTableName($revisionTable);
$this->copyTable($revisionTable, $backupTable, $preserve);
}

/**
* {@inheritDoc}
*/
public function restorePrimaryFieldTable(string $entityType, string $fieldName, bool $preserve = FALSE): void {
$primaryTable = $this->getPrimaryFieldTableName($entityType, $fieldName);
$backupTable = $this->getBackupTableName($primaryTable);
$this->copyTable($backupTable, $primaryTable, $preserve);
}

/**
* {@inheritDoc}
*/
public function restoreFieldRevisionTable(string $entityType, string $fieldName, bool $preserve = FALSE): void {
$revisionTable = $this->getFieldRevisionTableName($entityType, $fieldName);
$backupTable = $this->getBackupTableName($revisionTable);
$this->copyTable($backupTable, $revisionTable, $preserve);
}

}
Loading
Loading