Skip to content
This repository has been archived by the owner on Jul 27, 2022. It is now read-only.

ISAICP-6139: Show parent of distributions in download report. #2436

Merged
merged 14 commits into from
Apr 13, 2021
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
130 changes: 130 additions & 0 deletions config/sync/views.view.asset_distribution_downloads.yml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,136 @@ display:
hide_alter_empty: true
link_to_entity: true
plugin_id: entity_label
parent_entity_type:
id: parent_entity_type
table: joinup_download_event
field: parent_entity_type
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: true
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: download_event
entity_field: parent_entity_type
plugin_id: field
parent_entity_id:
id: parent_entity_id
table: joinup_download_event
field: parent_entity_id
relationship: none
group_type: group
admin_label: ''
label: 'Parent'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: download_event
entity_field: parent_entity_id
plugin_id: field
created:
id: created
table: joinup_download_event
Expand Down
27 changes: 16 additions & 11 deletions tests/features/asset_distribution/track_download.feature
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,23 @@ Feature: Asset distribution editing.
When I go to the "Winter of 95" release
Then I should see the link "External" in the "i386" tile

Scenario: Tests the CSV download.
Scenario: Download a CSV export of the distributions download report.
Given users:
| Username | E-mail |
| user1 | [email protected] |
| user2 | [email protected] |
And the following solution:
| title | Solution |
| state | validated |
And the following release:
| title | Release 1 |
| is version of | Solution |
| state | validated |
And the following distributions:
| title | parent | access url |
| Distribution 1 | Solution | text.pdf |
| Distribution 2 | Solution | test.zip |
| Distribution 3 | Solution | test1.zip |
| title | parent | access url |
| Distribution 1 | Release 1 | text.pdf |
| Distribution 2 | Solution | test.zip |
| Distribution 3 | Solution | test1.zip |
And the following distribution download events:
| distribution | user |
| Distribution 1 | [email protected] |
Expand All @@ -139,9 +143,10 @@ Feature: Asset distribution editing.
Then I should see the success message "Export complete. Download the file here if file is not automatically downloaded."
And I should see the link "here"
And the file downloaded from the "here" link contains the following strings:
| ID,User,Email,"File name",Distribution,Created |
| ,"Anonymous (not verified)",[email protected],text.pdf,"Distribution 1", |
| ,user1,[email protected],text.pdf,"Distribution 1", |
| ,user2,[email protected],test.zip,"Distribution 2", |
| ,"Anonymous (not verified)",[email protected],test1.zip,"Distribution 3", |
| ,user1,[email protected],test1.zip,"Distribution 3", |
| ID,User,Email,"File name",Distribution,Parent,Created |
# The %title% variable will translate the title to the entity ID since that is what is exported in the CSV file.
| ,"Anonymous (not verified)",[email protected],text.pdf,"Distribution 1",%Release 1%, |
| ,user1,[email protected],text.pdf,"Distribution 1",%Release 1%, |
| ,user2,[email protected],test.zip,"Distribution 2",%Solution%, |
| ,"Anonymous (not verified)",[email protected],test1.zip,"Distribution 3",%Solution%, |
| ,user1,[email protected],test1.zip,"Distribution 3",%Solution%, |
17 changes: 13 additions & 4 deletions tests/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -2006,11 +2006,20 @@ public function assertDownloadedFileContainsStrings(string $link_label, TableNod
throw new \Exception("The downloaded file has no content.");
}

$not_found = array_filter($strings_table->getColumn(0), function (string $text) use ($content): bool {
return strpos($content, $text) === FALSE;
});
$not_found = [];
foreach ($strings_table->getColumn(0) as $text) {
$matches = [];
if (preg_match('/^.*%(.*?)%.*$/', $text, $matches)) {
$entity = $this->getEntityByLabel('rdf_entity', $matches[1]);
$text = str_replace("%{$matches[1]}%", $entity->id(), $text);
}

if ($not_found) {
if (strpos($content, $text) === FALSE) {
$not_found[] = $text;
}
}

if (!empty($not_found)) {
throw new ExpectationFailedException("Following strings were not found in the downloaded file:\n- " . implode("\n- ", $not_found));
}
}
Expand Down
6 changes: 5 additions & 1 deletion tests/src/Context/AssetDistributionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,19 @@ public function clickLinkInCompactDistribution(string $link, string $heading): v
*/
public function downloadEvents(TableNode $table): void {
foreach ($table->getColumnsHash() as $row) {
/** @var \Drupal\asset_distribution\Entity\AssetDistributionInterface $distribution */
$distribution = $this->getRdfEntityByLabel($row['distribution'], 'asset_distribution');
/** @var \Drupal\file\FileInterface $file */
$file = FileUrlHandler::urlToFile($distribution->get('field_ad_access_url')->target_id);
$account = \Drupal::service('email.validator')->isValid($row['user']) ? new AnonymousUserSession() : user_load_by_name($row['user']);
$mail = $account->isAnonymous() ? $row['user'] : $account->getEmail();

$parent = $distribution->getParent();
$entity = DownloadEvent::create([
'uid' => $account->id(),
'mail' => $mail,
'file' => $file->id(),
'parent_entity_type' => $parent->getEntityTypeId(),
'parent_entity_id' => $parent->id(),
]);
$entity->save();
$this->entities['download_event'][$entity->id()] = $entity;
Expand Down
35 changes: 35 additions & 0 deletions web/modules/custom/asset_distribution/asset_distribution.module
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,38 @@ function asset_distribution_rdf_entity_delete(RdfInterface $distribution) {
}

}

/**
* Implements hook_preprocess_HOOK().
*/
function asset_distribution_preprocess_views_view_field(&$variables) {
$view = $variables['view'];
$field = $variables['field'];
if ($view->storage->id() !== 'asset_distribution_downloads' || $view->current_display !== 'page' || $field->field !== 'parent_entity_id') {
return;
}

/** @var \Drupal\asset_distribution\Entity\DownloadEvent $download_event */
$download_event = $variables['row']->_entity;
if ($download_event->get('parent_entity_type')->isEmpty() || $download_event->get('parent_entity_id')->isEmpty()) {
return;
}

// Notice: This code works for any entity type, though it is only meant for
// distributions so it might be too much.
$entity_type = $download_event->get('parent_entity_type')->value;
$entity_id = $download_event->get('parent_entity_id')->value;

try {
$storage = $storages[$entity_type] ?? \Drupal::entityTypeManager()->getStorage($entity_type);
}
catch (Exception $exception) {
return;
}

$storages[$entity_type] = $storage;
$entity = $storage->load($entity_id);
if (!empty($entity)) {
$variables['output'] = $entity->toLink($entity->label());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
use Drupal\Core\Session\AccountInterface;
use Drupal\asset_distribution\Form\AnonymousDownloadForm;
use Drupal\file\FileInterface;
use Drupal\file\FileUsage\FileUsageInterface;
use Drupal\file_url\FileUrlHandler;
use Drupal\sparql_entity_storage\SparqlEntityStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;

Expand Down Expand Up @@ -49,10 +51,17 @@ class DownloadTrackingController extends ControllerBase {
*/
protected $sparqlStorage;

/**
* The file usage service.
*
* @var \Drupal\file\FileUsage\FileUsageInterface
*/
protected $fileUsage;

/**
* Instantiates a new DownloadTrackingController object.
*
* @param \Drupal\Core\Entity\ContentEntityStorageInterface $sparql_storage
* @param \Drupal\sparql_entity_storage\SparqlEntityStorageInterface $sparql_storage
* The RDF entity storage.
* @param \Drupal\Core\Entity\ContentEntityStorageInterface $event_storage
* The download event entity storage.
Expand All @@ -62,13 +71,16 @@ class DownloadTrackingController extends ControllerBase {
* The form builder.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current logged in user.
* @param \Drupal\file\FileUsage\FileUsageInterface $file_usage
* The file usage service.
*/
public function __construct(ContentEntityStorageInterface $sparql_storage, ContentEntityStorageInterface $event_storage, FileUrlHandler $file_url_handler, FormBuilderInterface $form_builder, AccountInterface $current_user) {
public function __construct(SparqlEntityStorageInterface $sparql_storage, ContentEntityStorageInterface $event_storage, FileUrlHandler $file_url_handler, FormBuilderInterface $form_builder, AccountInterface $current_user, FileUsageInterface $file_usage) {
$this->sparqlStorage = $sparql_storage;
$this->eventStorage = $event_storage;
$this->fileUrlHandler = $file_url_handler;
$this->formBuilder = $form_builder;
$this->currentUser = $current_user;
$this->fileUsage = $file_usage;
}

/**
Expand All @@ -80,7 +92,8 @@ public static function create(ContainerInterface $container) {
$container->get('entity_type.manager')->getStorage('download_event'),
$container->get('file_url.handler'),
$container->get('form_builder'),
$container->get('current_user')
$container->get('current_user'),
$container->get('file.usage')
);
}

Expand Down Expand Up @@ -124,9 +137,26 @@ protected function trackAnonymousDownload(FileInterface $file) {
* The generated response.
*/
protected function trackAuthenticatedDownload(FileInterface $file) {
$usages = $this->fileUsage->listUsage($file);

// Normally, only one distribution is allowed to use a file and only
// distributions call this code.
if (empty($usages['file']['rdf_entity'])) {
throw new \RuntimeException('No distributions were found using the file with ID ' . $file->id());
}
if (count($usages['file']['rdf_entity']) > 1) {
throw new \RuntimeException('More than one distributions were found for the file with ID ' . $file->id());
}
$distribution = $this->sparqlStorage->load(key($usages['file']['rdf_entity']));

/** @var \Drupal\solution\Entity\SolutionInterface|\Drupal\asset_release\Entity\AssetReleaseInterface $parent */
$parent = $distribution->getParent();

$event = $this->eventStorage->create([
'uid' => $this->currentUser->id(),
'file' => $file->id(),
'parent_entity_type' => $parent->getEntityTypeId(),
'parent_entity_id' => $parent->id(),
]);
$event->save();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ class AssetDistribution extends Rdf implements AssetDistributionInterface {
/**
* {@inheritdoc}
*/
public function getParent() {
public function getParent(): DistributionsParentInterface {
/** @var \Drupal\asset_distribution\DistributionParentFieldItemList $field */
$field = $this->get('parent');

/** @var \Drupal\asset_release\Entity\AssetReleaseInterface|\Drupal\solution\Entity\SolutionInterface $parent */
if ($field->isEmpty() || !($parent = $field->entity)) {
if ($field->isEmpty() || !($parent = $field->entity) || !$parent instanceof DistributionsParentInterface) {
// During normal operation every distribution should have a parent entity,
// so the only way a parent can be missing is because of an unexpected
// condition occurring at runtime, for example if a data store goes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface AssetDistributionInterface extends RdfInterface, CollectionContentInte
/**
* Return the distribution's parent, either a release or a solution.
*
* @return \Drupal\asset_release\Entity\AssetReleaseInterface|\Drupal\solution\Entity\SolutionInterface
* @return \Drupal\asset_distribution\Entity\DistributionsParentInterface
* The parent entity, either a release or a solution.
*
* @throws \Drupal\asset_distribution\Exception\MissingDistributionParentException
Expand All @@ -26,7 +26,7 @@ interface AssetDistributionInterface extends RdfInterface, CollectionContentInte
* need to catch this exception. This will only be thrown in case something
* is seriously wrong, e.g. if the database is down.
*/
public function getParent();
public function getParent(): DistributionsParentInterface;

/**
* Checks whether the distribution parent is a solution rather than a release.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

namespace Drupal\asset_distribution\Entity;

use Drupal\Core\Entity\ContentEntityInterface;

/**
* Bundle class for content having distributions as children.
*/
interface DistributionsParentInterface {
interface DistributionsParentInterface extends ContentEntityInterface {

/**
* Returns the child distribution IDs.
Expand Down
Loading