Problem/Motivation

As pointed out in #2784921-112: Add Workspaces experimental module point 1, having a Live (default) workspace entity is not desirable. It's also problematic when uninstalling the Workspace module, as can be seen in #3059824: TypeError: Argument 1 passed to Drupal\workspaces\WorkspaceListBuilder::buildRow() must implement interface Drupal\Core\Entity\EntityInterface, null given.

Proposed resolution

Remove the Live (default) workspace and change the concept model for switching to the Live version of the site by switching out of the current workspace.

Remaining tasks

Reviews.

User interface changes

The workspace switcher block does not have Live as an option anymore, and a Switch to Live action button has been added instead.

Before - in the Live version of the site

Before - in the Stage workspace

After - in the Live version of the site

After - in the Stage workspace

API changes

API additions:

- three new methods on WorkspaceManagerInterface: hasActiveWorkspace(), switchToLive() and executeOutsideWorkspace().
- one new method on WorkspaceNegotiatorInterface: unsetActiveWorkspace()

Data model changes

There is no 'live' workspace entity anymore.

Release notes snippet

There is no concept of a "Live" (default) workspace anymore. Switching to the live version of the site is now accomplished by switching out of the current workspace.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

amateescu created an issue. See original summary.

amateescu’s picture

FileSize
3.1 KB

I tried to do it with the patch attached, but it's really not that simple because loading entities also relies on db queries on various code paths, and the number of hacks needed to have it available during db or entity queries would go through the roof :/

amateescu’s picture

Project: Workspace » Drupal core
Version: 8.x-2.x-dev » 8.6.x-dev
Component: Code » workspace.module
Fabianx’s picture

I think per the comment:

This not only created on demand, it is a memory-only representation of the default workspace, meaning that it's not saved to a storage anywhere, so there's no chance of conflicting IDs.

This can be closed now as fixed or not?

amateescu’s picture

The context of that comment was about the Live workspace returned by \Drupal\workspace\Negotiator\DefaultWorkspaceNegotiator. However, that in-memory "live" workspace is only returned when we don't have its in-storage representation. For example when we want to uninstall the module, we need to remove all the workspace entities from storage, including the Live one.

We currently still create a Live workspace in storage, see workspace_install().

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

amateescu’s picture

Component: workspace.module » workspaces.module

Fix component following module rename.

Sam152’s picture

The only other approach I can see for this is to not have a live workspace entity at all. From a mental model point of view, instead of switching to "Live" you would essentially switch out of "Stage". That may also clean up some of WorkspaceInterface, for example publish makes no sense for the live workspace, and isDefaultWorkspace would become irrelevant.

This seems like a pretty big shift though, so I suppose the question should be: has there been any kind of significant issue or risk with the installed live workspace disappearing or being deleted? How recoverable is workspaces if that is deleted?

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

amateescu’s picture

Title: Figure out if we can provide a hardcoded Live workspace instead of creating it during installation » Provide a hardcoded Live workspace instead of creating it during installation
Category: Task » Bug report
Status: Active » Needs work

I just closed an issue which shows that the current situation is actually a bug: #3059824: TypeError: Argument 1 passed to Drupal\workspaces\WorkspaceListBuilder::buildRow() must implement interface Drupal\Core\Entity\EntityInterface, null given, so re-classifying and adding credit to @kunalkursija for creating that issue with very helpful steps to reproduce.

amateescu’s picture

amateescu’s picture

Title: Provide a hardcoded Live workspace instead of creating it during installation » Remove the concept of a 'live' default workspace
Status: Needs work » Needs review
FileSize
53.91 KB

@Sam152, your idea from #8 makes perfect sense, and I think it's exactly what we should do :)

amateescu’s picture

Added a change record https://www.drupal.org/node/3071527 and removed a few unneeded redirects to the front page.

Manuel Garcia’s picture

Just a quick review / feelings after a first read of the patch:

+++ b/core/modules/workspaces/src/Form/WorkspaceDeactivateForm.php
@@ -0,0 +1,49 @@
+class WorkspaceDeactivateForm extends WorkspaceActivateForm implements WorkspaceFormInterface {

While it's true that this is what submitting this form does, I cant stop thinking about the difference is between deactivating a workspace vs switching to live.

Deactivating the workspace is what is actually happening in the system, in the code.

Switching to live is what the user actually wants to do, what the action means for the user anyway.

So I think naming everything as the user action would make sense to me. When someone tries to look for the form's code, it would be easier to find if it was called SwitchToLiveWorkspaceForm or something along those lines, but as I said, an argument could be made for keeping it like this - thoughts?

+++ b/core/modules/workspaces/src/Form/WorkspaceDeactivateForm.php
@@ -0,0 +1,49 @@
+/**
+ * Provides a form that deactivates a workspace.
+ */

This form description would also change if we decide to change the class name.

If not, I think Provides a form to deactivate a workspace. could be clearer.

amateescu’s picture

@Manuel Garcia, thanks for taking a look!

The funny thing is that I started this patch with a SwitchToLiveForm as you suggested, but went with the "deactivate" approach in the end because I thought it would be easier to take advantage of the workspace.view entity access operation.

However, after thinking it through a bit, I think we need a very simple "has active workspace" access check for that form, so that's what I implemented in this new patch.

amateescu’s picture

Fixed a copy/pasta mistake :/

amateescu’s picture

Issue summary: View changes
FileSize
10.76 KB
11.05 KB
11.04 KB
8.22 KB

Wrote a proper issue summary.

Manuel Garcia’s picture

diff --git a/core/modules/jsonapi/jsonapi.module b/core/modules/jsonapi/jsonapi.module
index 0e76517708..d0a58ecf9c 100644
--- a/core/modules/jsonapi/jsonapi.module
+++ b/core/modules/jsonapi/jsonapi.module
@@ -321,8 +321,6 @@ function jsonapi_jsonapi_user_filter_access(EntityTypeInterface $entity_type, Ac
  */
 function jsonapi_jsonapi_workspace_filter_access(EntityTypeInterface $entity_type, $published, $owner, AccountInterface $account) {
   // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess()
-  // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
-  // (isDefaultWorkspace()), so this does not have to.
   return ([
     JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'),
     JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'),
diff --git a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php
index 5c6c9c8d4e..dd3b0a8e3d 100644
--- a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php
+++ b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php
@@ -15,7 +15,6 @@
 use Drupal\jsonapi\Query\EntityCondition;
 use Drupal\jsonapi\Query\EntityConditionGroup;
 use Drupal\jsonapi\Query\Filter;
-use Drupal\workspaces\WorkspaceInterface;
 
 /**
  * Adds sufficient access control to collection queries.
@@ -306,20 +305,6 @@ protected static function getAccessCondition($entity_type_id, CacheableMetadata
         // @see \Drupal\user\UserAccessControlHandler::checkAccess()
         $specific_condition = new EntityCondition('uid', '0', '!=');
         break;
-
-      case 'workspace':
-        // The default workspace is always viewable, no matter what, so if
-        // the generic condition prevents that, add an OR.
-        // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess()
-        if ($generic_condition) {
-          $specific_condition = new EntityConditionGroup('OR', [
-            $generic_condition,
-            new EntityCondition('id', WorkspaceInterface::DEFAULT_WORKSPACE),
-          ]);
-          // The generic condition is now part of the specific condition.
-          $generic_condition = NULL;
-        }
-        break;
     }
 
     // Return a combined condition.

Yay!

+++ b/core/modules/workspaces/workspaces.post_update.php
@@ -10,3 +10,11 @@
+
+/**
+ * Remove the default workspace.
+ */
+function workspaces_post_update_remove_default_workspace() {
+  $workspace = \Drupal::entityTypeManager()->getStorage('workspace')->load('live');
+  $workspace->delete();
+}

While it makes sense to clean up, I can't shake this question out of my head: can this be dangerous for sites live using this workspace?

Also, should we bother checking that we indeed loaded the live workspace before trying to delete it?

amateescu’s picture

While it makes sense to clean up, I can't shake this question out of my head: can this be dangerous for sites live using this workspace?

I don't think there's a problem with deleting this workspace because users who have it set in their session by the workspace negotiator, on the next request when we try to load it from the workspace storage we won't get any result, and as a result the negotiator will return NULL and the workspace manager will fall back to the live version of site, which is exactly the expected outcome for those users.

Also, should we bother checking that we indeed loaded the live workspace before trying to delete it?

Good point, done :)

Manuel Garcia’s picture

Thanks @amateescu for the explanation, makes sense.

Patch is looking pretty good to me, just a couple more questions:

  1. +++ b/core/modules/workspaces/workspaces.module
    @@ -198,9 +199,9 @@ function workspaces_toolbar() {
    diff --git a/core/modules/workspaces/workspaces.post_update.php b/core/modules/workspaces/workspaces.post_update.php
    
    diff --git a/core/modules/workspaces/workspaces.post_update.php b/core/modules/workspaces/workspaces.post_update.php
    index 2ca0ced399..0df5add2cb 100644
    
    index 2ca0ced399..0df5add2cb 100644
    --- a/core/modules/workspaces/workspaces.post_update.php
    
    --- a/core/modules/workspaces/workspaces.post_update.php
    +++ b/core/modules/workspaces/workspaces.post_update.php
    
    +++ b/core/modules/workspaces/workspaces.post_update.php
    +++ b/core/modules/workspaces/workspaces.post_update.php
    @@ -10,3 +10,12 @@
    
    @@ -10,3 +10,12 @@
      */
     function workspaces_post_update_access_clear_caches() {
     }
    +
    +/**
    + * Remove the default workspace.
    + */
    +function workspaces_post_update_remove_default_workspace() {
    +  if ($workspace = \Drupal::entityTypeManager()->getStorage('workspace')->load('live')) {
    +    $workspace->delete();
    +  }
    +}
    

    Do we need an update path test for this? My guess is it isn’t necessary, since we are just deleting one entity, but well doesn’t hurt to ask I suppose.

  2. Should we document the changes to interfaces on the CR in case anyone is making use of them?
amateescu’s picture

Re #21:

1. I don't think we need an update path test because the update function either removes the live workspace if it exists, or it doesn't :) There's nothing really to test..

2. Good point, added two sections to the CR: "API additions" and "Deprecations".

Manuel Garcia’s picture

Re #22:
1. Yup, I can't think of anything else to test either by looking at the post_update function. The only fear I had with it was explained on #22 #20.

2. Thanks, had a read and the new sections are clear.

+++ b/core/modules/workspaces/src/Negotiator/SessionWorkspaceNegotiator.php
--- a/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php
+++ b/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php

+++ b/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php
+++ b/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php
@@ -47,4 +47,9 @@ public function getActiveWorkspace(Request $request);

@@ -47,4 +47,9 @@ public function getActiveWorkspace(Request $request);
    */
   public function setActiveWorkspace(WorkspaceInterface $workspace);
 
+  /**
+   * Unsets the negotiated workspace.
+   */
+  public function unsetActiveWorkspace();
+
 }

Does this need documenting in the CR?

amateescu’s picture

Issue summary: View changes

Of course! Added that new method to the IS and the change record :)

Manuel Garcia’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @amateescu - we now have a clear patch, a detailed CR and IS, so I'm RTBCing since I can't think of anything else to improve or that should be added.

amateescu’s picture

Marking as a stable blocker.

catch’s picture

Status: Reviewed & tested by the community » Needs review

Overall looks like a good simplification, couple of small things below:

  1. +++ b/core/modules/workspaces/src/Access/ActiveWorkspaceCheck.php
    @@ -0,0 +1,50 @@
    +    $required_value = filter_var($route->getRequirement('_has_active_workspace'), FILTER_VALIDATE_BOOLEAN);
    

    Minor but do we really need to validate what's in routing yaml? And if so why not assert() to tell module developers they did something wrong?

  2. +++ b/core/modules/workspaces/src/WorkspaceManager.php
    @@ -247,11 +273,23 @@ public function executeInWorkspace($workspace_id, callable $function) {
    +   * {@inheritdoc}
    +   */
    +  public function executeOutsideWorkspace(callable $function) {
    +    $previous_active_workspace = $this->getActiveWorkspace();
    +    $this->doSwitchWorkspace(NULL);
    

    Minor again, should $function be $callable?

  3. +++ b/core/modules/workspaces/src/WorkspaceManagerInterface.php
    @@ -28,6 +28,14 @@ public function isEntityTypeSupported(EntityTypeInterface $entity_type);
     
    +  /**
    +   * Determines whether a workspace is active in the current request.
    +   *
    +   * @return bool
    +   *   TRUE if there is an active workspace, FALSE otherwise.
    +   */
    +  public function hasActiveWorkspace();
    +
       /**
    

The naming seems a bit ambivalent in that it could also mean 'is there an active workspace on the site', but I don't have a better idea.

However I think the return could be:

TRUE if a workspace is active, FALSE otherwise.

Only other idea would be reversing it to something like isLive()?

amateescu’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
57.55 KB
621 bytes

Yup, this special Live workspace was bugging me since you and Fabian pointed it out in the reviews for the initial workspaces patch :)

Re #27:

  1. I copied this approach from \Drupal\user\Access\LoginStatusCheck::access() and I think the added benefit of filter_var() is that it also converts the variable to an actual boolean value so we can do a strict type check below.

  2. I used $function to match the existing executeInWorkspace() method, but I don't have any strong preference about it. Just that we should rename both at the same time :)

  3. I like the hasActiveWorkspace() name because it pairs very well with the get|setActiveWorkspace() methods, and because it denotes that the code within a condition needs to cater for an "exceptional" case, when the site is not in the usual/default state.

    However, if other folks have a different opinion or suggestion, I'm happy to go with the majority preference.

amateescu’s picture

Did a self-review and found a couple more things that should be cleaned up.

  • catch committed d881bd0 on 8.8.x
    Issue #2935780 by amateescu, Manuel Garcia, Fabianx, Sam152,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

#28 1, 2, and 3 are all fair enough.

Committed d881bd0 and pushed to 8.8.x. Thanks!

Status: Fixed » Closed (fixed)

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

rodrigoaguilera’s picture

The change record was still in draft state so I published it