Skip to content

Commit

Permalink
Merge pull request #296 from openeuropa/OEL-2632
Browse files Browse the repository at this point in the history
OEL-2632: Show project status badge in teaser view mode.
  • Loading branch information
brummbar authored Aug 2, 2024
2 parents 63b501e + 849a602 commit 2ce6006
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function oe_whitelabel_extra_project_preprocess_node__oe_project__oe_w_content_b
*/
function oe_whitelabel_extra_project_preprocess_node__oe_project__teaser(array &$variables): void {
_oe_whitelabel_extra_project_preprocess_featured_media($variables);
_oe_whitelabel_extra_project_preprocess_status($variables);
}

/**
Expand Down Expand Up @@ -168,7 +169,7 @@ function _oe_whitelabel_extra_project_preprocess_inpage_nav(array &$variables):
* @param array $variables
* Variables from hook_preprocess_node().
*/
function _oe_whitelabel_extra_project_preprocess_status_and_progress(array &$variables): void {
function _oe_whitelabel_extra_project_preprocess_status(array &$variables): void {
/** @var \Drupal\node\NodeInterface $node */
$node = $variables['node'];
/** @var \Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem|null $date_range_item */
Expand Down Expand Up @@ -203,6 +204,34 @@ function _oe_whitelabel_extra_project_preprocess_status_and_progress(array &$var
return;
}

$status_labels = [t('Planned'), t('Ongoing'), t('Closed')];

$variables['project_status_badge_args'] = [
'label' => '…',
'outline' => FALSE,
'attributes' => new Attribute([
'data-start-timestamp' => $t_start,
'data-end-timestamp' => $t_end,
'data-status-labels' => implode('|', $status_labels),
'class' => [
// Hide for non-js users, to avoid showing wrong/outdated information.
'd-none',
// Add a class to be able to target it with the JS library.
'oe-wt-project__status',
],
]),
];
}

/**
* Adds variables for the project status and progress component.
*
* @param array $variables
* Variables from hook_preprocess_node().
*/
function _oe_whitelabel_extra_project_preprocess_status_and_progress(array &$variables): void {
_oe_whitelabel_extra_project_preprocess_status($variables);

// Use the formatted field values for start / end date.
$element = $variables['elements']['oe_project_dates'][0] ?? [];
if ($element['#theme'] ?? NULL === 'time') {
Expand All @@ -220,11 +249,7 @@ function _oe_whitelabel_extra_project_preprocess_status_and_progress(array &$var
return;
}

$status_labels = [t('Planned'), t('Ongoing'), t('Closed')];

// Values for the 'bcl-project-status' component.
// Some values contain placeholders that will be updated with javascript.
// This makes sure that tests will fail if js does not run.
$variables['project_status_args'] = [
// Placeholder value.
'status' => 'planned',
Expand All @@ -233,18 +258,10 @@ function _oe_whitelabel_extra_project_preprocess_status_and_progress(array &$var
'end_date' => $end_date_element,
'end_label' => t('End'),
'label' => t('Status'),
// Placeholder value.
'badge' => '&ellipsis;',
'badge' => '…',
// Placeholder value, identical to 'planned'.
'progress' => 0,
'attributes' => new Attribute([
'data-start-timestamp' => $t_start,
'data-end-timestamp' => $t_end,
'data-status-labels' => implode('|', $status_labels),
// Hide for non-js users, to avoid showing wrong/outdated information.
'class' => ['d-none'],
]),
];
] + $variables['project_status_badge_args'];
}

/**
Expand Down
100 changes: 63 additions & 37 deletions resources/js/oe_whitelabel.project_status.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
* @file
* Attaches behaviors for the project status element.
*/
(function (bootstrap, Drupal, once) {
(function (Drupal, once) {

/**
* List of background classes.
*
* @type {string[]}
*/
const colorClasses = [
'bg-secondary',
'bg-info',
Expand All @@ -20,43 +25,64 @@
*/
Drupal.behaviors.projectStatus = {
attach: function (context) {
const bclProjectStatus = once('bcl-project-status', '.bcl-project-status', context);

bclProjectStatus.forEach(function (element) {
var msBegin = element.dataset.startTimestamp * 1000;
var msEnd = element.dataset.endTimestamp * 1000;
var statusLabels = element.dataset.statusLabels.split('|');
var msNow = Date.now();
// Calculate a status id: planned = 0, ongoing = 1, closed = 2.
var status = (msNow >= msBegin) + (msNow > msEnd);
// Calculate a progress: planned = 0, ongoing = 0..1, closed = 1.
var progress01 = Math.max(0, Math.min(1, (msNow - msBegin) / (msEnd - msBegin)));
// Convert to percent: planned = 0%, ongoing = 0%..100%, closed = 100%.
// Round to 1%, to avoid overwhelming float digits in aria attributes.
var percent = Math.round(progress01 * 100);

// Process the status label.
var badges = element.getElementsByClassName('badge');
Array.from(badges).forEach(function(badge) {
badge.classList.remove(...colorClasses);
badge.classList.add(colorClasses[status]);
badge.innerHTML = statusLabels[status];
});

// Process the progress bar.
var progressBars = element.getElementsByClassName('progress-bar');
Array.from(progressBars).forEach(function(progressBar) {
progressBar.classList.remove(...colorClasses);
progressBar.classList.add(colorClasses[status]);
progressBar.style.width = percent + '%';
progressBar.setAttribute('aria-valuenow', percent);
progressBar.setAttribute('aria-label', percent);
});

// Reveal the entire section.
element.classList.remove('d-none');
const statusComponents = once('oe-wt-project-status', '.bcl-project-status', context);
const statusBadges = once('oe-wt-project-status', '.badge.oe-wt-project__status', context);

statusComponents.forEach(function (wrapper) {
const badges = wrapper.getElementsByClassName('badge');
const progressBars = wrapper.getElementsByClassName('progress-bar');

calculateProjectStatusAndProgress(wrapper, badges, progressBars);
});

statusBadges.forEach(function (wrapper) {
// Status badges don't have the progress element.
calculateProjectStatusAndProgress(wrapper, [wrapper], []);
});
}
};

})(bootstrap, Drupal, once);
/**
* Calculates the values for the project status and progress elements.
*
* @param {Element} wrapper
* The element that holds the project data.
* @param {array.<Element>} badges
* A list of badges to process.
* @param {array.<Element>} progressBars
* A list of progress bars to process.
*/
function calculateProjectStatusAndProgress(wrapper, badges, progressBars) {
const msBegin = wrapper.dataset.startTimestamp * 1000;
const msEnd = wrapper.dataset.endTimestamp * 1000;
const statusLabels = wrapper.dataset.statusLabels.split('|');
const msNow = Date.now();
// Calculate a status id: planned = 0, ongoing = 1, closed = 2.
const status = (msNow >= msBegin) + (msNow > msEnd);
// Calculate a progress: planned = 0, ongoing = 0..1, closed = 1.
const progress01 = Math.max(0, Math.min(1, (msNow - msBegin) / (msEnd - msBegin)));
// Convert to percent: planned = 0%, ongoing = 0%..100%, closed = 100%.
// Round to 1%, to avoid overwhelming float digits in aria attributes.
const percent = Math.round(progress01 * 100);

// Process the status label.
Array.from(badges).forEach(function (badge) {
badge.classList.remove(...colorClasses);
badge.classList.add(colorClasses[status]);
badge.innerHTML = statusLabels[status];
});

// Process the progress bar.
Array.from(progressBars).forEach(function (progressBar) {
progressBar.classList.remove(...colorClasses);
progressBar.classList.add(colorClasses[status]);
progressBar.style.width = percent + '%';
progressBar.setAttribute('aria-valuenow', percent);
progressBar.setAttribute('aria-label', percent);
});

// Reveal the wrapper element.
wrapper.classList.remove('d-none');
}

})(Drupal, once);
4 changes: 4 additions & 0 deletions templates/content/node--oe-project--teaser.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
{% endset %}
{% set _badges = [] %}
{% set _meta = [] %}
{% if project_status_badge_args is not empty %}
{{ attach_library('oe_whitelabel/project_status') }}
{% set _badges = _badges|merge([project_status_badge_args]) %}
{% endif %}
{% for _item in content.oe_subject|field_value %}
{% set _badges = _badges|merge([{
label: _item,
Expand Down
74 changes: 68 additions & 6 deletions tests/src/FunctionalJavascript/ContentProjectRenderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Drupal\Tests\oe_whitelabel\FunctionalJavascript;

use Behat\Mink\Element\NodeElement;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Url;
Expand All @@ -15,7 +16,7 @@
use Drupal\Tests\oe_bootstrap_theme\PatternAssertion\DescriptionListAssert;
use Drupal\Tests\oe_bootstrap_theme\PatternAssertion\GalleryPatternAssert;
use Drupal\Tests\oe_bootstrap_theme\PatternAssertion\InPageNavigationAssert;
use Drupal\Tests\oe_whitelabel\Traits\MediaCreationTrait;
use Drupal\Tests\oe_whitelabel\Traits\NodeCreationTrait;
use Drupal\Tests\sparql_entity_storage\Traits\SparqlConnectionTrait;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\user\Entity\Role;
Expand All @@ -26,7 +27,7 @@
*/
class ContentProjectRenderTest extends WebDriverTestBase {

use MediaCreationTrait;
use NodeCreationTrait;
use SparqlConnectionTrait;
use TestFileCreationTrait;

Expand Down Expand Up @@ -377,6 +378,48 @@ public function testProjectRendering(): void {
], $gallery_container->getOuterHtml());
}

/**
* Tests the status badge rendering for the teaser view mode.
*/
public function testTeaserStatusBadge(): void {
// Create a project with dates set in the past.
$this->createProjectNode([
'oe_project_dates' => [
'value' => '2000-05-10',
'end_value' => '2010-05-15',
],
]);

// In order to render a teaser, we use the default node list view.
$this->drupalGet('/node');

$teasers = $this->getSession()->getPage()->findAll('css', 'article.listing-item');
$this->assertCount(1, $teasers);
$this->assertTeaserStatusBadge($teasers[0], 'Closed', 'bg-dark');

// Create an ongoing project.
$this->createProjectNode([
'oe_project_dates' => [
'value' => '2010-05-10',
'end_value' => '2100-05-15',
],
]);
// And a planned project.
$this->createProjectNode([
'oe_project_dates' => [
'value' => '2100-05-10',
'end_value' => '2200-05-15',
],
]);

$this->drupalGet('/node');
$teasers = $this->getSession()->getPage()->findAll('css', 'article.listing-item');
$this->assertCount(3, $teasers);
$this->assertTeaserStatusBadge($teasers[0], 'Planned', 'bg-secondary');
$this->assertTeaserStatusBadge($teasers[1], 'Ongoing', 'bg-info');
$this->assertTeaserStatusBadge($teasers[2], 'Closed', 'bg-dark');
}

/**
* Creates a stakeholder organisation entity.
*
Expand Down Expand Up @@ -430,10 +473,12 @@ protected function getStorage(string $entity_type_id): EntityStorageInterface {
* End date string.
*/
protected function setProjectDateRange(NodeInterface $node, string $begin, string $end): void {
$node->oe_project_dates = [
'value' => (new DrupalDateTime($begin, 'Europe/Brussels'))->format('Y-m-d'),
'end_value' => (new DrupalDateTime($end, 'Europe/Brussels'))->format('Y-m-d'),
];
$node->set('oe_project_dates', [
[
'value' => (new DrupalDateTime($begin, 'Europe/Brussels'))->format('Y-m-d'),
'end_value' => (new DrupalDateTime($end, 'Europe/Brussels'))->format('Y-m-d'),
],
]);
}

/**
Expand Down Expand Up @@ -531,4 +576,21 @@ protected function assertProjectDates(string $expected_start_date, string $expec
$this->assertEquals($expected_end_date, trim($end_element->getText()));
}

/**
* Asserts the teaser status badge.
*
* @param \Behat\Mink\Element\NodeElement $wrapper
* The teaser wrapper element.
* @param string $status_label
* The expected status label.
* @param string $status_class
* The expected status class.
*/
protected function assertTeaserStatusBadge(NodeElement $wrapper, string $status_label, string $status_class): void {
$badges = $wrapper->findAll('css', '.badge');
$this->assertCount(2, $badges);
$this->assertEquals($status_label, $badges[0]->getText());
$this->assertTrue($badges[0]->hasClass($status_class));
}

}
6 changes: 5 additions & 1 deletion tests/src/Kernel/ProjectRenderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ public function testProjectTeaser(): void {
'title' => 'Project 1',
'url' => '/node/1',
'description' => 'The teaser text',
'badges' => ['EU financing'],
'badges' => [
// The project status is only calculated when JavaScript is executed.
'&hellip;',
'EU financing',
],
'image' => [
'src' => 'example_1.jpeg',
'alt' => 'Alternative text',
Expand Down

0 comments on commit 2ce6006

Please sign in to comment.