Commit 0968d287 authored by catch's avatar catch
Browse files

Issue #2958752 by amateescu, timmillwood, catch: Refactor workspace content...

Issue #2958752 by amateescu, timmillwood, catch: Refactor workspace content replication plugin system to three services
parent 5a7dc82f
<?php
namespace Drupal\workspace\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a RepositoryHandler annotation object.
*
* @see \Drupal\workspace\RepositoryHandlerInterface
* @see \Drupal\workspace\RepositoryHandlerBase
* @see \Drupal\workspace\RepositoryHandlerManager
* @see plugin_api
*
* @Annotation
*/
class RepositoryHandler extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the repository handler plugin.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;
/**
* A short description of the repository handler plugin.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $description;
/**
* The human-readable category.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $category = '';
}
......@@ -108,35 +108,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setLabel(new TranslatableMarkup('Created'))
->setDescription(new TranslatableMarkup('The time that the workspaces was created.'));
$fields['target'] = BaseFieldDefinition::create('string')
->setLabel(new TranslatableMarkup('Target workspace'))
->setDescription(new TranslatableMarkup('The workspace to push to and pull from.'))
->setRevisionable(TRUE)
->setRequired(TRUE)
->setDefaultValue('live');
return $fields;
}
/**
* {@inheritdoc}
*/
public function push() {
return $this->getRepositoryHandler()->push();
}
/**
* {@inheritdoc}
*/
public function pull() {
return $this->getRepositoryHandler()->pull();
}
/**
* {@inheritdoc}
*/
public function getRepositoryHandler() {
return \Drupal::service('plugin.manager.workspace.repository_handler')->createFromWorkspace($this);
public function publish() {
return \Drupal::service('workspace.operation_factory')->getPublisher($this)->publish();
}
/**
......
......@@ -24,7 +24,7 @@ class WorkspaceDeleteForm extends ContentEntityDeleteForm {
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$source_rev_diff = $this->entity->getRepositoryHandler()->getDifferringRevisionIdsOnSource();
$source_rev_diff = $this->entityTypeManager->getStorage('workspace_association')->getTrackedEntities($this->entity->id());
$items = [];
foreach ($source_rev_diff as $entity_type_id => $revision_ids) {
$label = $this->entityTypeManager->getDefinition($entity_type_id)->getLabel();
......
......@@ -8,6 +8,7 @@
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\workspace\WorkspaceOperationFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -29,21 +30,31 @@ class WorkspaceDeployForm extends ContentEntityForm {
*/
protected $messenger;
/**
* The workspace operation factory.
*
* @var \Drupal\workspace\WorkspaceOperationFactory
*/
protected $workspaceOperationFactory;
/**
* Constructs a new WorkspaceDeployForm.
*
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository service.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
* @param \Drupal\workspace\WorkspaceOperationFactory $workspace_operation_factory
* The workspace operation factory service.
*/
public function __construct(EntityRepositoryInterface $entity_repository, MessengerInterface $messenger, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, MessengerInterface $messenger, WorkspaceOperationFactory $workspace_operation_factory) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->messenger = $messenger;
$this->workspaceOperationFactory = $workspace_operation_factory;
}
/**
......@@ -52,9 +63,10 @@ public function __construct(EntityRepositoryInterface $entity_repository, Messen
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.repository'),
$container->get('messenger'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time')
$container->get('datetime.time'),
$container->get('messenger'),
$container->get('workspace.operation_factory')
);
}
......@@ -64,17 +76,17 @@ public static function create(ContainerInterface $container) {
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$repository_handler = $this->entity->getRepositoryHandler();
$workspace_publisher = $this->workspaceOperationFactory->getPublisher($this->entity);
$args = [
'%source_label' => $this->entity->label(),
'%target_label' => $repository_handler->getLabel(),
'%target_label' => $workspace_publisher->getTargetLabel(),
];
$form['#title'] = $this->t('Deploy %source_label workspace', $args);
// List the changes that can be pushed.
if ($source_rev_diff = $repository_handler->getDifferringRevisionIdsOnSource()) {
$total_count = $repository_handler->getNumberOfChangesOnSource();
if ($source_rev_diff = $workspace_publisher->getDifferringRevisionIdsOnSource()) {
$total_count = $workspace_publisher->getNumberOfChangesOnSource();
$form['deploy'] = [
'#theme' => 'item_list',
'#title' => $this->formatPlural($total_count, 'There is @count item that can be deployed from %source_label to %target_label', 'There are @count items that can be deployed from %source_label to %target_label', $args),
......@@ -86,20 +98,6 @@ public function form(array $form, FormStateInterface $form_state) {
}
}
// List the changes that can be pulled.
if ($target_rev_diff = $repository_handler->getDifferringRevisionIdsOnTarget()) {
$total_count = $repository_handler->getNumberOfChangesOnTarget();
$form['refresh'] = [
'#theme' => 'item_list',
'#title' => $this->formatPlural($total_count, 'There is @count item that can be refreshed from %target_label to %source_label', 'There are @count items that can be refreshed from %target_label to %source_label', $args),
'#items' => [],
'#total_count' => $total_count,
];
foreach ($target_rev_diff as $entity_type_id => $revision_difference) {
$form['deploy']['#items'][$entity_type_id] = $this->entityTypeManager->getDefinition($entity_type_id)->getCountLabel(count($revision_difference));
}
}
// If there are no changes to push or pull, show an informational message.
if (!isset($form['deploy']) && !isset($form['refresh'])) {
$form['help'] = [
......@@ -117,11 +115,11 @@ public function actions(array $form, FormStateInterface $form_state) {
$elements = parent::actions($form, $form_state);
unset($elements['delete']);
$repository_handler = $this->entity->getRepositoryHandler();
$workspace_publisher = $this->workspaceOperationFactory->getPublisher($this->entity);
if (isset($form['deploy'])) {
$total_count = $form['deploy']['#total_count'];
$elements['submit']['#value'] = $this->formatPlural($total_count, 'Deploy @count item to @target', 'Deploy @count items to @target', ['@target' => $repository_handler->getLabel()]);
$elements['submit']['#value'] = $this->formatPlural($total_count, 'Deploy @count item to @target', 'Deploy @count items to @target', ['@target' => $workspace_publisher->getTargetLabel()]);
$elements['submit']['#submit'] = ['::submitForm', '::deploy'];
}
else {
......@@ -130,16 +128,6 @@ public function actions(array $form, FormStateInterface $form_state) {
$elements['submit']['#disabled'] = TRUE;
}
// Only show the 'Refresh' operation if there's something to pull.
if (isset($form['refresh'])) {
$total_count = $form['refresh']['#total_count'];
$elements['refresh'] = [
'#type' => 'submit',
'#value' => $this->formatPlural($total_count, 'Refresh @count item from @target', 'Refresh @count items from @target', ['@target' => $repository_handler->getLabel()]),
'#submit' => ['::submitForm', '::refresh'],
];
}
$elements['cancel'] = [
'#type' => 'link',
'#title' => $this->t('Cancel'),
......@@ -162,7 +150,7 @@ public function deploy(array &$form, FormStateInterface $form_state) {
$workspace = $this->entity;
try {
$workspace->push();
$workspace->publish();
$this->messenger->addMessage($this->t('Successful deployment.'));
}
catch (\Exception $e) {
......@@ -170,24 +158,4 @@ public function deploy(array &$form, FormStateInterface $form_state) {
}
}
/**
* Form submission handler; pulls the target's content into a workspace.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function refresh(array &$form, FormStateInterface $form_state) {
$workspace = $this->entity;
try {
$workspace->pull();
$this->messenger->addMessage($this->t('Refresh successful.'));
}
catch (\Exception $e) {
$this->messenger->addMessage($this->t('Refresh failed. All errors have been logged.'), 'error');
}
}
}
......@@ -51,7 +51,6 @@ public function getActiveWorkspace(Request $request) {
$default_workspace = $this->workspaceStorage->create([
'id' => WorkspaceInterface::DEFAULT_WORKSPACE,
'label' => Unicode::ucwords(WorkspaceInterface::DEFAULT_WORKSPACE),
'target' => '',
]);
$default_workspace->enforceIsNew(FALSE);
......
<?php
namespace Drupal\workspace\Plugin\RepositoryHandler;
use Drupal\Core\Plugin\PluginBase;
use Drupal\workspace\RepositoryHandlerInterface;
/**
* Defines a fallback repository handler plugin.
*
* @RepositoryHandler(
* id = "null",
* label = @Translation("Missing repository handler"),
* description = @Translation("Provides a fallback for missing repository handlers. Do not use."),
* )
*/
class NullRepositoryHandler extends PluginBase implements RepositoryHandlerInterface {
/**
* {@inheritdoc}
*/
public function push() {
// Nothing to do here.
}
/**
* {@inheritdoc}
*/
public function pull() {
// Nothing to do here.
}
/**
* {@inheritdoc}
*/
public function checkConflictsOnTarget() {
return [];
}
/**
* {@inheritdoc}
*/
public function getDifferringRevisionIdsOnTarget() {
return [];
}
/**
* {@inheritdoc}
*/
public function getDifferringRevisionIdsOnSource() {
return [];
}
/**
* {@inheritdoc}
*/
public function getNumberOfChangesOnTarget() {
return 0;
}
/**
* {@inheritdoc}
*/
public function getNumberOfChangesOnSource() {
return 0;
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->getPluginDefinition()['label'];
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->getPluginDefinition()['description'];
}
}
<?php
namespace Drupal\workspace;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Plugin\PluginBase;
/**
* Defines a base RepositoryHandler plugin implementation.
*
* @see \Drupal\workspace\RepositoryHandlerInterface
* @see \Drupal\workspace\RepositoryHandlerManager
* @see \Drupal\workspace\Annotation\RepositoryHandler
* @see plugin_api
*/
abstract class RepositoryHandlerBase extends PluginBase implements RepositoryHandlerInterface {
use DependencyTrait;
/**
* The source repository identifier.
*
* @var string
*/
protected $source;
/**
* The target repository identifier.
*
* @var string
*/
protected $target;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
if (!isset($configuration['source'])) {
throw new \InvalidArgumentException('Missing repository handler source configuration');
}
if (!isset($configuration['target'])) {
throw new \InvalidArgumentException('Missing repository handler target configuration');
}
$this->source = $configuration['source'];
$this->target = $configuration['target'];
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->getPluginDefinition()['label'];
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->getPluginDefinition()['description'];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
}
<?php
namespace Drupal\workspace;
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Provides a plugin manager for Repository Handlers.
*
* @see \Drupal\workspace\Annotation\RepositoryHandler
* @see \Drupal\workspace\RepositoryHandlerInterface
* @see plugin_api
*/
class RepositoryHandlerManager extends DefaultPluginManager implements RepositoryHandlerManagerInterface, FallbackPluginManagerInterface {
use CategorizingPluginManagerTrait;
/**
* Constructs a new RepositoryHandlerManager.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/RepositoryHandler', $namespaces, $module_handler, 'Drupal\workspace\RepositoryHandlerInterface', 'Drupal\workspace\Annotation\RepositoryHandler');
$this->alterInfo('workspace_repository_handler_info');
$this->setCacheBackend($cache_backend, 'workspace_repository_handler');
}
/**
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
$this->processDefinitionCategory($definition);
}
/**
* {@inheritdoc}
*/
public function createFromWorkspace(WorkspaceInterface $workspace) {
$target = $workspace->target->value;
$configuration = [
'source' => $workspace->id(),
'target' => $target,
];
return $this->createInstance($target, $configuration);
}
/**
* {@inheritdoc}
*/
public function getFallbackPluginId($plugin_id, array $configuration = []) {
return 'null';
}
}
<?php
namespace Drupal\workspace;
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
/**
* Provides the interface for a plugin manager of repository handlers.
*/
interface RepositoryHandlerManagerInterface extends CategorizingPluginManagerInterface {
/**
* Creates a repository handler instance from a given workspace entity.
*
* @param \Drupal\workspace\WorkspaceInterface $workspace
* A workspace entity.
*
* @return \Drupal\workspace\RepositoryHandlerInterface
* A repository handler plugin.
*/
public function createFromWorkspace(WorkspaceInterface $workspace);
}
......@@ -17,22 +17,9 @@ interface WorkspaceInterface extends ContentEntityInterface, EntityChangedInterf
const DEFAULT_WORKSPACE = 'live';
/**
* Pushes content from this workspace to the target repository.
* Publishes the contents of this workspace to the default (Live) workspace.
*/
public function push();
/**
* Pulls content from the target repository into this workspace.
*/
public function pull();
/**
* Gets an instance of the repository handler configured for the workspace.
*
* @return \Drupal\workspace\RepositoryHandlerInterface
* A repository handler plugin object.
*/
public function getRepositoryHandler();
public function publish();
/**
* Determines whether the workspace is the default one or not.
......
......@@ -6,7 +6,6 @@
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\workspace\Plugin\RepositoryHandler\NullRepositoryHandler;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -94,7 +93,7 @@ public function getDefaultOperations(EntityInterface $entity) {
];
}
if (!$entity->getRepositoryHandler() instanceof NullRepositoryHandler) {
if (!$entity->isDefaultWorkspace()) {
$operations['deploy'] = [
'title' => $this->t('Deploy content'),
// The 'Deploy' operation should be the default one for the currently
......
<?php
namespace Drupal\workspace;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* Defines a factory class for workspace operations.
*
* @see \Drupal\workspace\WorkspaceOperationInterface
* @see \Drupal\workspace\WorkspacePublisherInterface
*
* @internal
*/
class WorkspaceOperationFactory {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a new WorkspacePublisher.
*