From b0c5b6943e204b0d4cd35a20a072ad8a352c4bf1 Mon Sep 17 00:00:00 2001 From: Yeni Atencio Date: Sun, 17 Nov 2024 10:22:07 +1100 Subject: [PATCH] Added batch service and drush command --- drush.services.yml | 9 + src/Batch/BatchService.php | 187 ++++++++++++++++++ src/Batch/BatchServiceInterface.php | 53 +++++ .../TideParagraphRevisionsCleanUp.php | 113 +++++++++++ tide_core.services.yml | 5 + 5 files changed, 367 insertions(+) create mode 100644 src/Batch/BatchService.php create mode 100644 src/Batch/BatchServiceInterface.php create mode 100644 src/Commands/TideParagraphRevisionsCleanUp.php diff --git a/drush.services.yml b/drush.services.yml index 3cd5a78e4..e1e332e07 100644 --- a/drush.services.yml +++ b/drush.services.yml @@ -4,4 +4,13 @@ services: arguments: ['@keyvalue'] tags: - { name: drush.command } + tide_core.batch_command: + class: \Drupal\tide_core\Commands\TideParagraphRevisionsCleanUp + tags: + - { name: drush.command } + arguments: + - '@entity_type.manager' + - '@logger.factory' + - '@tide_core.batch' + diff --git a/src/Batch/BatchService.php b/src/Batch/BatchService.php new file mode 100644 index 000000000..cd3e3c380 --- /dev/null +++ b/src/Batch/BatchService.php @@ -0,0 +1,187 @@ +entityTypeManager = $entityTypeManager; + $this->loggerChannel = $loggerFactory->get('tide_core'); + } + + /** + * {@inheritdoc} + */ + public function create(int $batchSize = 100, array $data = []): void { + if (empty($data)) { + $this->loggerChannel->notice('There is no data to process.'); + } + else { + $batch = new BatchBuilder(); + $batch->setTitle($this->t('Running batch process.')) + ->setFinishCallback([self::class, 'batchFinished']) + ->setInitMessage('Commencing') + ->setProgressMessage('Processing...') + ->setErrorMessage('An error occurred during processing.'); + + // Create chunks of all items. + $chunks = array_chunk($data, $batchSize); + + // Process each chunk in the array. + foreach ($chunks as $id => $chunk) { + $args = [ + $id, + $chunk, + ]; + $batch->addOperation([BatchService::class, 'batchProcess'], $args); + } + batch_set($batch->toArray()); + + $this->loggerChannel->notice('Batch created.'); + drush_backend_batch_process(); + + // Finish. + $this->loggerChannel->notice('Batch operations end.'); + } + } + + /** + * {@inheritdoc} + */ + public static function batchProcess(int $batchId, array $chunk, array &$context): void { + if (!isset($context['sandbox']['progress'])) { + $context['sandbox']['progress'] = 0; + $context['sandbox']['max'] = 1000; + } + if (!isset($context['results']['updated'])) { + $context['results']['updated'] = 0; + $context['results']['skipped'] = 0; + $context['results']['failed'] = 0; + $context['results']['progress'] = 0; + $context['results']['process'] = 'Batch processing completed'; + } + + // Keep track of progress. + $context['results']['progress'] += count($chunk); + + // Message above progress bar. + $context['message'] = t('Processing batch #@batch_id, batch size @batch_size for total @count items.', [ + '@batch_id' => number_format($batchId), + '@batch_size' => number_format(count($chunk)), + '@count' => number_format($context['sandbox']['max']), + ]); + + foreach ($chunk as $dataProcessed) { + $result = self::cleanRevisions($dataProcessed['revision_list']); + switch ($result) { + case 1: + $context['results']['updated']++; + break; + + case 0: + $context['results']['skipped']++; + break; + } + } + } + + /** + * {@inheritdoc} + */ + public static function batchFinished(bool $success, array $results, array $operations, string $elapsed): void { + // Grab the messenger service, this will be needed if the batch was a + // success or a failure. + $messenger = \Drupal::messenger(); + if ($success) { + // The success variable was true, which indicates that the batch process + // was successful (i.e. no errors occurred). + // Show success message to the user. + $messenger->addMessage(t('@process processed @count, skipped @skipped, updated @updated, failed @failed in @elapsed.', [ + '@process' => $results['process'], + '@count' => $results['progress'], + '@skipped' => $results['skipped'], + '@updated' => $results['updated'], + '@failed' => $results['failed'], + '@elapsed' => $elapsed, + ])); + // Log the batch success. + \Drupal::logger('batch_form_example')->info( + '@process processed @count, skipped @skipped, updated @updated, failed @failed in @elapsed.', + [ + '@process' => $results['process'], + '@count' => $results['progress'], + '@skipped' => $results['skipped'], + '@updated' => $results['updated'], + '@failed' => $results['failed'], + '@elapsed' => $elapsed, + ] + ); + } else { + // An error occurred. $operations contains the operations that remained + // unprocessed. Pick the last operation and report on what happened. + $error_operation = reset($operations); + if ($error_operation) { + $message = t('An error occurred while processing %error_operation with arguments: @arguments', [ + '%error_operation' => print_r($error_operation[0]), + '@arguments' => print_r($error_operation[1], TRUE), + ]); + $messenger->addError($message); + } + } + } + + /** + * {@inheritdoc} + */ + public static function cleanRevisions(array $revisionIds): int { + $storage = \Drupal::entityTypeManager()->getStorage('node'); + /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */ + if (!empty($revisionIds)) { + if (count($revisionIds)) { + return 2; + } + + foreach ($revisionIds as $revisionId) { + $storage->deleteRevision($revisionId); + } + + return 1; + } + else { + return 0; + } + } +} diff --git a/src/Batch/BatchServiceInterface.php b/src/Batch/BatchServiceInterface.php new file mode 100644 index 000000000..425f3b8e9 --- /dev/null +++ b/src/Batch/BatchServiceInterface.php @@ -0,0 +1,53 @@ +entityTypeManager = $entityTypeManager; + $this->loggerChannel = $loggerFactory->get('tide_core'); + $this->batch = $batch; + } + + /** + * Get node revisions per node. + * + * @return array + * A list of nodes with their corresponding node revisions. + */ + public function getNodeRevisions(string $timeAgo) { + $nodeRevisions = []; + $storage = $this->entityTypeManager->getStorage('node'); + + try { + $query = $storage->getQuery() + ->condition('status', '1') + ->condition('created', $timeAgo, '<') + ->range(0, 500) + ->accessCheck(FALSE); + $nids = $query->execute(); + + if (!empty($nids)) { + foreach($nids as $vid => $nid) { + $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid); + $revisionIds = $storage->revisionIds($node); + $latestRevisionId = $storage->getLatestRevisionId($nid); + $nodeRevisions[$nid] = [ + 'revision_list' => $revisionIds, + 'current_revision' => $latestRevisionId, + ]; + } + } + } catch (\Exception $e) { + $this->output()->writeln($e); + $this->loggerChannel->warning('Error found @e', ['@e' => $e]); + } + + return $nodeRevisions; + } + + /** + * Delete paragraphs revisions. + * + * @command tide_core:paragraph-revisions-cleanup + * @aliases dpr + * + * @usage tide_core:paragraph-revisions-cleanup --batch=150 --older='30 days' + */ + public function deleteParagraphRevision(array $options = ['batch' => 10, 'older' => '30 days']) { + $timeAgo = strtotime('-' . $options['older']); + $data = $this->getNodeRevisions($timeAgo); + if (!empty($data)) { + foreach ($data as $key => $value) { + foreach($value['revision_list'] as $krid => $rid) { + if ($value['current_revision'] == $rid) { + unset($data[$key]['revision_list'][$krid]); + } + } + unset($data[$key]['current_revision']); + } + } + + $this->batch->create($options['batch'], $data); + } +} diff --git a/tide_core.services.yml b/tide_core.services.yml index c332995ba..6923ec32d 100644 --- a/tide_core.services.yml +++ b/tide_core.services.yml @@ -35,3 +35,8 @@ services: - '@logger.factory' - '@file_system' - '@monitoring.sensor_runner' + tide_core.batch: + class: 'Drupal\tide_core\Batch\BatchService' + arguments: + - '@entity_type.manager' + - '@logger.factory'