Problem/Motivation

The current way of associating a revision with a workspace has a couple of problems:

- the association history is lost when a workspace is published to Live, because we delete all entries from the workspace_association tables
- when we introduce workspace hierarchies, operations on the workspace_association entities will be either 1) very costly, because we have to update all the associations of a workspace as well as its descendants, or 2) bypass the Entity API completely, which makes the current implementation completely unnecessary overhead :)

Proposed resolution

- track the workspace in which a revision was created in a (revision metadata) base field
- convert the workspace_association entity type to a custom index table

Remaining tasks

Discuss, agree on the proposed resolution, write upgrade path and tests.

User interface changes

Nope.

API changes

The workspace_association will be a service in the container instead of an entity type.

Data model changes

All entity types supported by Workspaces will have an additional revision metadata (reference) field: workspace

Release notes snippet

TBD.

CommentFileSizeAuthor
#54 3062434-54.patch99.81 KBamateescu
#53 interdiff-53.txt953 bytesamateescu
#53 3062434-53.patch91.95 KBamateescu
#46 interdiff-46.txt4.4 KBamateescu
#46 3062434-46.patch99.82 KBamateescu
#45 3062434-45.patch99.83 KBamateescu
#43 3062434-43.patch99.86 KBamateescu
#43 3062434-43-test-only.patch98.04 KBamateescu
#42 interdiff-42.txt11.22 KBamateescu
#42 3062434-42.patch92 KBamateescu
#42 interdiff-42-test-only.txt3.3 KBamateescu
#42 3062434-42-test-only.patch90.18 KBamateescu
#38 interdiff-38.txt3.62 KBamateescu
#38 3062434-38.patch95.32 KBamateescu
#36 interdiff-36.txt2.42 KBamateescu
#36 3062434-36.patch87 KBamateescu
#31 interdiff-31.txt593 bytesamateescu
#31 3062434-31.patch94.54 KBamateescu
#27 interdiff-27.txt1.83 KBamateescu
#27 3062434-27.patch94.38 KBamateescu
#23 3062434-23-combined.patch116.41 KBamateescu
#23 3062434-23.patch94.02 KBamateescu
#22 interdiff-22.txt4.56 KBamateescu
#22 3062434-22-combined.patch108.54 KBamateescu
#22 3062434-22.patch86.15 KBamateescu
#19 3062434-19.patch108.74 KBblazey
#18 3062434-18.patch100.88 KBblazey
#17 interdiff.txt1.29 KBblazey
#17 3062434-17.patch200.14 KBblazey
#16 3062434-16-combined.patch107.67 KBamateescu
#14 3062434-14-workspaces-installed-fixture.txt42.75 KBamateescu
#14 3062434-14-combined.patch101.25 KBamateescu
#14 3062434-14.patch94.45 KBamateescu
#13 interdiff-13.txt20.2 KBamateescu
#13 3062434-13.patch86.59 KBamateescu
#13 3062434-13-combined.patch92.6 KBamateescu
#12 interdiff-12.txt2.99 KBamateescu
#12 3062434-12.patch67.05 KBamateescu
#10 interdiff.3062434.7-10.txt3.38 KBpmelab
#10 3062434-10.drupal.Track-the-workspace-of-a-revision-in-a-base-field-and-convert-the-workspaceassociation-entity-type-to-a-custom-index.patch67.27 KBpmelab
#7 interdiff-7.txt11.54 KBamateescu
#7 3062434-7.patch66.46 KBamateescu
#6 interdiff.3062434.3-6.txt16.19 KBpmelab
#6 3062434-6.drupal.Track-the-workspace-of-a-revision-in-a-base-field-and-convert-the-workspaceassociation-entity-type-to-a-custom-index.patch65.82 KBpmelab
#4 interdiff-3-4.txt11.38 KBpmelab
#4 3062434-workspace-index-4.patch62.85 KBpmelab
#3 interdiff-3.txt762 bytesamateescu
#3 3062434-3.patch57.05 KBamateescu
#2 3062434.patch56.57 KBamateescu
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

amateescu created an issue. See original summary.

amateescu’s picture

Title: Track the workspace of a revision in an entity field and convert the workspace_association entity type to a custom index » Track the workspace of a revision in a base field and convert the workspace_association entity type to a custom index
Status: Active » Needs review
Issue tags: +DevDaysTransylvania
FileSize
56.57 KB

Here's an initial patch to kick off the review process.

Some tests still need to be updated, and it needs an upgrade path and tests for it.

amateescu’s picture

Version: 8.7.x-dev » 8.8.x-dev
FileSize
57.05 KB
762 bytes

A small fix and moving to 8.8.x for now.

pmelab’s picture

Fixed remaining tests.

Patch #3 only deleted the currently indexed (latest) revision for a workspace. But I suppose it should delete all revisions? I changed that in Workspace::postDelete().

pmelab’s picture

pmelab’s picture

amateescu’s picture

The interdiff from #6 looks great, thanks @pmelab!

I did a few cosmetic fixes and a more substantial one:

+++ b/core/modules/workspaces/workspaces.install
@@ -58,17 +58,8 @@ function workspaces_module_preinstall($module) {
-  /** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */
-  $workspace_manager = \Drupal::service('workspaces.manager');
-  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
-  foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) {
-    $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
-    if ($workspace_manager->isEntityTypeSupported($entity_type)) {
-      unset($revision_metadata_keys['workspace']);
-      $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
-      $entity_definition_update_manager->updateEntityType($entity_type);
-    }
-  }
+  // No need to manually uninstall the workspace field.
+  // Handled by the EntityDefinitionUpdateManager now.

We're not deleting the workspace field here, just the revision metadata key :) And it's still needed, so I brought back this code.

Status: Needs review » Needs work

The last submitted patch, 7: 3062434-7.patch, failed testing. View results

pmelab’s picture

I tried to get hold of the last failing test. But I can't tell what's wrong. It seems like the EntityDefinitionUpdateManager tries to remove the workspace field twice, and this results in a fatal error when uninstalling the module.

pmelab’s picture

I finally was able to track down the problem:

Removing the revision metadata key in workspaces_uninstall causes the entity updated manager to think workspace is a not-revisionable field and tries to remove it from the regular data table, which in return causes the fatal error.

My current approach to solve this is to remove the revision metadata key in system_modules_uninstalled instead. I was able to verify this manually, but unfortunately not in a test case since I was not able to get updated entity information. I'm happy for any suggestions!

yogeshmpawar’s picture

Status: Needs work » Needs review
amateescu’s picture

I looked into the failures from #7 as well and I can confirm the problem found by @pmelab, apparently there is no way for modules to clean up the revision metadata keys that they are adding to other entity types. This will need a separate core issue to be discussed and fixed properly, but until then I think the workaround of doing it in system module is our only choice :/

Cleaned up the uninstall code a bit and I think we need to test only the definitions from the entity definition update manager, because that's what the storage and the table mapping are using to determine the table of a field.

amateescu’s picture

I discussed this issue with @catch a few days ago and he was +1 on the general direction, so here's an update path and tests for it.

While writing this upgrade path it turned out we're blocked by #3056816: Installing a new field storage definition during a fieldable entity type update is not possible because we want to install the new workspace revision metadata field for the entity types that have been converted to revisionable in 8.7.0, and it's not possible without that fix.

amateescu’s picture

Hm.. the patches above can't apply because of core/modules/content_moderation/tests/fixtures/update/drupal-8.4.0-content_moderation_installed.php, which contains serialized entity type definitions and git considers it a binary file.

Here's a new set of patches generated with --binary, and also attaching just that file generated with --text for easier reviewing :)

Status: Needs review » Needs work

The last submitted patch, 14: 3062434-14-combined.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Needs review
FileSize
107.67 KB

Filed #3067024: Add test coverage for uninstalling revisionable entity types whose code doesn't exist anymore for the failures of the combined patch from #14, so we have two dependencies now :/ Here's a new patch which includes both of them.

blazey’s picture

Workspaces cause EntityStorageBase::loadMultiple to return entities that weren't requested, i.e. their ids were not on the list, see #3068733: EntityStorageBase::loadMultiple returns unwanted entities when the static cache is warm. The Drupal\workspaces\EntityOperations::entityPreload is refactored in this issue but the problem is still there, so adding the test here as well.

blazey’s picture

blazey’s picture

Git is hard :D

Status: Needs review » Needs work

The last submitted patch, 19: 3062434-19.patch, failed testing. View results

blazey’s picture

Status: Needs work » Needs review

Ok, it looks like #3068733: EntityStorageBase::loadMultiple returns unwanted entities when the static cache is warm can be fixed in EntityStorageBase::loadMultiple and no changes to Drupal\workspaces\EntityOperations::entityPreload are required. In this case these issues can probably be dispatched independently, so removing the files added after #16.

amateescu’s picture

It seems that #3067024: Add test coverage for uninstalling revisionable entity types whose code doesn't exist anymore is not going to be easy, so let's drop that dependency and uninstall the workspace_association in a different way.

We have only one blocker issue left: #3056816: Installing a new field storage definition during a fieldable entity type update is not possible.

amateescu’s picture

amateescu’s picture

Yay, #3056816: Installing a new field storage definition during a fieldable entity type update is not possible was committed, so the non-combined patch from #23 is ready for final reviews!

The last submitted patch, 23: 3062434-23.patch, failed testing. View results

Sam152’s picture

Posting an initial review, leaving as NR to encourage others to take a look.

  1. +++ b/core/modules/system/system.module
    @@ -1441,3 +1441,22 @@ function system_element_info_alter(&$type) {
    +/**
    + * Implements hook_modules_uninstalled().
    + */
    +function system_modules_uninstalled($modules) {
    +  if (!in_array('workspaces', $modules, TRUE)) {
    +    return;
    +  }
    +
    +  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
    +  foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) {
    +    $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
    +    if ($revision_metadata_keys && array_key_exists('workspace', $revision_metadata_keys)) {
    +      unset($revision_metadata_keys['workspace']);
    +      $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
    +      $entity_definition_update_manager->updateEntityType($entity_type);
    +    }
    +  }
    +}
    

    Should there be a @todo and a follow-up to create an API for modules to update schema after they are uninstalled? Maybe also needs to explain why hook_uninstall wasn't used?

  2. +++ b/core/modules/workspaces/src/WorkspaceAssociation.php
    @@ -0,0 +1,155 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL) {
    

    Part of the motivation for this issue was to side-step entity API when managing workspaces, but doesn't the workspace field on each entity revision reintroduce that problem to some degree?

  3. +++ b/core/modules/workspaces/src/WorkspaceAssociation.php
    @@ -0,0 +1,155 @@
    +    $entity_type = $storage->getEntityType();
    +    $workspace_field = $entity_type->get('revision_metadata_keys')['workspace'];
    +    $id_field = $entity_type->getKey('id');
    

    Should this be using the installed definition? Possibly causes issues on a site between deploy => updb or sites or where updates are stuck?

  4. +++ b/core/modules/workspaces/src/WorkspaceAssociation.php
    @@ -0,0 +1,155 @@
    +    // If the workspace ID is NULL, query for the 'live' workspace.
    

    The docblock doesn't document NULL as a possible value.

  5. +++ b/core/modules/workspaces/src/WorkspaceAssociation.php
    @@ -0,0 +1,155 @@
    +    // Restrict the result to a set of entity ID's if provided.
    +    if ($entity_ids) {
    +      $query->condition($id_field, $entity_ids, 'IN');
    +    }
    +
    +    return $query->execute();
    

    Why would some code use the index and some code use the revision metadata field? As a broad question, why do both have to exist? Is it possible to answer that question through the implementation somehow?

  6. +++ b/core/modules/workspaces/src/WorkspaceAssociationInterface.php
    @@ -0,0 +1,92 @@
    +interface WorkspaceAssociationInterface {
    

    With 7.1 being the new minimum supported version, I wonder if new interfaces can start making use of features like scalar type hints?

  7. +++ b/core/modules/workspaces/src/WorkspacePublisher.php
    @@ -100,6 +102,11 @@ public function publish() {
    +            // The default revision is not workspace-specific anymore.
    +            $field_name = $entity->getEntityType()->getRevisionMetadataKey('workspace');
    +            $entity->{$field_name}->target_id = NULL;
    

    Is removing 'live' a blocker for this?

  8. +++ b/core/modules/workspaces/tests/src/Functional/Update/WorkspacesUpdateTest.php
    @@ -0,0 +1,105 @@
    +      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
    +      __DIR__ . '/../../../fixtures/update/drupal-8.6.0-workspaces_installed.php',
    

    I'm surprised this works TBH, I've seen these updates blow up when you combine an 8.6 module with 8.0+ updates.

  9. +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php
    @@ -275,7 +275,7 @@ public function testWorkspaces() {
    -    $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 6, 7]];
    +    $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 7]];
    

    Why did this test case change?

  10. +++ b/core/modules/workspaces/workspaces.install
    @@ -67,3 +90,83 @@ function workspaces_install() {
    +        'length' => 128,
    

    How come workspaces have a large ID than EntityTypeInterface::ID_MAX_LENGTH?

  11. +++ b/core/modules/workspaces/workspaces.post_update.php
    @@ -5,8 +5,133 @@
    +    $database->schema()->dropTable('workspace_association');
    +    $database->schema()->dropTable('workspace_association_revision');
    

    How come this is required?

amateescu’s picture

Thanks @Sam152 for the review, great questions!

  1. Yup, I need to create an issue for the problem explained in #10/#12.
  2. It doesn't re-introduce that problem because we are not bulk-editing old revisions, just the latest revision of entity when its workspace is published :)
  3. We are already using the last installed entity type definition because we retrieve it from the storage object, which uses the last installed def since #2554235: Make the content entity storage and entity query use the last installed definitions instead of the ones living in code.
  4. Fixed!
  5. The workspace reference that is now tracked in a field on each entity revision is the canonical source of workspace association information, which was previous stored in the workspace_association_revision table.

    The new workspace_association index table contains only the _latest_ associated revisions (just like the base table of the previous workspace_association entity type) and it's needed as a performance optimization. We tried to make the whole system use only the data available in the workspace reference field, but it turned out that it needed an extra join for each (parent) workspace in the hierarchy, per the feature proposed in #3062486: Add the ability to create sub-workspaces in order to enable the dev -> stage -> live workflow for content.

  6. I think 7.0 is new minimum supported version and there are some patches which introduce at least return types, but I'm not sure whether we should try to do that here as well..
  7. Nope, with or without a Live workspace, the workspace reference needs to be NULL for default revisions.
  8. I guess I was lucky when I wrote the 8.6 db fixture from this patch :D
  9. Because previously we were querying data from workspace_association_revision, which tracked *all* associated revisions, and now we are querying the workspace_association service/index, which tracks only the latest associated revisions.
  10. EntityTypeInterface::ID_MAX_LENGTH specifies the max length of the entity type ID (e.g. node, taxonomy_term), and that column stores workspace entity IDs (e.g. stage).
  11. See #22 :) I've done it that way in order to minimize the amount of dependencies for this issue.

Status: Needs review » Needs work

The last submitted patch, 27: 3062434-27.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Needs review

#3056816: Installing a new field storage definition during a fieldable entity type update is not possible was committed again so #27 should be green and waiting for more reviews :)

Sam152’s picture

Nice, changes and explanations look fine to me. Leaving NR for other reviews.

amateescu’s picture

Opened an issue for #10 / #12 and linked it in a @todo.

This patch will fail because #3056816: Installing a new field storage definition during a fieldable entity type update is not possible has been reverted.. again :/

Status: Needs review » Needs work

The last submitted patch, 31: 3062434-31.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Needs review

#3056816: Installing a new field storage definition during a fieldable entity type update is not possible is in again, hopefully for good this time. So we're ready for final reviews here :)

Leksat’s picture

Status: Needs review » Reviewed & tested by the community

I checked the code and did some manual testing (including the update process). Could not find any issues with the patch. Looks very good to me!

catch’s picture

The revision metadata field is a nice way to keep history and seems an OK trade-off having to maintain a custom index table for that.

  1. +++ b/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php
    @@ -0,0 +1,147 @@
    +      // through the entity entity update process.
    +      $this->entityLastInsytalledSchemaRepository->setLastInstalledDefinition($entity_type);
    +    }
    +  }
    +
    +  /**
    

    entity entity

  2. +++ b/core/modules/workspaces/src/WorkspaceAssociationInterface.php
    @@ -0,0 +1,97 @@
    + * Defines an interface for the workspace_association service.
    

    This could use a bit more documentation - i.e. that it's an index table and the canonical associations are in the revision metadata or similar.

  3. +++ b/core/modules/workspaces/src/WorkspaceManager.php
    @@ -267,67 +277,42 @@ public function purgeDeletedWorkspacesBatch() {
    -        $associated_entity_storage = $this->entityTypeManager->getStorage($workspace_association->target_entity_type_id->value);
             // Delete the associated entity revision.
    -        if ($entity = $associated_entity_storage->loadRevision($workspace_association->target_entity_revision_id->value)) {
    +        if ($entity = $associated_entity_storage->loadRevision($revision_id)) {
               if ($entity->isDefaultRevision()) {
                 $entity->delete();
               }
    

    I didn't realise we were doing this, but do we really want to have this revision purging logic in core? Is this a specific purge operation or does it happen whenever a workspace is deleted? Also how can a default revision be part of a workspace and why would we ever delete that here?

amateescu’s picture

Thanks for the reviews!

@catch, re #35:

  1. Fixed :)
  2. Done!
  3. That purging happens every time a workspace is deleted, and I think it's needed in core because otherwise we wouldn't be able to handle the case when a workspace is deleted and then another one is created with the same ID/machine name. I'm happy to discuss alternatives in a separate issue though :)

    As for default revisions being part of a workspace, I think that code is quite old and it was probably needed in some initial version of the contrib module which was tracking revisions for the Live workspace. However, since we no longer do that, we can definitely clean it up a bit, very good catch!

catch’s picture

Status: Reviewed & tested by the community » Needs review

Got interrupted during the last review, here's a few more things.

  1. +++ b/core/modules/system/system.module
    @@ -1441,3 +1441,25 @@ function system_element_info_alter(&$type) {
    +function system_modules_uninstalled($modules) {
    +  // @todo Remove this when modules are able to maintain their revision metadata
    +  //   keys.
    +  //   @see https://www.drupal.org/project/drupal/issues/3074333
    +  if (!in_array('workspaces', $modules, TRUE)) {
    +    return;
    +  }
    

    Oof, but following the follow-up and don't think it should block this.

  2. +++ b/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php
    @@ -0,0 +1,147 @@
    +      // through the entity entity update process.
    

    entity entity is still here (or maybe another instance?)

  3. +++ b/core/modules/workspaces/src/WorkspaceAssociation.php
    @@ -0,0 +1,156 @@
    +   */
    +  public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entity_ids = NULL) {
    +    $query = $this->database->select(static::TABLE, 'wa');
    +    $query
    +      ->fields('wa', ['target_entity_type_id', 'target_entity_id', 'target_entity_revision_id'])
    

    Minor, but there's no join here so why alias?

  4. +++ b/core/modules/workspaces/src/WorkspaceAssociation.php
    @@ -0,0 +1,156 @@
    +   */
    +  public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL) {
    +    /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
    +    $storage = $this->entityTypeManager->getStorage($entity_type_id);
    +    $entity_type = $storage->getEntityType();
    +    $workspace_field = $entity_type->get('revision_metadata_keys')['workspace'];
    +    $id_field = $entity_type->getKey('id');
    +
    +    $query = $storage->getQuery()->allRevisions();
    +
    +    if ($workspace_id !== NULL) {
    +      $query->condition($workspace_field, $workspace_id);
    +    }
    +    else {
    +      // If the workspace ID is NULL, query for the revisions which are not
    +      // associated with any workspace.
    +      $query->notExists($workspace_field);
    +    }
    +
    +    // Restrict the result to a set of entity ID's if provided.
    +    if ($entity_ids) {
    +      $query->condition($id_field, $entity_ids, 'IN');
    +    }
    +
    +    return $query->execute();
    +  }
    

    This looks like it's missing an ->accessCheck(FALSE)

  5. +++ b/core/modules/workspaces/src/WorkspaceAssociation.php
    @@ -0,0 +1,156 @@
    +   */
    +  public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity) {
    +    $query = $this->database->select(static::TABLE, 'wa');
    +    $query
    

    Same question with the alias and no join. Or really why not just a static query in these two places since there's nothing dynamic about the query? But then asking that table name is a constant which makes a static query harder, but does it really need to be a constant? The thing that made me think about this in the first place was using the constant, then having to use a literal string for the alias immediately afterwards which slightly undermines having a constant.

  6. +++ b/core/modules/workspaces/src/WorkspaceAssociationInterface.php
    @@ -0,0 +1,104 @@
    +   * Deletes all the association entries for the given workspace.
    

    Should be 'associated entities'.

  7. +++ b/core/modules/workspaces/src/WorkspaceManager.php
    @@ -267,67 +277,35 @@ public function purgeDeletedWorkspacesBatch() {
         // associated with it, along with the workspace_association entries.
    

    This still says workspace_association entities. Use 'records' instead?

  8. +++ b/core/modules/workspaces/workspaces.install
    @@ -67,3 +90,83 @@ function workspaces_install() {
    + */
    +function workspaces_schema() {
    +  $schema['workspace_association'] = [
    

    Since this is owned by the service, would it make sense to do the 'lazy table creation' logic that key/value and database cache backends do? We've slowly been getting rid of core usages of hook_schema().

As for default revisions being part of a workspace, I think that code is quite old and it was probably needed in some initial version of the contrib module which was tracking revisions for the Live workspace. However, since we no longer do that, we can definitely clean it up a bit, very good catch!

When a revision is made 'live'/default, is it definitely never associated with a workspace at that point (except possibly the now-defunct 'live' workspace?) - if so this might be OK, but if not then deleting previously-default revisions feels really destructive. I realise this code is already there but it'd be good to open a spin-off issue to discuss more yeah.

(also patch isn't applying).

amateescu’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
95.32 KB
3.62 KB

Re #37:

  1. I'm equally sad about that, but I'm fairly confident that we can sort it out before 8.8.0.
  2. It was another instance, fixed.
  3. Because \Drupal\Core\Database\Query\SelectInterface::fields() requires a $table_alias as its first argument. Every select query needs to set an alias like this even if they don't need to join another table :)
  4. Ugh, I'll never learn this :( Fixed.
  5. See 3. As for using a constant for the table name, I think that's the usual practice when working with a single (known) table name in a class. I'd be happy to get rid of the constant if you think it's not really needed.
  6. Nope, we are deleting the workspace_association records in that method, not the associated revisions. I changed the comment to be more clear though.
  7. It was using entries, not entities, but I agree that records is better :)
  8. TBH, I'm not a big fan of that pattern. It makes sense for some very low level services like the ones you specified, but in general it makes the code a lot harder to read and I don't think it provides any benefit in this case.

When a revision is made 'live'/default, is it definitely never associated with a workspace at that point (except possibly the now-defunct 'live' workspace?) - if so this might be OK, but if not then deleting previously-default revisions feels really destructive. I realise this code is already there but it'd be good to open a spin-off issue to discuss more yeah.

Note that in #36 I removed the code which could delete default revisions, so we are only deleting pending deletions now. Also opened #3077202: Revaluate the deletion of pending revisions when a workspace is deleted to discuss this further.

The patch wasn't applying because I forgot to roll it with --binary :)

catch’s picture

Note that in #36 I removed the code which could delete default revisions, so we are only deleting pending deletions now.

This brings up another question for me which is perhaps more relevant to the patch.

When we only stored workspace_association entities, and delete a workspace when it's published, the workspace association entities get deleted too. If you later delete the workspace itself, there are no associations, so the revisions won't get deleted.

But now, the workspace associations remain after the workspace is published, so if you delete an old, published workspace, then those revisions (which are the 'source' revisions for published content, not abandoned drafts) will get deleted. Or if that's not a correct summary what exactly does happen now?

amateescu’s picture

Very good question! And you're right that with this patch, the "old" revisions that belong to a workspace that has been published and then deleted will be deleted as well.

I see two (quick) options:

- add another workspace_association_revision index table, which would store all the workspace-specific revisions that haven't been published yet
- remove the code which deletes old revisions and deal with the "delete than recreate the same workspce ID" in #3077202: Revaluate the deletion of pending revisions when a workspace is deleted.

catch’s picture

Status: Reviewed & tested by the community » Needs work

I think I prefer option #2. It should be possible to implement a retrospective revision purge logic if we eventually want to, but we can't bring data back. Marking needs work for that.

amateescu’s picture

I started doing that but I found that there's quite a bit of code, tests and UI assumptions/constraints around the fact that we delete pending revisions and that it shouldn't be possible to create a workspace with the same ID as a deleted one, so I think it's safer for now to choose another approach: only delete pending revisions with an ID that's higher than the default revision ID. This way, we can never delete 'source' revisions for published content, but only pending 'draft' revisions.

This approach is possible at the moment because the EntityWorkspaceConflict constraint prevents editing an entity in any other workspace (or Live) if it has a pending draft revision in one workspace. In other words: if an entity has been edited in a workspace, it can only get to a default revision state if that workspace is published. This assumption will probably change when we get the conflict resolution API and the ability to merge workspaces, but in the meantime we can discuss all this in depth in #3077202: Revaluate the deletion of pending revisions when a workspace is deleted.

Here's a test-only patch which shows the problem described in #39 with the patch from #38, and one that implements the proposal from this comment.

amateescu’s picture

Sigh, forgot --binary again :/

The last submitted patch, 43: 3062434-43-test-only.patch, failed testing. View results

amateescu’s picture

amateescu’s picture

dixon_’s picture

Status: Needs review » Reviewed & tested by the community

All points from previous reviews have been addressed. And to me the patch and the interdiff looks good as well!

I'm not too opinionated on how/if we delete revisions when a workspace is deleted, so I'm happy to discuss other solutions. But I like the current approach in the patch of only deleting the forward revisions when the workspace itself is deleted. It feels like a clean solution aligned with what I think most users would expect when deleting a workspace.

  • catch committed 17bd9ce on 8.8.x
    Issue #3062434 by amateescu, pmelab, blazey, catch, Sam152, Leksat:...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Deleting just the forward revisions seems reasonable here and we can discuss further changes in the follow-up.

Committed 17bd9ce and pushed to 8.8.x. Thanks!

mikelutz’s picture

Status: Fixed » Needs work

Looks like this broke PostGre on HEAD. https://www.drupal.org/pift-ci-job/1410286

  • catch committed ade7b95 on 8.8.x
    Revert "Issue #3062434 by amateescu, pmelab, blazey, catch, Sam152,...
catch’s picture

Status: Needs work » Needs review

Reverted for now. Will kick off postgres and sqlite test runs here.

amateescu’s picture

amateescu’s picture

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

All green, back to RTBC :)

  • catch committed 6caa28c on 8.8.x
    Issue #3062434 by amateescu, pmelab, blazey, catch, Sam152, Leksat:...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed 6caa28c and pushed to 8.8.x. Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

xjm’s picture

Status: Closed (fixed) » Needs work
Issue tags: +Needs change record

This probably needs at least a CR.

amateescu’s picture

Status: Needs work » Fixed
Issue tags: -Needs change record +WI critical
jibran’s picture

+++ b/core/modules/workspaces/src/EntityTypeInfo.php
@@ -84,4 +91,30 @@ public function fieldInfoAlter(&$definitions) {
+      $fields[$field_name] = BaseFieldDefinition::create('entity_reference')
+        ->setLabel(new TranslatableMarkup('Workspace'))
+        ->setDescription(new TranslatableMarkup('Indicates the workspace that this revision belongs to.'))
+        ->setSetting('target_type', 'workspace')
+        ->setInternal(TRUE)
+        ->setTranslatable(FALSE)
+        ->setRevisionable(TRUE);

+++ b/core/modules/workspaces/workspaces.install
@@ -61,3 +84,83 @@ function workspaces_install() {
+      $field_storage = BaseFieldDefinition::create('entity_reference')
+        ->setLabel(t('Workspace'))
+        ->setDescription(t('Indicates the workspace that this revision belongs to.'))
+        ->setSetting('target_type', 'workspace')
+        ->setInternal(TRUE)
+        ->setTranslatable(FALSE)
+        ->setRevisionable(TRUE);

I was wondering should we enforce max_length here as workflow ID has ->setSetting('max_length', 128) but the question is can we enforce lenght on ER field target_id field?

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

hchonov’s picture

I was wondering should we enforce max_length here as workflow ID has ->setSetting('max_length', 128) but the question is can we enforce lenght on ER field target_id field?

We limit the length on the target_id property to 255 by default or to 32 when referencing a bundle entity type. Instead of hardcoding the value we could retrieve it from the target type ID definition. This will be not workspaces specific then. Could you please open an issue for this?

andypost’s picture

+++ b/core/modules/workspaces/workspaces.install
@@ -32,6 +34,27 @@ function workspaces_requirements($phase) {
+ * Implements hook_module_preinstall().
...
+function workspaces_module_preinstall($module) {
+  if ($module !== 'workspaces') {
+    return;

This hook should be moved to .module file so this condition is useless, faced in #3118155-20: Improve hook_module_preinstall & hook_module_preuninstall to include recommendations for config import and pass an $is_syncing argument