diff --git a/modules/datastore/src/Form/DashboardForm.php b/modules/datastore/src/Form/DashboardForm.php index 9e1b78f718..4a2a2bb538 100644 --- a/modules/datastore/src/Form/DashboardForm.php +++ b/modules/datastore/src/Form/DashboardForm.php @@ -2,14 +2,16 @@ namespace Drupal\datastore\Form; -use Drupal\common\DataResource; -use Drupal\Core\Pager\PagerManagerInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Datetime\DateFormatter; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\common\DatasetInfo; -use Drupal\Core\Datetime\DateFormatter; +use Drupal\Core\Pager\PagerManagerInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; +use Drupal\common\DataResource; +use Drupal\common\DatasetInfo; use Drupal\common\UrlHostTokenResolver; use Drupal\harvest\HarvestService; use Drupal\metastore\MetastoreService; @@ -71,6 +73,13 @@ class DashboardForm extends FormBase { */ protected PostImportResultFactory $postImportResultFactory; + /** + * Node storage service. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected EntityStorageInterface $nodeStorage; + /** * DashboardController constructor. * @@ -86,6 +95,8 @@ class DashboardForm extends FormBase { * Date formatter service. * @param \Drupal\datastore\PostImportResultFactory $postImportResultFactory * The PostImportResultFactory service.. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * Entity type manager service. */ public function __construct( HarvestService $harvestService, @@ -93,13 +104,15 @@ public function __construct( MetastoreService $metastoreService, PagerManagerInterface $pagerManager, DateFormatter $dateFormatter, - PostImportResultFactory $postImportResultFactory + PostImportResultFactory $postImportResultFactory, + EntityTypeManagerInterface $entityTypeManager, ) { $this->harvest = $harvestService; $this->datasetInfo = $datasetInfo; $this->metastore = $metastoreService; $this->pagerManager = $pagerManager; $this->dateFormatter = $dateFormatter; + $this->nodeStorage = $entityTypeManager->getStorage('node'); $this->itemsPerPage = 10; $this->postImportResultFactory = $postImportResultFactory; } @@ -115,6 +128,7 @@ public static function create(ContainerInterface $container): self { $container->get('pager.manager'), $container->get('date.formatter'), $container->get('dkan.datastore.post_import_result_factory'), + $container->get('entity_type.manager'), ); } @@ -190,6 +204,12 @@ protected function buildFilters(array $filters): array { '#title' => $this->t('Dataset ID'), '#default_value' => $filters['uuid'] ?? '', ], + 'dataset_title' => [ + '#type' => 'textfield', + '#weight' => 1, + '#title' => $this->t('Dataset Title'), + '#default_value' => $filters['dataset_title'] ?? '', + ], 'harvest_id' => [ '#type' => 'select', '#weight' => 1, @@ -241,11 +261,20 @@ public function buildTable(array $datasets): array { /** * Retrieve list of UUIDs for datasets matching the given filters. * + * Filters over-ride each other, in this order of priority: + * - UUID + * - Title search + * - Harvest plan ID. + * * @param string[] $filters - * Datasets filters. + * Datasets filters. Keys determine the filter. Recognized keys: + * - uuid - Dataset UUID. + * - dataset_title - A CONTAINS search within the dataset title field. + * - harvest_id - A harvest plan ID. * * @return string[] - * Filtered list of dataset UUIDs. + * Paged, filtered list of dataset UUIDs. If no filter was supplied, all + * dataset UUIDs will be returned, paged. */ protected function getDatasets(array $filters): array { $datasets = []; @@ -255,16 +284,17 @@ protected function getDatasets(array $filters): array { if (isset($filters['uuid'])) { $datasets = [$filters['uuid']]; } + // Is the user searching for a dataset title? + elseif (isset($filters['dataset_title'])) { + $results = $this->getDatasetsByTitle($filters); + $datasets = $this->pagedFilteredList($results); + } // If a value was supplied for the harvest ID filter, retrieve dataset UUIDs - // belonging to the specfied harvest. + // belonging to the specified harvest. elseif (isset($filters['harvest_id'])) { $harvestLoad = iterator_to_array($this->getHarvestLoadStatus($filters['harvest_id'])); $datasets = array_keys($harvestLoad); - $total = count($datasets); - $currentPage = $this->pagerManager->createPager($total, $this->itemsPerPage)->getCurrentPage(); - - $chunks = array_chunk($datasets, $this->itemsPerPage) ?: [[]]; - $datasets = $chunks[$currentPage]; + $datasets = $this->pagedFilteredList($datasets); } // If no filter values were supplied, fetch from the list of all dataset // UUIDs. @@ -282,6 +312,50 @@ protected function getDatasets(array $filters): array { return $datasets; } + /** + * Entity query for nodes containing the dataset title. + * + * @param string[] $filters + * Datasets filters. + * + * @return string[] + * Dataset UUIDs . + */ + protected function getDatasetsByTitle(array $filters): array { + // Get the ids using an entity query, because our dataset title is in the + // node title field. + // @todo Unify different queries against Data nodes using a repository or + // the NodeData wrapper. + $results = $this->nodeStorage->getQuery() + ->accessCheck(FALSE) + ->condition('type', 'data') + ->condition('field_data_type', 'dataset') + ->condition('title', $filters['dataset_title'], 'CONTAINS') + ->execute(); + foreach ($this->nodeStorage->loadMultiple($results) as $node) { + $datasets[] = $node->uuid(); + } + + return $datasets; + } + + /** + * Paged, filtered list of dataset UUIDs. + * + * @param string[] $datasets + * Dataset UUIDs. + * + * @return string[] + * Paged, filtered list of dataset UUIDs. + */ + protected function pagedFilteredList(array $datasets): array { + $total = count($datasets); + $currentPage = $this->pagerManager->createPager($total, $this->itemsPerPage)->getCurrentPage(); + $chunks = array_chunk($datasets, $this->itemsPerPage) ?: [[]]; + + return $chunks[$currentPage]; + } + /** * Builds dataset rows array. * diff --git a/modules/datastore/tests/src/Unit/Form/DashboardFormTest.php b/modules/datastore/tests/src/Unit/Form/DashboardFormTest.php index 378923bdbc..e9c4317c79 100644 --- a/modules/datastore/tests/src/Unit/Form/DashboardFormTest.php +++ b/modules/datastore/tests/src/Unit/Form/DashboardFormTest.php @@ -28,6 +28,10 @@ use Drupal\datastore\PostImportResult; use Drupal\datastore\PostImportResultFactory; use Drupal\common\DataResource; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\node\NodeInterface; /** * @group dkan @@ -151,6 +155,72 @@ public function testBuildTableRowsWithHarvestIdFilter() { $this->assertEquals(NULL, $form['table']['#rows'][0][6]['data']['#error']); } + /** + * Test building the dashboard table with a Dataset Title filter. + */ + public function testBuildTableRowsWithDatasetTitleFilter() { + $info = [ + 'uuid' => 'dataset-1', + 'title' => 'Dataset 1', + 'revision_id' => '2', + 'moderation_state' => 'published', + 'modified_date_metadata' => '2020-01-15', + 'modified_date_dkan' => '2021-02-11', + ]; + $distribution = [ + 'distribution_uuid' => 'dist-1', + 'resource_id' => '9ad17d45894f823c6a8e4f6d32b9535f', + 'resource_version' => '1679508886', + 'fetcher_status' => 'done', + 'fetcher_percent_done' => 100, + 'importer_status' => 'done', + 'importer_percent_done' => 100, + 'importer_error' => '', + 'source_path' => 'http://example.com/file.csv', + ]; + + $postImportInfo = [ + 'resource_version' => '1679508886', + 'post_import_status' => 'done', + 'post_import_error' => NULL, + ]; + + $connectionMock = $this->createMock(Connection::class); + $resourceMappermock = $this->createMock(ResourceMapper::class); + $dataResourceMock = $this->createMock(DataResource::class); + $postImportResultMock = $this->getMockBuilder(PostImportResult::class) + ->setConstructorArgs(['', '', NULL, $dataResourceMock, $connectionMock, $resourceMappermock]) + ->onlyMethods(['retrieveJobStatus']) + ->getMock(); + + $postImportResultMock->method('retrieveJobStatus')->willReturn($postImportInfo); + + $nodeMock = (new Chain($this)) + ->add(NodeInterface::class, 'uuid', 'dataset-1') + ->getMock(); + + $container = $this->buildContainerChain() + ->add(EntityTypeManagerInterface::class, 'getStorage', EntityStorageInterface::class) + ->add(RequestStack::class, 'getCurrentRequest', new Request(['dataset_title' => 'Dataset 1'])) + ->add(EntityStorageInterface::class, 'getQuery', QueryInterface::class) + ->add(EntityStorageInterface::class, 'loadMultiple', [$nodeMock]) + ->add(QueryInterface::class, 'accessCheck', QueryInterface::class) + ->add(QueryInterface::class, 'condition', QueryInterface::class) + ->add(QueryInterface::class, 'execute', [$nodeMock]) + ->add(DatasetInfo::class, 'gather', ['latest_revision' => $info + ['distributions' => [$distribution]]]) + ->add(PostImportResultFactory::class, 'initializeFromDistribution', $postImportResultMock) + ->getMock(); + \Drupal::setContainer($container); + $form = DashboardForm::create($container)->buildForm([], new FormState()); + + $this->assertEquals(1, count($form['table']['#rows'])); + $this->assertEquals('dataset-1', $form['table']['#rows'][0][0]['data']['#uuid']); + $this->assertEquals('Dataset 1', $form['table']['#rows'][0][0]['data']['#title']); + $this->assertEquals('NEW', $form['table']['#rows'][0][2]['data']); + $this->assertEquals('done', $form['table']['#rows'][0][6]['data']['#status']); + $this->assertEquals(NULL, $form['table']['#rows'][0][6]['data']['#error']); + } + /** * Test building the dashboard table with a UUID filter. */ @@ -429,6 +499,8 @@ private function buildContainerChain(): Chain { ->add('dkan.harvest.storage.harvest_run_repository', HarvestRunRepository::class) ->add('database', Connection::class) ->add('dkan.datastore.post_import_result_factory', PostImportResultFactory::class) + ->add('entity.storage.interface', EntityStorageInterface::class) + ->add('entity_type.manager', EntityTypeManagerInterface::class) ->index(0); $runStatus = [ @@ -459,6 +531,7 @@ private function buildContainerChain(): Chain { ->add(PathValidator::class, 'getUrlIfValidWithoutAccessCheck', NULL) ->add(StreamWrapperManager::class, 'getViaUri', PublicStream::class) ->add(PublicStream::class, 'getExternalUrl', 'http://example.com') - ->add(Pager::class, 'getCurrentPage', 0); + ->add(Pager::class, 'getCurrentPage', 0) + ->add(EntityTypeManagerInterface::class, 'getStorage', EntityStorageInterface::class); } }