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

Filter by title on the dataset import status dashboard #4376

Merged
merged 5 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 88 additions & 14 deletions modules/datastore/src/Form/DashboardForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,6 +73,13 @@ class DashboardForm extends FormBase {
*/
protected PostImportResultFactory $postImportResultFactory;

/**
* Node storage service.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected EntityStorageInterface $nodeStorage;

/**
* DashboardController constructor.
*
Expand All @@ -86,20 +95,24 @@ 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,
DatasetInfo $datasetInfo,
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;
}
Expand All @@ -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'),
);
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 = [];
Expand All @@ -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.
Expand All @@ -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.
*
Expand Down
75 changes: 74 additions & 1 deletion modules/datastore/tests/src/Unit/Form/DashboardFormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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);
}
}