data_pipelines-1.x-dev/src/Form/DatasetBatchOperations.php

src/Form/DatasetBatchOperations.php
<?php

declare(strict_types=1);

namespace Drupal\data_pipelines\Form;

use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Queue\QueueWorkerManagerInterface;
use Drupal\Core\Queue\RequeueException;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\data_pipelines\Entity\Dataset;
use Drupal\data_pipelines\Entity\DatasetInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Defines a class for building and running dataset batched operations.
 */
class DatasetBatchOperations implements ContainerInjectionInterface {

  /**
   * Constructs a new PreviewSiteBuilder.
   *
   * @param \Drupal\Core\Queue\QueueFactory $queueFactory
   *   Queue factory.
   * @param \Drupal\Core\Queue\QueueWorkerManagerInterface $queueManager
   *   Queue manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   */
  final public function __construct(protected QueueFactory $queueFactory, protected QueueWorkerManagerInterface $queueManager, protected EntityTypeManagerInterface $entityTypeManager) {
  }

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

  /**
   * Factory method.
   *
   * @return \Drupal\data_pipelines\Form\DatasetBatchOperations
   *   New instance.
   */
  protected static function factory(): DatasetBatchOperations {
    $instance = \Drupal::classResolver(self::class);
    assert($instance instanceof DatasetBatchOperations);
    return $instance;
  }

  /**
   * Gets batch for dataset.
   *
   * @param \Drupal\data_pipelines\Entity\DatasetInterface $dataset
   *   Dataset.
   * @param bool $saving
   *   TRUE if Dataset is being saved FALSE otherwise.
   *
   * @return array
   *   Batch.
   */
  public static function batchForDataset(DatasetInterface $dataset, bool $saving = FALSE): array {
    if (!$dataset->isPublished()) {
      return [];
    }
    $batch_builder = new BatchBuilder();
    $batch_builder
      ->setTitle(new TranslatableMarkup('Validating and indexing dataset %name', [
        '%name' => $dataset->label(),
      ]))
      ->setInitMessage(new TranslatableMarkup('Preparing...'))
      ->setProgressive(TRUE)
      ->setFinishCallback([self::class, 'finished'])
      ->setProgressMessage(new TranslatableMarkup('Step @current of @total'))
      ->addOperation([self::class, 'operationQueueItem'], [$dataset->id()])
      ->addOperation([self::class, 'operationProcess'], [$dataset->id()]);
    return $batch_builder->toArray();
  }

  /**
   * Batch callback.
   */
  public static function operationQueueItem(int $dataset_id, &$context): void {
    $context['results']['dataset_id'] = $dataset_id;
    $dataset = Dataset::load($dataset_id);
    assert($dataset instanceof DatasetInterface);
    if (!$context['sandbox']) {
      $context['sandbox']['context'] = new DatasetBatchContext($dataset->getBatchSize());
      $dataset->resetLogs();
      $dataset->setPendingProcessing();
    }
    $inner_context = &$context['sandbox']['context'];
    $context['finished'] = 0;
    $dataset->queueProcessing(\Drupal::queue($dataset->getProcessingQueueId()), $inner_context);
    $context['message'] = new TranslatableMarkup('Queueing processing, queued @count records.', ['@count' => $inner_context->getProcessed()]);
    if ($inner_context->isFinished()) {
      $context['finished'] = 1;
      $context['message'] = new TranslatableMarkup('Data is successfully queued for processing.');
    }
  }

  /**
   * Batch callback.
   */
  public static function operationProcess(int $dataset_id, &$context): void {
    $dataset = Dataset::load($dataset_id);
    assert($dataset instanceof DatasetInterface);
    if ($dataset->isPendingProcessing()) {
      $remaining = self::factory()->process($dataset);
      self::updateFinishedPercent($remaining, $context);
      $total = $context['sandbox']['total'];
      $context['message'] = new TranslatableMarkup('Processed @count of @total (@percent %) chunks, @remaining remaining.', [
        '@count' => number_format($total - $remaining),
        '@total' => number_format($total),
        '@percent' => $total > 0 ? round(100 * ($total - $remaining) / $total) : 0,
        '@remaining' => number_format($remaining),
      ]);
      if ($remaining === 0) {
        $dataset->setProcessingComplete();
        $context['message'] = new TranslatableMarkup('Processed dataset.');
        return;
      }
      return;
    }
    $context['finished'] = 1;
    $context['message'] = new TranslatableMarkup('Skipped processing dataset, validation errors exist.');
  }

  /**
   * Index a dataset.
   *
   * @param \Drupal\data_pipelines\Entity\DatasetInterface $dataset
   *   Dataset.
   *
   * @return int
   *   Number remaining.
   */
  protected function process(DatasetInterface $dataset): int {
    return $this->processQueueItem($dataset->getProcessingQueueId());
  }

  /**
   * Processes a queue item.
   *
   * @param string $queue_name
   *   Queue name.
   *
   * @return int
   *   Number of remaining items.
   */
  protected function processQueueItem(string $queue_name) : int {
    $queue = $this->queueFactory->get($queue_name);
    $item = $queue->claimItem();
    if (!$item) {
      return 0;
    }
    $queue->createQueue();
    $worker = $this->queueManager->createInstance($queue_name);
    try {
      $worker->processItem($item->data);
      $queue->deleteItem($item);
    }
    catch (RequeueException $e) {
      $queue->releaseItem($item);
    }
    return $queue->numberOfItems();
  }

  /**
   * Batch finished callback.
   */
  public static function finished(bool $success, array $results) {
    if (!empty($results['dataset_id'])) {
      $dataset = Dataset::load($results['dataset_id']);
      assert($dataset instanceof DatasetInterface);
      $log_count = count($dataset->getLogs());
      if ($dataset->isProcessed() && $log_count === 0) {
        $message = new TranslatableMarkup('Dataset %name was successfully processed.', ['%name' => $dataset->label()]);
        static::log($message);
        return;
      }
      $message = new TranslatableMarkup('Dataset %name was processed but some rows failed validation or skipped and were not processed. Please check <a href=":url">the logs</a>.', [
        '%name' => $dataset->label(),
        ':url' => $dataset->toUrl()->toString(),
      ]);
      static::log($message, TRUE);
      return;
    }
    $message = new TranslatableMarkup('The validation and processing operation did not complete.');
    static::log($message, TRUE);
  }

  /**
   * A method to facilitate logging.
   *
   * @param \Drupal\Component\Render\MarkupInterface $message
   *   The message.
   * @param bool $error
   *   A boolean indicating whether the log is an error.
   */
  protected static function log(MarkupInterface $message, bool $error = FALSE): void {
    \Drupal::logger('data_pipelines')->{$error ? 'warning' : 'info'}($message);
    \Drupal::messenger()->{$error ? 'addError' : 'addMessage'}($message);
  }

  /**
   * Updates finished percent.
   *
   * @param int $remaining
   *   Remaining items.
   * @param array $context
   *   Batch context.
   */
  protected static function updateFinishedPercent(int $remaining, &$context): void {
    if ($remaining === 0) {
      $context['finished'] = 1;
      $context['sandbox']['total'] = 1;
      return;
    }
    if (!isset($context['sandbox']['total'])) {
      $context['sandbox']['total'] = $remaining + 1;
    }
    $context['finished'] = ($context['sandbox']['total'] - $remaining) / $context['sandbox']['total'];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc