Commit f8d4b113 authored by Gábor Hojtsy's avatar Gábor Hojtsy
Browse files

Issue #3102724 by dww, tedbow, bnjmnm, webchick, ckrina, Gábor Hojtsy, xjm,...

Issue #3102724 by dww, tedbow, bnjmnm, webchick, ckrina, Gábor Hojtsy, xjm, benjifisher, AaronMcHale, lauriii, shaal, andrewmacpherson, rainbreaw, worldlinemine: Improve usability of core compatibility ranges on available updates report
parent 5d4c083e
......@@ -7,12 +7,19 @@
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Utility class to set core compatibility messages for module updates.
* Utility class to set core compatibility messages for project releases.
*/
class ProjectCoreCompatibility {
use StringTranslationTrait;
/**
* The currently installed version of Drupal core.
*
* @var string
*/
protected $existingCoreVersion;
/**
* Cache of core versions that are available for updates.
*
......@@ -50,30 +57,29 @@ class ProjectCoreCompatibility {
*/
public function __construct(array $core_data, array $core_releases) {
if (isset($core_data['existing_version'])) {
$this->possibleCoreUpdateVersions = $this->getPossibleCoreUpdateVersions($core_data['existing_version'], $core_releases);
$this->existingCoreVersion = $core_data['existing_version'];
$this->possibleCoreUpdateVersions = $this->getPossibleCoreUpdateVersions($core_releases);
}
}
/**
* Gets the core versions that should be considered for compatibility ranges.
*
* @param string $existing_version
* The existing (currently installed) version of Drupal core.
* @param array $core_releases
* The Drupal core available releases.
*
* @return string[]
* The core version numbers that are possible to update the site to.
*/
protected function getPossibleCoreUpdateVersions($existing_version, array $core_releases) {
if (!isset($core_releases[$existing_version])) {
protected function getPossibleCoreUpdateVersions(array $core_releases) {
if (!isset($core_releases[$this->existingCoreVersion])) {
// If we can't determine the existing version of core then we can't
// calculate the core compatibility of a given release based on core
// versions after the existing version.
return [];
}
$core_release_versions = array_keys($core_releases);
$possible_core_update_versions = Semver::satisfiedBy($core_release_versions, '>= ' . $existing_version);
$possible_core_update_versions = Semver::satisfiedBy($core_release_versions, '>= ' . $this->existingCoreVersion);
$possible_core_update_versions = Semver::sort($possible_core_update_versions);
$possible_core_update_versions = array_filter($possible_core_update_versions, function ($version) {
return VersionParser::parseStability($version) === 'stable';
......@@ -131,11 +137,26 @@ public function setReleaseMessage(array &$project_data) {
}
foreach ($releases_to_set as &$release) {
if (!empty($release['core_compatibility'])) {
$release['core_compatible'] = $this->isCoreCompatible($release['core_compatibility']);
$release['core_compatibility_message'] = $this->createMessageFromCoreCompatibility($release['core_compatibility']);
}
}
}
/**
* Determines if a release is compatible with the currently installed core.
*
* @param string $core_compatibility_constraint
* A semantic version constraint.
*
* @return bool
* TRUE if the given constraint is satisfied by the currently installed
* version of Drupal core, otherwise FALSE.
*/
protected function isCoreCompatible($core_compatibility_constraint) {
return Semver::satisfies($this->existingCoreVersion, $core_compatibility_constraint);
}
/**
* Creates core a compatibility message from a semantic version constraint.
*
......@@ -157,7 +178,7 @@ protected function createMessageFromCoreCompatibility($core_compatibility_constr
$range_messages[] = $core_compatibility_range[0];
}
}
$this->compatibilityMessages[$core_compatibility_constraint] = $this->t('This module is compatible with Drupal core:') . ' ' . implode(', ', $range_messages);
$this->compatibilityMessages[$core_compatibility_constraint] = $this->t('Requires Drupal core:') . ' ' . implode(', ', $range_messages);
}
return $this->compatibilityMessages[$core_compatibility_constraint];
}
......
......@@ -6,11 +6,21 @@
* Available variables:
* - attributes: HTML attributes suitable for a container element.
* - title: The title of the project.
* - core_compatibility_details: Render array of core compatibility details.
* - version: A list of data about the latest released version, containing:
* - version: The version number.
* - date: The date of the release.
* - download_link: The URL for the downloadable file.
* - release_link: The URL for the release notes.
* - core_compatible: A flag indicating whether the project is compatible
* with the currently installed version of Drupal core. This flag is not
* set for the Drupal core project itself.
* - core_compatibility_message: A message indicating the versions of Drupal
* core with which this project is compatible. This message is also
* contained within the 'core_compatibility_details' variable documented
* above. This message is not set for the Drupal core project itself.
*
* @see template_preprocess_update_version()
*
* @ingroup themeable
*/
......@@ -21,18 +31,22 @@
<div class="project-update__version-details layout-column layout-column--quarter">
<a href="{{ version.release_link }}">{{ version.version }}</a>
<span class="project-update__version-date">({{ version.date|date('Y-M-d') }})</span>
{% if version.core_compatibility_message %}
<span class="project-update__core-compatibility-message">{{ version.core_compatibility_message }}</span>
{% endif %}
</div>
<div class="layout-column layout-column--half">
<ul class="project-update__version-links">
<li class="project-update__download-link">
<a href="{{ version.download_link }}">{{ 'Download'|t }}</a>
</li>
{% if version.core_compatible is not defined or version.core_compatible %}
<li class="project-update__download-link">
<a href="{{ version.download_link }}">{{ 'Download'|t }}</a>
</li>
{% endif %}
<li class="project-update__release-notes-link">
<a href="{{ version.release_link }}">{{ 'Release notes'|t }}</a>
</li>
{% if core_compatibility_details %}
<li class="project-update__compatibility-details">
{{ core_compatibility_details }}
</li>
{% endif %}
</ul>
</div>
</div>
......
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>AAA Update test</title>
<short_name>aaa_update_test</short_name>
<dc:creator>Drupal</dc:creator>
<supported_branches>8.x-1.,8.x-2.</supported_branches>
<project_status>published</project_status>
<link>http://example.com/project/aaa_update_test</link>
<terms>
<term><name>Projects</name><value>Modules</value></term>
</terms>
<releases>
<release>
<name>aaa_update_test 8.x-2.2</name>
<version>8.x-2.2</version>
<tag>8.x-2.2</tag>
<core_compatibility>^8.1.1</core_compatibility>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-2-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-2.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>Security update</value></term>
</terms>
</release>
<release>
<name>aaa_update_test 8.x-1.2</name>
<version>8.x-1.2</version>
<tag>8.x-2.2</tag>
<core_compatibility>^8.1.0</core_compatibility>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-2-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-2.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>Security update</value></term>
</terms>
</release>
<release>
<name>aaa_update_test 8.x-1.0</name>
<version>8.x-1.0</version>
<tag>8.x-2.2</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-0-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>Insecure</value></term>
</terms>
</release>
</releases>
</project>
......@@ -30,7 +30,6 @@
<name>aaa_update_test 8.x-3.0-beta1</name>
<version>8.x-3.0-beta1</version>
<tag>8.x-3.0-beta1</tag>
<core_compatibility>^8.1.1</core_compatibility>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-3-0-beta1</release_link>
<download_link>http://example.com/aaa_update_test--8-x-3-0-beta1.tar.gz</download_link>
......@@ -48,7 +47,6 @@
<name>aaa_update_test 8.x-2.2</name>
<version>8.x-2.2</version>
<tag>DRUPAL-8--2-2</tag>
<core_compatibility>^8.1.1</core_compatibility>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-2-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-2.tar.gz</download_link>
......@@ -92,7 +90,6 @@
<name>aaa_update_test 8.x-1.2</name>
<version>8.x-1.2</version>
<tag>DRUPAL-8--1-2</tag>
<core_compatibility>^8.1.0</core_compatibility>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-2-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-2.tar.gz</download_link>
......
......@@ -575,9 +575,9 @@ public function testCoreCompatibilityMessage() {
// Confirm that messages are displayed for security and 'Also available'
// updates.
$this->refreshUpdateStatus(['drupal' => '1.1', 'aaa_update_test' => 'sec.8.x-1.2_8.x-2.2']);
$this->assertCoreCompatibilityMessage('8.x-1.2', '8.1.0 to 8.1.1', 'Security update:');
$this->assertCoreCompatibilityMessage('8.x-2.2', '8.1.1', 'Also available:');
$this->refreshUpdateStatus(['drupal' => '1.1', 'aaa_update_test' => 'core_compatibility.8.x-1.2_8.x-2.2']);
$this->assertCoreCompatibilityMessage('8.x-1.2', '8.1.0 to 8.1.1', 'Security update:', FALSE);
$this->assertCoreCompatibilityMessage('8.x-2.2', '8.1.1', 'Also available:', FALSE);
}
/**
......@@ -788,17 +788,39 @@ public function testUnsupportedRelease() {
*
* @param string $version
* The version of the update.
* @param string $expected_compatibility_range
* @param string $expected_range
* The expected core compatibility range.
* @param string $expected_release_title
* The expected release title.
* @param bool $is_compatible
* If the update is compatible with the installed version of Drupal.
*/
protected function assertCoreCompatibilityMessage($version, $expected_compatibility_range, $expected_release_title) {
$link = $this->getSession()->getPage()->findLink($version);
$update_info_element = $link->getParent();
$this->assertContains("This module is compatible with Drupal core: $expected_compatibility_range", $update_info_element->getText());
$update_title_element = $update_info_element->getParent()->find('css', '.project-update__version-title');
$this->assertSame($expected_release_title, $update_title_element->getText());
protected function assertCoreCompatibilityMessage($version, $expected_range, $expected_release_title, $is_compatible = TRUE) {
$update_element = $this->findUpdateElementByLabel($expected_release_title);
$this->assertTrue($update_element->hasLink($version));
$compatibility_details = $update_element->find('css', '.project-update__compatibility-details details');
$this->assertContains("Requires Drupal core: $expected_range", $compatibility_details->getText());
$details_summary_element = $compatibility_details->find('css', 'summary');
if ($is_compatible) {
$download_version = str_replace('.', '-', $version);
// If an update is compatible with the installed version of Drupal core,
// it should have a download link and the details element should be closed
// by default.
$this->assertFalse($compatibility_details->hasAttribute('open'));
$this->assertSame('Compatible', $details_summary_element->getText());
$this->assertEquals(
$update_element->findLink('Download')->getAttribute('href'),
"http://example.com/{$this->updateProject}-$download_version.tar.gz"
);
}
else {
// If an update is not compatible with the installed version of Drupal
// core, it should not have a download link and the details element should
// be open by default.
$this->assertTrue($compatibility_details->hasAttribute('open'));
$this->assertSame('Not compatible', $details_summary_element->getText());
$this->assertFalse($update_element->hasLink('Download'));
}
}
}
......@@ -195,7 +195,7 @@ protected function assertSecurityUpdates($project_path_part, array $expected_sec
*/
protected function assertVersionUpdateLinks($label, $version, $download_version = NULL) {
$download_version = $download_version ?? $version;
$update_element = $this->getSession()->getPage()->find('css', $this->updateTableLocator . " .project-update__version:contains(\"$label\")");
$update_element = $this->findUpdateElementByLabel($label);
// In the release notes URL the periods are replaced with dashes.
$url_version = str_replace('.', '-', $version);
......@@ -274,4 +274,21 @@ protected function assertUpdateTableElementContains($text) {
->elementContains('css', $this->updateTableLocator, $text);
}
/**
* Finds an update page element by label.
*
* @param string $label
* The label for the update, for example "Recommended version:" or
* "Latest version:".
*
* @return \Behat\Mink\Element\NodeElement
* The update element.
*/
protected function findUpdateElementByLabel($label) {
$update_elements = $this->getSession()->getPage()
->findAll('css', $this->updateTableLocator . " .project-update__version:contains(\"$label\")");
$this->assertCount(1, $update_elements);
return $update_elements[0];
}
}
......@@ -72,22 +72,26 @@ public function providerSetProjectCoreCompatibilityRanges() {
'expected_releases' => [
'1.0.1' => [
'core_compatibility' => '8.x',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.8.0 to 8.9.2',
'core_compatible' => TRUE,
'core_compatibility_message' => 'Requires Drupal core: 8.8.0 to 8.9.2',
],
'1.2.3' => [
'core_compatibility' => '^8.9 || ^9',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0 to 8.9.2',
'core_compatible' => FALSE,
'core_compatibility_message' => 'Requires Drupal core: 8.9.0 to 8.9.2',
],
'1.2.4' => [
'core_compatibility' => '^8.9.2 || ^9',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.2',
'core_compatible' => FALSE,
'core_compatibility_message' => 'Requires Drupal core: 8.9.2',
],
'1.2.6' => [],
],
'expected_security_updates' => [
'1.2.5' => [
'core_compatibility' => '8.9.0 || 8.9.2 || ^9.0.1',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0, 8.9.2',
'core_compatible' => FALSE,
'core_compatibility_message' => 'Requires Drupal core: 8.9.0, 8.9.2',
],
],
];
......@@ -110,22 +114,26 @@ public function providerSetProjectCoreCompatibilityRanges() {
$test_cases['with 9 full releases']['expected_releases'] = [
'1.0.1' => [
'core_compatibility' => '8.x',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.8.0 to 8.9.2',
'core_compatible' => TRUE,
'core_compatibility_message' => 'Requires Drupal core: 8.8.0 to 8.9.2',
],
'1.2.3' => [
'core_compatibility' => '^8.9 || ^9',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0 to 9.0.2',
'core_compatible' => FALSE,
'core_compatibility_message' => 'Requires Drupal core: 8.9.0 to 9.0.2',
],
'1.2.4' => [
'core_compatibility' => '^8.9.2 || ^9',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.2 to 9.0.2',
'core_compatible' => FALSE,
'core_compatibility_message' => 'Requires Drupal core: 8.9.2 to 9.0.2',
],
'1.2.6' => [],
];
$test_cases['with 9 full releases']['expected_security_updates'] = [
'1.2.5' => [
'core_compatibility' => '8.9.0 || 8.9.2 || ^9.0.1',
'core_compatibility_message' => 'This module is compatible with Drupal core: 8.9.0, 8.9.2, 9.0.1 to 9.0.2',
'core_compatible' => FALSE,
'core_compatibility_message' => 'Requires Drupal core: 8.9.0, 8.9.2, 9.0.1 to 9.0.2',
],
];
return $test_cases;
......
......@@ -101,6 +101,36 @@ function template_preprocess_update_report(&$variables) {
}
}
/**
* Prepares variables for update version templates.
*
* Default template: update-version.html.twig.
*
* @param array $variables
* An associative array containing:
* - version: An array of information about the release version.
*/
function template_preprocess_update_version(array &$variables) {
$version = $variables['version'];
if (empty($version['core_compatibility_message'])) {
return;
}
$core_compatible = !empty($version['core_compatible']);
$variables['core_compatibility_details'] = [
'#type' => 'details',
'#title' => $core_compatible ? t('Compatible') : t('Not compatible'),
'#open' => !$core_compatible,
'message' => [
'#markup' => $version['core_compatibility_message'],
],
'#attributes' => [
'class' => [
$core_compatible ? 'compatible' : 'not-compatible',
],
],
];
}
/**
* Prepares variables for update project status templates.
*
......
......@@ -6,11 +6,21 @@
* Available variables:
* - attributes: HTML attributes suitable for a container element.
* - title: The title of the project.
* - core_compatibility_details: Render array of core compatibility details.
* - version: A list of data about the latest released version, containing:
* - version: The version number.
* - date: The date of the release.
* - download_link: The URL for the downloadable file.
* - release_link: The URL for the release notes.
* - core_compatible: A flag indicating whether the project is compatible
* with the currently installed version of Drupal core. This flag is not
* set for the Drupal core project itself.
* - core_compatibility_message: A message indicating the versions of Drupal
* core with which this project is compatible. This message is also
* contained within the 'core_compatibility_details' variable documented
* above. This message is not set for the Drupal core project itself.
*
* @see template_preprocess_update_version()
*/
#}
<div class="{{ attributes.class }} project-update__version"{{ attributes|without('class') }}>
......@@ -19,18 +29,22 @@
<div class="project-update__version-details layout-column layout-column--quarter">
<a href="{{ version.release_link }}">{{ version.version }}</a>
<span class="project-update__version-date">({{ version.date|date('Y-M-d') }})</span>
{% if version.core_compatibility_message %}
<span class="project-update__core-compatibility-message">{{ version.core_compatibility_message }}</span>
{% endif %}
</div>
<div class="layout-column layout-column--half">
<ul class="project-update__version-links">
<li class="project-update__download-link">
<a href="{{ version.download_link }}">{{ 'Download'|t }}</a>
</li>
{% if version.core_compatible is not defined or version.core_compatible %}
<li class="project-update__download-link">
<a href="{{ version.download_link }}">{{ 'Download'|t }}</a>
</li>
{% endif %}
<li class="project-update__release-notes-link">
<a href="{{ version.release_link }}">{{ 'Release notes'|t }}</a>
</li>
{% if core_compatibility_details %}
<li class="project-update__compatibility-details">
{{ core_compatibility_details }}
</li>
{% endif %}
</ul>
</div>
</div>
......
/**
* @file update-report.css
*
* Styling for the Available updates report at admin/reports/updates
*/
.project-update__compatibility-details details {
height: 20px;
margin: 2px 0;
border: 0;
background-color: inherit;
}
.project-update__compatibility-details details[open] {
overflow: visible;
height: auto;
white-space: normal;
}
.project-update__compatibility-details summary {
border: 0;
margin: 0;
padding: 0;
text-transform: none;
font-weight: normal;
}
.project-update__compatibility-details .details-wrapper {
margin: 0;
padding: 5px 0;
}
......@@ -83,6 +83,12 @@ maintenance-page:
- system/maintenance
- seven/global-styling
update-report:
version: VERSION
css:
theme:
css/theme/update-report.css: {}
install-page:
version: VERSION
js:
......
......@@ -425,3 +425,16 @@ function seven_preprocess_image_widget(array &$variables) {
unset($data['preview']);
}
}
/**
* Prepares variables for update version templates.
*
* Default template: update-version.html.twig.
*
* @param array $variables
* An associative array containing:
* - version: An array of information about the release version.
*/
function seven_preprocess_update_version(array &$variables) {
$variables['#attached']['library'][] = 'seven/update-report';
}
......@@ -6,11 +6,21 @@
* Available variables:
* - attributes: HTML attributes suitable for a container element.
* - title: The title of the project.
* - core_compatibility_details: Render array of core compatibility details.
* - version: A list of data about the latest released version, containing:
* - version: The version number.
* - date: The date of the release.
* - download_link: The URL for the downloadable file.
* - release_link: The URL for the release notes.
* - core_compatible: A flag indicating whether the project is compatible
* with the currently installed version of Drupal core. This flag is not
* set for the Drupal core project itself.
* - core_compatibility_message: A message indicating the versions of Drupal
* core with which this project is compatible. This message is also
* contained within the 'core_compatibility_details' variable documented
* above. This message is not set for the Drupal core project itself.
*
* @see template_preprocess_update_version()
*/
#}
<div class="{{ attributes.class }} project-update__version"{{ attributes|without('class') }}>
......@@ -19,18 +29,22 @@
<div class="project-update__version-details layout-column layout-column--quarter">
<a href="{{ version.release_link }}">{{ version.version }}</a>
<span class="project-update__version-date">({{ version.date|date('Y-M-d') }})</span>
{% if version.core_compatibility_message %}
<span class="project-update__core-compatibility-message">{{ version.core_compatibility_message }}</span>
{% endif %}
</div>
<div class="layout-column layout-column--half">
<ul class="project-update__version-links">
<li class="project-update__download-link">
<a href="{{ version.download_link }}">{{ 'Download'|t }}</a>
</li>
{% if version.core_compatible is not defined or version.core_compatible %}
<li class="project-update__download-link">
<a href="{{ version.download_link }}">{{ 'Download'|t }}</a>
</li>
{% endif %}
<li class="project-update__release-notes-link">
<a href="{{ version.release_link }}">{{ 'Release notes'|t }}</a>
</li>
{% if core_compatibility_details %}
<li class="project-update__compatibility-details">
{{ core_compatibility_details }}
</li>
{% endif %}
</ul>
</div>
</div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment