From cd12944d9abbbeb40fa3bd684d84272b26604fd1 Mon Sep 17 00:00:00 2001 From: catch <catch@35733.no-reply.drupal.org> Date: Tue, 2 May 2023 09:05:43 +0100 Subject: [PATCH] Issue #3129762 by amateescu, adriancid, msuthars, Alexj12: Creating an unpublished entity in a workspace does not set the workspace field on the revision --- .../workspaces/src/EntityOperations.php | 4 +- .../DeletedWorkspaceConstraintValidator.php | 21 ++- .../workspaces/src/WorkspaceAssociation.php | 48 ++++- .../src/WorkspaceAssociationInterface.php | 17 ++ .../workspaces/src/WorkspaceManager.php | 45 ++++- .../workspaces/src/WorkspaceRepository.php | 1 + .../src/Kernel/WorkspaceAssociationTest.php | 172 ++++++++++++++++++ .../tests/src/Kernel/WorkspaceCRUDTest.php | 46 ++--- .../tests/src/Kernel/WorkspaceTestTrait.php | 6 + 9 files changed, 305 insertions(+), 55 deletions(-) create mode 100644 core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php diff --git a/core/modules/workspaces/src/EntityOperations.php b/core/modules/workspaces/src/EntityOperations.php index 603223d75132..128f6915e90c 100644 --- a/core/modules/workspaces/src/EntityOperations.php +++ b/core/modules/workspaces/src/EntityOperations.php @@ -136,8 +136,10 @@ public function entityPresave(EntityInterface $entity) { // become the default revision only when it is replicated to the default // workspace. $entity->isDefaultRevision(FALSE); + } - // Track the workspaces in which the new revision was saved. + // Track the workspaces in which the new revision was saved. + if (!$entity->isSyncing()) { $field_name = $entity_type->getRevisionMetadataKey('workspace'); $entity->{$field_name}->target_id = $this->workspaceManager->getActiveWorkspace()->id(); } diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php index 1543b5fc2b30..0d554c8c3a9d 100644 --- a/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php +++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php @@ -3,7 +3,7 @@ namespace Drupal\workspaces\Plugin\Validation\Constraint; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\workspaces\WorkspaceAssociationInterface; +use Drupal\Core\State\StateInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -14,20 +14,20 @@ class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface { /** - * The workspace association service. + * The state service. * - * @var \Drupal\workspaces\WorkspaceAssociationInterface + * @var \Drupal\Core\State\StateInterface */ - protected $workspaceAssociation; + protected $state; /** * Creates a new DeletedWorkspaceConstraintValidator instance. * - * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association - * The workspace association service. + * @param \Drupal\Core\State\StateInterface $state + * The state service. */ - public function __construct(WorkspaceAssociationInterface $workspace_association) { - $this->workspaceAssociation = $workspace_association; + public function __construct(StateInterface $state) { + $this->state = $state; } /** @@ -35,7 +35,7 @@ public function __construct(WorkspaceAssociationInterface $workspace_association */ public static function create(ContainerInterface $container) { return new static( - $container->get('workspaces.association') + $container->get('state') ); } @@ -49,7 +49,8 @@ public function validate($value, Constraint $constraint) { return; } - if ($this->workspaceAssociation->getTrackedEntities($value->getEntity()->id())) { + $deleted_workspace_ids = $this->state->get('workspace.deleted', []); + if (isset($deleted_workspace_ids[$value->getEntity()->id()])) { $this->context->addViolation($constraint->message); } } diff --git a/core/modules/workspaces/src/WorkspaceAssociation.php b/core/modules/workspaces/src/WorkspaceAssociation.php index c9bcee9219ec..471ff986b958 100644 --- a/core/modules/workspaces/src/WorkspaceAssociation.php +++ b/core/modules/workspaces/src/WorkspaceAssociation.php @@ -186,13 +186,21 @@ public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_i $id_field = $table_mapping->getColumnNames($entity_type->getKey('id'))['value']; $revision_id_field = $table_mapping->getColumnNames($entity_type->getKey('revision'))['value']; + $workspace_tree = $this->workspaceRepository->loadTree(); + if (isset($workspace_tree[$workspace_id])) { + $workspace_candidates = array_merge([$workspace_id], $workspace_tree[$workspace_id]['ancestors']); + } + else { + $workspace_candidates = [$workspace_id]; + } + $query = $this->database->select($entity_type->getRevisionTable(), 'revision'); $query->leftJoin($entity_type->getBaseTable(), 'base', "[revision].[$id_field] = [base].[$id_field]"); $query ->fields('revision', [$revision_id_field, $id_field]) - ->condition("revision.$workspace_field", $workspace_id) - ->where("[revision].[$revision_id_field] > [base].[$revision_id_field]") + ->condition("revision.$workspace_field", $workspace_candidates, 'IN') + ->where("[revision].[$revision_id_field] >= [base].[$revision_id_field]") ->orderBy("revision.$revision_id_field", 'ASC'); // Restrict the result to a set of entity ID's if provided. @@ -203,6 +211,42 @@ public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_i return $query->execute()->fetchAllKeyed(); } + /** + * {@inheritdoc} + */ + public function getAssociatedInitialRevisions(string $workspace_id, string $entity_type_id, array $entity_ids = []) { + /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ + $storage = $this->entityTypeManager->getStorage($entity_type_id); + + // If the entity type is not using core's default entity storage, we can't + // assume the table mapping layout so we have to return only the latest + // tracked revisions. + if (!$storage instanceof SqlContentEntityStorage) { + return $this->getTrackedEntities($workspace_id, $entity_type_id, $entity_ids)[$entity_type_id]; + } + + $entity_type = $storage->getEntityType(); + $table_mapping = $storage->getTableMapping(); + $workspace_field = $table_mapping->getColumnNames($entity_type->get('revision_metadata_keys')['workspace'])['target_id']; + $id_field = $table_mapping->getColumnNames($entity_type->getKey('id'))['value']; + $revision_id_field = $table_mapping->getColumnNames($entity_type->getKey('revision'))['value']; + + $query = $this->database->select($entity_type->getBaseTable(), 'base'); + $query->leftJoin($entity_type->getRevisionTable(), 'revision', "[base].[$revision_id_field] = [revision].[$revision_id_field]"); + + $query + ->fields('base', [$revision_id_field, $id_field]) + ->condition("revision.$workspace_field", $workspace_id, '=') + ->orderBy("base.$revision_id_field", 'ASC'); + + // Restrict the result to a set of entity ID's if provided. + if ($entity_ids) { + $query->condition("base.$id_field", $entity_ids, 'IN'); + } + + return $query->execute()->fetchAllKeyed(); + } + /** * {@inheritdoc} */ diff --git a/core/modules/workspaces/src/WorkspaceAssociationInterface.php b/core/modules/workspaces/src/WorkspaceAssociationInterface.php index 72d0c107caba..36a27b698157 100644 --- a/core/modules/workspaces/src/WorkspaceAssociationInterface.php +++ b/core/modules/workspaces/src/WorkspaceAssociationInterface.php @@ -74,6 +74,23 @@ public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entit */ public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL); + /** + * Retrieves all content revisions that were created in a given workspace. + * + * @param string $workspace_id + * The ID of the workspace. + * @param string $entity_type_id + * An entity type ID to find revisions for. + * @param int[]|string[] $entity_ids + * (optional) An array of entity IDs to filter the results by. Defaults to + * an empty array. + * + * @return array + * Returns an array where the values are an array of entity IDs keyed by + * revision IDs. + */ + public function getAssociatedInitialRevisions(string $workspace_id, string $entity_type_id, array $entity_ids = []); + /** * Gets a list of workspace IDs in which an entity is tracked. * diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php index fdb47d6973a8..f48d1c805d59 100644 --- a/core/modules/workspaces/src/WorkspaceManager.php +++ b/core/modules/workspaces/src/WorkspaceManager.php @@ -316,31 +316,60 @@ public function purgeDeletedWorkspacesBatch() { // Get the first deleted workspace from the list and delete the revisions // associated with it, along with the workspace association records. $workspace_id = reset($deleted_workspace_ids); - $tracked_entities = $this->workspaceAssociation->getTrackedEntities($workspace_id); + + $all_associated_revisions = []; + foreach (array_keys($this->getSupportedEntityTypes()) as $entity_type_id) { + $all_associated_revisions[$entity_type_id] = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id); + } + $all_associated_revisions = array_filter($all_associated_revisions); $count = 1; - foreach ($tracked_entities as $entity_type_id => $entities) { + foreach ($all_associated_revisions as $entity_type_id => $associated_revisions) { $associated_entity_storage = $this->entityTypeManager->getStorage($entity_type_id); - $associated_revisions = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id); + + // Sort the associated revisions in reverse ID order, so we can delete the + // most recent revisions first. + krsort($associated_revisions); + + // Get a list of default revisions tracked by the given workspace, because + // they need to be handled differently than pending revisions. + $initial_revision_ids = $this->workspaceAssociation->getAssociatedInitialRevisions($workspace_id, $entity_type_id); + foreach (array_keys($associated_revisions) as $revision_id) { if ($count > $batch_size) { continue 2; } - // Delete the associated entity revision. - $associated_entity_storage->deleteRevision($revision_id); + // If the workspace is tracking the entity's default revision (i.e. the + // entity was created inside that workspace), we need to delete the + // whole entity after all of its pending revisions are gone. + if (isset($initial_revision_ids[$revision_id])) { + $associated_entity_storage->delete([$associated_entity_storage->load($initial_revision_ids[$revision_id])]); + } + else { + // Delete the associated entity revision. + $associated_entity_storage->deleteRevision($revision_id); + } $count++; } - // Delete the workspace association entries. - $this->workspaceAssociation->deleteAssociations($workspace_id, $entity_type_id, $entities); } // The purging operation above might have taken a long time, so we need to // request a fresh list of tracked entities. If it is empty, we can go ahead // and remove the deleted workspace ID entry from state. - if (!$this->workspaceAssociation->getTrackedEntities($workspace_id)) { + $has_associated_revisions = FALSE; + foreach (array_keys($this->getSupportedEntityTypes()) as $entity_type_id) { + if (!empty($this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id))) { + $has_associated_revisions = TRUE; + break; + } + } + if (!$has_associated_revisions) { unset($deleted_workspace_ids[$workspace_id]); $this->state->set('workspace.deleted', $deleted_workspace_ids); + + // Delete any possible leftover association entries. + $this->workspaceAssociation->deleteAssociations($workspace_id); } } diff --git a/core/modules/workspaces/src/WorkspaceRepository.php b/core/modules/workspaces/src/WorkspaceRepository.php index d939cafdf590..55ecffe1d7fb 100644 --- a/core/modules/workspaces/src/WorkspaceRepository.php +++ b/core/modules/workspaces/src/WorkspaceRepository.php @@ -145,6 +145,7 @@ public function getDescendantsAndSelf($workspace_id) { * {@inheritdoc} */ public function resetCache() { + $this->cache->invalidate('workspace_tree'); $this->tree = NULL; return $this; diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php new file mode 100644 index 000000000000..6bf43f30504b --- /dev/null +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php @@ -0,0 +1,172 @@ +<?php + +namespace Drupal\Tests\workspaces\Kernel; + +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\node\Traits\ContentTypeCreationTrait; +use Drupal\Tests\node\Traits\NodeCreationTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; +use Drupal\workspaces\Entity\Workspace; + +/** + * Tests workspace associations. + * + * @coversDefaultClass \Drupal\workspaces\WorkspaceAssociation + * + * @group workspaces + */ +class WorkspaceAssociationTest extends KernelTestBase { + + use ContentTypeCreationTrait; + use NodeCreationTrait; + use UserCreationTrait; + use WorkspaceTestTrait; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'field', + 'filter', + 'node', + 'text', + 'user', + 'system', + 'path_alias', + 'workspaces', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->entityTypeManager = \Drupal::entityTypeManager(); + + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installEntitySchema('workspace'); + + $this->installConfig(['filter', 'node', 'system']); + + $this->installSchema('node', ['node_access']); + $this->installSchema('system', ['sequences']); + $this->installSchema('workspaces', ['workspace_association']); + + $this->createContentType(['type' => 'article']); + + $permissions = array_intersect([ + 'administer nodes', + 'create workspace', + 'edit any workspace', + 'view any workspace', + ], array_keys($this->container->get('user.permissions')->getPermissions())); + $this->setCurrentUser($this->createUser($permissions)); + + $this->workspaces['stage'] = Workspace::create(['id' => 'stage', 'label' => 'Stage']); + $this->workspaces['stage']->save(); + $this->workspaces['dev'] = Workspace::create(['id' => 'dev', 'parent' => 'stage', 'label' => 'Dev']); + $this->workspaces['dev']->save(); + } + + /** + * Tests the revisions tracked by a workspace. + * + * @covers ::getTrackedEntities + * @covers ::getAssociatedRevisions + */ + public function testWorkspaceAssociation() { + $this->createNode(['title' => 'Test article 1 - live - unpublished', 'type' => 'article', 'status' => 0]); + $this->createNode(['title' => 'Test article 2 - live - published', 'type' => 'article']); + + // Edit one of the existing nodes in 'stage'. + $this->switchToWorkspace('stage'); + $node = $this->entityTypeManager->getStorage('node')->load(1); + $node->setTitle('Test article 1 - stage - published'); + $node->setPublished(); + // This creates rev. 3. + $node->save(); + + // Generate content with the following structure: + // Stage: + // - Test article 3 - stage - unpublished (rev. 4) + // - Test article 4 - stage - published (rev. 5 and 6) + $this->createNode(['title' => 'Test article 3 - stage - unpublished', 'type' => 'article', 'status' => 0]); + $this->createNode(['title' => 'Test article 4 - stage - published', 'type' => 'article']); + + $expected_latest_revisions = [ + 'stage' => [3, 4, 6], + ]; + $expected_all_revisions = [ + 'stage' => [3, 4, 5, 6], + ]; + $expected_initial_revisions = [ + 'stage' => [4, 5], + ]; + $this->assertWorkspaceAssociations('node', $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions); + + // Dev: + // - Test article 1 - stage - published (rev. 3) + // - Test article 3 - stage - unpublished (rev. 4) + // - Test article 4 - stage - published (rev. 5 and 6) + // - Test article 5 - dev - unpublished (rev. 7) + // - Test article 6 - dev - published (rev. 8 and 9) + $this->switchToWorkspace('dev'); + $this->createNode(['title' => 'Test article 5 - dev - unpublished', 'type' => 'article', 'status' => 0]); + $this->createNode(['title' => 'Test article 6 - dev - published', 'type' => 'article']); + + $expected_latest_revisions += [ + 'dev' => [3, 4, 6, 7, 9], + ]; + // Revisions 3, 4, 5 and 6 that were created in the parent 'stage' workspace + // are also considered as being part of the child 'dev' workspace. + $expected_all_revisions += [ + 'dev' => [3, 4, 5, 6, 7, 8, 9], + ]; + $expected_initial_revisions += [ + 'stage' => [7, 8], + ]; + $this->assertWorkspaceAssociations('node', $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions); + } + + /** + * Checks the workspace associations for a test scenario. + * + * @param string $entity_type_id + * The ID of the entity type that is being tested. + * @param array $expected_latest_revisions + * An array of expected values for the latest tracked revisions. + * @param array $expected_all_revisions + * An array of expected values for all the tracked revisions. + * @param array $expected_initial_revisions + * An array of expected values for the initial revisions, i.e. for the + * entities that were created in the specified workspace. + */ + protected function assertWorkspaceAssociations($entity_type_id, array $expected_latest_revisions, array $expected_all_revisions, array $expected_initial_revisions) { + $workspace_association = \Drupal::service('workspaces.association'); + foreach ($expected_latest_revisions as $workspace_id => $expected_tracked_revision_ids) { + $tracked_entities = $workspace_association->getTrackedEntities($workspace_id, $entity_type_id); + $tracked_revision_ids = $tracked_entities[$entity_type_id] ?? []; + $this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids)); + } + + foreach ($expected_all_revisions as $workspace_id => $expected_all_revision_ids) { + $all_associated_revisions = $workspace_association->getAssociatedRevisions($workspace_id, $entity_type_id); + $this->assertEquals($expected_all_revision_ids, array_keys($all_associated_revisions)); + } + + foreach ($expected_initial_revisions as $workspace_id => $expected_initial_revision_ids) { + $initial_revisions = $workspace_association->getAssociatedInitialRevisions($workspace_id, $entity_type_id); + $this->assertEquals($expected_initial_revision_ids, array_keys($initial_revisions)); + } + } + +} diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php index 71803e50d9bf..9dd5177c6a25 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php @@ -69,6 +69,7 @@ protected function setUp(): void { $this->installEntitySchema('workspace'); $this->installSchema('workspaces', ['workspace_association']); $this->installEntitySchema('node'); + $this->installEntitySchema('path_alias'); $this->installConfig(['filter', 'node', 'system']); @@ -106,10 +107,9 @@ public function testDeletingWorkspaces() { $workspace_1_node_1 = $this->createNode(['status' => FALSE]); $workspace_1_node_2 = $this->createNode(['status' => FALSE]); - // The 'live' workspace should have 2 revisions now. The initial revision - // for each node. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(2, $live_revisions); + // Check that the workspace tracks the initial revisions for both nodes. + $initial_revisions = $workspace_association->getAssociatedInitialRevisions($workspace_1->id(), 'node'); + $this->assertCount(2, $initial_revisions); for ($i = 0; $i < 4; $i++) { $workspace_1_node_1->setNewRevision(TRUE); @@ -123,13 +123,10 @@ public function testDeletingWorkspaces() { $tracked_entities = $workspace_association->getTrackedEntities($workspace_1->id()); $this->assertCount(2, $tracked_entities['node']); - // There should still be 2 revisions associated with 'live'. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(2, $live_revisions); - - // The other 8 revisions should be associated with 'workspace_1'. + // Since all the revisions were created inside a workspace, including the + // default one, 'workspace_1' should be tracking all 10 revisions. $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node'); - $this->assertCount(8, $associated_revisions); + $this->assertCount(10, $associated_revisions); // Check that we are allowed to delete the workspace. $this->assertTrue($workspace_1->access('delete', $admin)); @@ -146,10 +143,6 @@ public function testDeletingWorkspaces() { $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node'); $this->assertCount(0, $associated_revisions); - // There should still be 2 revisions associated with 'live'. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(2, $live_revisions); - // Create another workspace, this time with a larger number of associated // node revisions so we can test the batch purge process. $workspace_2 = Workspace::create([ @@ -169,23 +162,15 @@ public function testDeletingWorkspaces() { $tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id()); $this->assertCount(1, $tracked_entities['node']); - // One revision of this entity is in 'live'. - $live_revisions = $this->getUnassociatedRevisions('node', [$workspace_2_node_1->id()]); - $this->assertCount(1, $live_revisions); - - // The other 59 are associated with 'workspace_2'. + // All 60 are associated with 'workspace_2'. $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]); - $this->assertCount(59, $associated_revisions); + $this->assertCount(60, $associated_revisions); - // Delete the workspace and check that we still have 9 revision left to + // Delete the workspace and check that we still have 10 revision left to // delete. $workspace_2->delete(); $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]); - $this->assertCount(9, $associated_revisions); - - // The live revision is also still there. - $live_revisions = $this->getUnassociatedRevisions('node', [$workspace_2_node_1->id()]); - $this->assertCount(1, $live_revisions); + $this->assertCount(10, $associated_revisions); $workspace_deleted = \Drupal::state()->get('workspace.deleted'); $this->assertCount(1, $workspace_deleted); @@ -204,18 +189,11 @@ public function testDeletingWorkspaces() { // from the "workspace.delete" state entry. \Drupal::service('cron')->run(); - $associated_revisions = $workspace_association->getTrackedEntities($workspace_2->id()); - $this->assertCount(0, $associated_revisions); - // 'workspace_2 'is empty now. $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]); $this->assertCount(0, $associated_revisions); $tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id()); - $this->assertEmpty($tracked_entities); - - // The 3 revisions in 'live' remain. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(3, $live_revisions); + $this->assertCount(0, $tracked_entities); $workspace_deleted = \Drupal::state()->get('workspace.deleted'); $this->assertCount(0, $workspace_deleted); diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php index 6f6d723cd653..7d6cfdeb99e9 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php @@ -37,6 +37,12 @@ protected function initializeWorkspacesModule() { $this->installEntitySchema('workspace'); $this->installSchema('workspaces', ['workspace_association']); + // Install the entity schema for supported entity types to ensure that the + // 'workspace' revision metadata field gets created. + foreach (array_keys($this->workspaceManager->getSupportedEntityTypes()) as $entity_type_id) { + $this->installEntitySchema($entity_type_id); + } + // Create two workspaces by default, 'live' and 'stage'. $this->workspaces['live'] = Workspace::create(['id' => 'live', 'label' => 'Live']); $this->workspaces['live']->save(); -- GitLab