Skip to content
Snippets Groups Projects
Commit d595c319 authored by Ted Bowman's avatar Ted Bowman
Browse files

Issue #3293427 by rahul_, tedbow, kunal.sachdev, immaculatexavier,...

Issue #3293427 by rahul_, tedbow, kunal.sachdev, immaculatexavier, phenaproxima: Display all projects that will be updated in Extensions confirmation form
parent 9e2c8f7e
No related branches found
No related tags found
1 merge request!364Issue #3293427: Display all projects that will be updated in Extensions confirmation form
Showing
with 431 additions and 11 deletions
......@@ -2,6 +2,7 @@
namespace Drupal\automatic_updates_extensions\Form;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates\Validator\StagedDatabaseUpdateValidator;
use Drupal\automatic_updates_extensions\BatchProcessor;
use Drupal\automatic_updates\BatchProcessor as AutoUpdatesBatchProcessor;
......@@ -13,6 +14,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\package_manager\Exception\StageException;
use Drupal\package_manager\Exception\StageOwnershipException;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -159,9 +161,7 @@ final class UpdateReady extends FormBase {
'#type' => 'value',
'#value' => $stage_id,
];
// @todo Display the project versions that will be update including any
// dependencies that are Drupal projects.
$form['package_updates'] = $this->showUpdates();
$form['backup'] = [
'#prefix' => '<strong>',
'#markup' => $this->t('Back up your database and site before you continue. <a href=":backup_url">Learn how</a>.', [':backup_url' => 'https://www.drupal.org/node/22281']),
......@@ -219,4 +219,112 @@ final class UpdateReady extends FormBase {
}
}
/**
* Displays all projects that will be updated.
*
* @return mixed[][]
* A render array displaying packages that will be updated.
*/
private function showUpdates(): array {
// Get packages that were updated in the staging area.
$active = $this->updater->getActiveComposer();
$staged = $this->updater->getStageComposer();
$updated_packages = $staged->getPackagesWithDifferentVersionsIn($active);
// Build a list of package names that were updated by user request.
$updated_by_request = [];
foreach ($this->updater->getPackageVersions() as $group) {
$updated_by_request = array_merge($updated_by_request, array_keys($group));
}
$installed_packages = $active->getInstalledPackages();
$updated_by_request_info = [];
$updated_project_info = [];
$supported_package_types = ['drupal-module', 'drupal-theme'];
// Compile an array of relevant information about the packages that will be
// updated.
foreach ($updated_packages as $name => $updated_package) {
// Ignore anything that isn't a module or a theme.
if (!in_array($updated_package->getType(), $supported_package_types, TRUE)) {
continue;
}
$updated_project_info[$name] = [
'title' => $this->getProjectTitle($updated_package->getName()),
'installed_version' => $installed_packages[$name]->getPrettyVersion(),
'updated_version' => $updated_package->getPrettyVersion(),
];
}
foreach (array_keys($updated_packages) as $name) {
// Sort the updated packages into two groups: the ones that were updated
// at the request of the user, and the ones that got updated anyway
// (probably due to Composer's dependency resolution).
if (in_array($name, $updated_by_request, TRUE)) {
$updated_by_request_info[$name] = $updated_project_info[$name];
unset($updated_project_info[$name]);
}
}
$output = [];
if ($updated_by_request_info) {
// Create the list of messages for the packages updated by request.
$output['requested'] = $this->getUpdatedPackagesItemList($updated_by_request_info, $this->t('The following projects will be updated:'));
}
if ($updated_project_info) {
// Create the list of messages for packages that were updated
// incidentally.
$output['dependencies'] = $this->getUpdatedPackagesItemList($updated_project_info, $this->t('The following dependencies will also be updated:'));
}
return $output;
}
/**
* Gets the human-readable project title for a Composer package.
*
* @param string $package_name
* Package name.
*
* @return string
* The human-readable title of the project.
*/
private function getProjectTitle(string $package_name): string {
$project_name = str_replace('drupal/', '', $package_name);
$project_info = new ProjectInfo($project_name);
$project_data = $project_info->getProjectInfo();
if ($project_data) {
return $project_data['title'];
}
else {
return $project_name;
}
}
/**
* Generates an item list of packages that will be updated.
*
* @param array[] $updated_packages
* An array of packages that will be updated, each sub-array containing the
* project title, installed version, and target version.
* @param \Drupal\Core\StringTranslation\TranslatableMarkup $item_list_title
* The title of the generated item list.
*
* @return array
* A render array for the generated item list.
*/
private function getUpdatedPackagesItemList(array $updated_packages, TranslatableMarkup $item_list_title): array {
$create_message_for_project = function (array $project): TranslatableMarkup {
return $this->t('@title from @from_version to @to_version', [
'@title' => $project['title'],
'@from_version' => $project['installed_version'],
'@to_version' => $project['updated_version'],
]);
};
return [
'#theme' => 'item_list',
'#prefix' => '<p>' . $item_list_title . '</p>',
'#items' => array_map($create_message_for_project, $updated_packages),
];
}
}
{
"extra": {
"_readme": [
"This fixture assumes that ../../two_projects is active directory for the test.",
"Simulates a stage directory in which aaa_update_test is updated."
]
}
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/my_module",
"version": "9.8.0"
}
],
"packages-dev": [
{
"name": "drupal/my_dev_module",
"version": "9.8.1"
}
]
}
{
"packages": [
{
"name": "drupal/core",
"version": "9.8.1",
"type": "drupal-core",
"extra": {
"drupal-scaffold": {
"file-mapping": {}
}
}
},
{
"name": "drupal/semver_test",
"version": "8.1.0",
"type": "drupal-module"
},
{
"name": "drupal/aaa_update_test",
"version": "2.1.0",
"type": "drupal-module"
},{
"name": "drupal/test_theme",
"version": "2.0.0",
"type": "drupal-theme"
}
]
}
{
"extra": {
"_readme": [
"This fixture assumes that ../../two_projects is active directory for the test.",
"Simulates a stage directory in which semver_test is updated."
]
}
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/my_module",
"version": "9.8.0"
}
],
"packages-dev": [
{
"name": "drupal/my_dev_module",
"version": "9.8.1"
}
]
}
{
"packages": [
{
"name": "drupal/core",
"version": "9.8.1",
"type": "drupal-core",
"extra": {
"drupal-scaffold": {
"file-mapping": {}
}
}
},
{
"name": "drupal/semver_test",
"version": "8.1.1",
"type": "drupal-module"
},
{
"name": "drupal/aaa_update_test",
"version": "2.0.0",
"type": "drupal-module"
},
{
"name": "drupal/test_theme",
"version": "2.0.0",
"type": "drupal-theme"
}
]
}
{
"extra": {
"_readme": [
"This fixture assumes that ../../two_projects is active directory for the test.",
"Simulates a stage directory in which test_theme is updated."
]
}
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/my_module",
"version": "9.8.0"
}
],
"packages-dev": [
{
"name": "drupal/my_dev_module",
"version": "9.8.1"
}
]
}
{
"packages": [
{
"name": "drupal/core",
"version": "9.8.1",
"type": "drupal-core",
"extra": {
"drupal-scaffold": {
"file-mapping": {}
}
}
},
{
"name": "drupal/semver_test",
"version": "8.1.0",
"type": "drupal-module"
},
{
"name": "drupal/aaa_update_test",
"version": "2.0.0",
"type": "drupal-module"
},
{
"name": "drupal/test_theme",
"version": "2.1.0",
"type": "drupal-theme"
}
]
}
{
"extra": {
"_readme": [
"This fixture assumes that ../../two_projects is active directory for the test.",
"Simulates a stage directory in which semver_test and aaa_update_test have been updated."
]
}
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/my_module",
"version": "9.8.0"
}
],
"packages-dev": [
{
"name": "drupal/my_dev_module",
"version": "9.8.1"
}
]
}
{
"packages": [
{
"name": "drupal/core",
"version": "9.8.1",
"type": "drupal-core",
"extra": {
"drupal-scaffold": {
"file-mapping": {}
}
}
},
{
"name": "drupal/semver_test",
"version": "8.1.1",
"type": "drupal-module"
},
{
"name": "drupal/aaa_update_test",
"version": "2.1.0",
"type": "drupal-module"
},
{
"name": "drupal/test_theme",
"version": "2.0.0",
"type": "drupal-theme"
}
]
}
......@@ -14,6 +14,10 @@
{
"name": "drupal/aaa_update_test",
"version": "9.8.1"
},
{
"name": "drupal/test_theme",
"version": "9.8.1"
}
]
}
......@@ -12,17 +12,17 @@
},
{
"name": "drupal/semver_test",
"version": "9.8.1",
"version": "8.1.0",
"type": "drupal-module"
},
{
"name": "drupal/aaa_update_test",
"version": "9.8.1",
"version": "2.0.0",
"type": "drupal-module"
},
{
"name": "drupal/test_theme",
"version": "9.8.1",
"version": "2.0.0",
"type": "drupal-theme"
}
]
......
......@@ -8,6 +8,7 @@ use Drupal\automatic_updates_test\StagedDatabaseUpdateValidator;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\ValidationResult;
use Drupal\package_manager_bypass\Beginner;
use Drupal\package_manager_bypass\Stager;
use Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use Drupal\Tests\automatic_updates_extensions\Traits\FormTestTrait;
......@@ -122,9 +123,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
* The expected installed version.
* @param string $expected_target_version
* The expected target version.
* @param int $row
* The row number.
*/
private function assertTableShowsUpdates(string $expected_project_title, string $expected_installed_version, string $expected_target_version): void {
$this->assertUpdateTableRow($this->assertSession(), $expected_project_title, $expected_installed_version, $expected_target_version);
private function assertTableShowsUpdates(string $expected_project_title, string $expected_installed_version, string $expected_target_version, int $row = 1): void {
$this->assertUpdateTableRow($this->assertSession(), $expected_project_title, $expected_installed_version, $expected_target_version, $row);
}
/**
......@@ -154,13 +157,13 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
*/
public function testSuccessfulUpdate(bool $maintenance_mode_on, string $project_name, string $project_title, string $installed_version, string $target_version): void {
$this->container->get('theme_installer')->install(['automatic_updates_theme_with_updates']);
$this->updateProject = $project_name;
// By default, the Update module only checks for updates of installed modules
// and themes. The two modules we're testing here (semver_test and aaa_update_test)
// are already installed by static::$modules.
$this->container->get('theme_installer')->install(['test_theme']);
Stager::setFixturePath(__DIR__ . '/../../fixtures/stage_composer/' . $project_name);
$this->setReleaseMetadata(__DIR__ . '/../../../../tests/fixtures/release-history/drupal.9.8.2.xml');
$this->setReleaseMetadata(__DIR__ . "/../../fixtures/release-history/$project_name.1.1.xml");
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/' . $project_name . '.1.1.xml');
$this->setProjectInstalledVersion([$project_name => $installed_version]);
$this->checkForUpdates();
$state = $this->container->get('state');
......@@ -184,13 +187,14 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$assert_session->pageTextContains('Please select one or more projects.');
// Submit with a project selected.
$page->checkField('projects[' . $this->updateProject . ']');
$page->checkField('projects[' . $project_name . ']');
$page->pressButton('Update');
$this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1);
// Confirm that the site was put into maintenance mode if needed.
$this->assertSame($state->get('system.maintenance_mode'), $maintenance_mode_on);
$assert_session->pageTextNotContains('The following dependencies will also be updated:');
// Ensure that a list of pending database updates is visible, along with a
// short explanation, in the warning messages.
$warning_messages = $assert_session->elementExists('xpath', '//div[@data-drupal-messages]//div[@aria-label="Warning message"]');
......@@ -212,6 +216,79 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->assertSame($state->get('system.maintenance_mode'), $maintenance_mode_on);
}
/**
* Data provider for testDisplayUpdates().
*
* @return array[]
* The test cases.
*/
public function providerDisplayUpdates(): array {
return [
'with unrequested updates' => [TRUE],
'without unrequested updates' => [FALSE],
];
}
/**
* Tests the form displays the correct projects which will be updated.
*
* @param bool $unrequested_updates
* Whether unrequested updates are present during update.
*
* @dataProvider providerDisplayUpdates
*/
public function testDisplayUpdates(bool $unrequested_updates): void {
$this->container->get('theme_installer')->install(['automatic_updates_theme_with_updates']);
$this->setReleaseMetadata(__DIR__ . '/../../../../tests/fixtures/release-history/drupal.9.8.2.xml');
$this->setReleaseMetadata(__DIR__ . "/../../fixtures/release-history/semver_test.1.1.xml");
$this->setReleaseMetadata(__DIR__ . "/../../fixtures/release-history/aaa_update_test.1.1.xml");
Stager::setFixturePath(__DIR__ . '/../../fixtures/stage_composer/two_projects');
$this->setProjectInstalledVersion([
'semver_test' => '8.1.0',
'aaa_update_test' => '8.x-2.0',
]);
$this->checkForUpdates();
$state = $this->container->get('state');
$page = $this->getSession()->getPage();
// Navigate to the automatic updates form.
$this->drupalGet('/admin/reports/updates');
$this->clickLink('Update Extensions');
$this->assertTableShowsUpdates(
'AAA Update test',
'8.x-2.0',
'8.x-2.1',
);
$this->assertTableShowsUpdates(
'Semver Test',
'8.1.0',
'8.1.1',
2
);
// User will choose both the projects to update and there will be no
// unrequested updates.
if ($unrequested_updates === FALSE) {
$page->checkField('projects[aaa_update_test]');
}
$page->checkField('projects[semver_test]');
$page->pressButton('Update');
$this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1);
$assert_session = $this->assertSession();
// Both projects will be shown as requested updates if there are no
// unrequested updates, otherwise one project which user chose will be shown
// as requested update and other one will be shown as unrequested update.
if ($unrequested_updates === FALSE) {
$assert_session->pageTextNotContains('The following dependencies will also be updated:');
}
else {
$assert_session->pageTextContains('The following dependencies will also be updated:');
}
$assert_session->pageTextContains('The following projects will be updated:');
$assert_session->pageTextContains('Semver Test from 8.1.0 to 8.1.1');
$assert_session->pageTextContains('AAA Update test from 2.0.0 to 2.1.0');
}
/**
* Tests the form when modules requiring an update not installed via composer.
*/
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment