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
No related merge requests found
Showing
with 431 additions and 11 deletions
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\automatic_updates_extensions\Form; namespace Drupal\automatic_updates_extensions\Form;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates\Validator\StagedDatabaseUpdateValidator; use Drupal\automatic_updates\Validator\StagedDatabaseUpdateValidator;
use Drupal\automatic_updates_extensions\BatchProcessor; use Drupal\automatic_updates_extensions\BatchProcessor;
use Drupal\automatic_updates\BatchProcessor as AutoUpdatesBatchProcessor; use Drupal\automatic_updates\BatchProcessor as AutoUpdatesBatchProcessor;
...@@ -13,6 +14,7 @@ use Drupal\Core\Form\FormStateInterface; ...@@ -13,6 +14,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\State\StateInterface; use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\package_manager\Exception\StageException; use Drupal\package_manager\Exception\StageException;
use Drupal\package_manager\Exception\StageOwnershipException; use Drupal\package_manager\Exception\StageOwnershipException;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
...@@ -159,9 +161,7 @@ final class UpdateReady extends FormBase { ...@@ -159,9 +161,7 @@ final class UpdateReady extends FormBase {
'#type' => 'value', '#type' => 'value',
'#value' => $stage_id, '#value' => $stage_id,
]; ];
$form['package_updates'] = $this->showUpdates();
// @todo Display the project versions that will be update including any
// dependencies that are Drupal projects.
$form['backup'] = [ $form['backup'] = [
'#prefix' => '<strong>', '#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']), '#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 { ...@@ -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 @@ ...@@ -14,6 +14,10 @@
{ {
"name": "drupal/aaa_update_test", "name": "drupal/aaa_update_test",
"version": "9.8.1" "version": "9.8.1"
},
{
"name": "drupal/test_theme",
"version": "9.8.1"
} }
] ]
} }
...@@ -12,17 +12,17 @@ ...@@ -12,17 +12,17 @@
}, },
{ {
"name": "drupal/semver_test", "name": "drupal/semver_test",
"version": "9.8.1", "version": "8.1.0",
"type": "drupal-module" "type": "drupal-module"
}, },
{ {
"name": "drupal/aaa_update_test", "name": "drupal/aaa_update_test",
"version": "9.8.1", "version": "2.0.0",
"type": "drupal-module" "type": "drupal-module"
}, },
{ {
"name": "drupal/test_theme", "name": "drupal/test_theme",
"version": "9.8.1", "version": "2.0.0",
"type": "drupal-theme" "type": "drupal-theme"
} }
] ]
......
...@@ -8,6 +8,7 @@ use Drupal\automatic_updates_test\StagedDatabaseUpdateValidator; ...@@ -8,6 +8,7 @@ use Drupal\automatic_updates_test\StagedDatabaseUpdateValidator;
use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\ValidationResult; use Drupal\package_manager\ValidationResult;
use Drupal\package_manager_bypass\Beginner; use Drupal\package_manager_bypass\Beginner;
use Drupal\package_manager_bypass\Stager;
use Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase; use Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use Drupal\Tests\automatic_updates_extensions\Traits\FormTestTrait; use Drupal\Tests\automatic_updates_extensions\Traits\FormTestTrait;
...@@ -122,9 +123,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -122,9 +123,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
* The expected installed version. * The expected installed version.
* @param string $expected_target_version * @param string $expected_target_version
* The 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 { 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); $this->assertUpdateTableRow($this->assertSession(), $expected_project_title, $expected_installed_version, $expected_target_version, $row);
} }
/** /**
...@@ -154,13 +157,13 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -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 { 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->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 // 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) // and themes. The two modules we're testing here (semver_test and aaa_update_test)
// are already installed by static::$modules. // are already installed by static::$modules.
$this->container->get('theme_installer')->install(['test_theme']); $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__ . '/../../../../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->setProjectInstalledVersion([$project_name => $installed_version]);
$this->checkForUpdates(); $this->checkForUpdates();
$state = $this->container->get('state'); $state = $this->container->get('state');
...@@ -184,13 +187,14 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -184,13 +187,14 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$assert_session->pageTextContains('Please select one or more projects.'); $assert_session->pageTextContains('Please select one or more projects.');
// Submit with a project selected. // Submit with a project selected.
$page->checkField('projects[' . $this->updateProject . ']'); $page->checkField('projects[' . $project_name . ']');
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1); $this->assertUpdateStagedTimes(1);
// Confirm that the site was put into maintenance mode if needed. // Confirm that the site was put into maintenance mode if needed.
$this->assertSame($state->get('system.maintenance_mode'), $maintenance_mode_on); $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 // Ensure that a list of pending database updates is visible, along with a
// short explanation, in the warning messages. // short explanation, in the warning messages.
$warning_messages = $assert_session->elementExists('xpath', '//div[@data-drupal-messages]//div[@aria-label="Warning message"]'); $warning_messages = $assert_session->elementExists('xpath', '//div[@data-drupal-messages]//div[@aria-label="Warning message"]');
...@@ -212,6 +216,79 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -212,6 +216,79 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->assertSame($state->get('system.maintenance_mode'), $maintenance_mode_on); $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. * 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