Overview

In #3500042: Auto-save code components will allow Code Component that are edited in the UI to have an auto-saved state.

This means the edits to them should not affect the live site if they have already been placed. See also #3500043: Publishing code components

Since we are not using Workspaces in #3455753: Milestone 0.2.0: Early preview we can't do a real config entity save until we publish the changes in the Code Component because otherwise that would affect the live site.

As far as I know this is the only case where when are rendering Component inside XB (which itself is using an auto-save state) we need to consider that Component will have an auto-save state that should override the config. SDC and Block components don't have this dynamic.

⚠️ This assumes that the auto-saved code component is working. If it doesn't, then it'd be the client-side equivalent of #3485878: Server-rendered component instances should NEVER result in a user-facing error, should fall back to a meaningful error instead (+ log), which needs its own issue. Raised this at #3499919-20: [Meta] Plan for in-browser code components.

Proposed resolution

When rendering a Code Component inside XB it should use the auto save if there is one. When we render the code component in regular entity render it should not use the auto-save state

When thinking of a solution we should keep in the mind that after #3455753: Milestone 0.2.0: Early preview when we can use Workspace we might not need this functionality because we may be able to rely on a real config save if using Workspaces Extras module which has a sub-module Workspaces Config

Possible solutions

  1. Code Components implementation of \Drupal\experience_builder\ComponentSource\ComponentSourceInterface::renderComponent could check if the current route is experience_builder.api.preview and if it is rendering using the auto-save state

    this might be we could encapsulate the logic in CodeComponent as this might be the only part of the system. It also might be easy to remove if we determine using Workspaces Config is better for this when it is available

  2. Per #7 and #8:
    class XbPreviewConfigOverride implements ConfigFactoryOverrideInterface {
    
      /**
       * {@inheritdoc}
       */
      public function loadOverrides($names) {
        if (!$this->routeMatch->getRouteObject()?->getOption('_xb_use_template_draft')) {
          return;
        }
    
        $overrides = [];
        foreach ($names as $config_name) {
          // Check if $config_name is for a "code component", otherwise continue.
    
          // Use \Drupal\experience_builder\AutoSave\AutoSaveManager::getAutoSaveData() to get that data.
    
          // Assign to overrides.
        }
    
        return $overrides;
      }
    
      /**
       * {@inheritdoc}
       */
      public function getCacheSuffix() {
        return 'XbPreview';
      }
    
      /**
       * {@inheritdoc}
       */
      public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
        return NULL;
      }
    
      /**
       * {@inheritdoc}
       */
      public function getCacheableMetadata($name) {
        $metadata = new CacheableMetadata();
        $metadata
          ->setCacheContexts(['route'])
          ->setCacheTags([AutoSaveManager::CACHE_TAG])
          ->setCacheMaxAge(0);
        return $metadata;
      }
    
    }
    

User interface changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

tedbow created an issue. See original summary.

tedbow’s picture

Title: Code Components should reflect render with their auto-saved state(if any) when placed in XB » Code Components should render with their auto-saved state(if any) when rendered in the XB UI
Priority: Normal » Major
tedbow’s picture

I bumped it to major only because I think we should decide sooner rather than later if this going to need changes to the system outside of Code Components. I made possible way we could do this that might just apply to the Code Components code

effulgentsia’s picture

"Possible solution #1" seems good to me.

wim leers’s picture

That solution might stop working once #3491701: [later phase] ApiLayoutController must use the previewed route's controller, and override canonical content entity routes' received entity object happens. But we'll cross that bridge when we arrive there.

More short-term concern: no ComponentSource plugin should be aware of the global/request context, which this would violate. But as long as it's documented with an explicit caveat and bound to change prior to 1.0, then I'm fine with it as an interim step.

Long-term, I think this ought to be implemented as \Drupal\Core\Config\ConfigFactoryOverrideInterface. That's the mechanism that Drupal core provides for context-dependent overrides of stored configuration. Would that even be more work to implement? Because that'd set us up better for the future AFAICT. 😇

tedbow’s picture

Long-term, I think this ought to be implemented as \Drupal\Core\Config\ConfigFactoryOverrideInterface.

Are we sure long term we will need this at all if rely on wse_config?

We have requirement out auto-save js components but this seem like more a UX requirement than a specification for how this would handled as far as backend storage. Could we just do real config saves, to back up work without user action and just let wse_config be responsible for making sure that doesn't pollute the live site? (relating #3493461: Identify roadblocks to staging config in Workspaces (e.g., via wse_config) to this issue for that reason)

I think in #3475672: Research: Possible backend implementations of auto-save, drafts, and publishing the 2 main reasons we decided we needed some auto-save mechanism is that not a real entity save was

  1. Bypassing Validation: saving work in progress that is not valid
  2. Avoid the saving overhead so we could save frequently

For 1 we could still use a simple auto-save if the validation does not pass but a real entity save if it does. In the case of saving content entities we are not using the auto-save state, which might not be valid, to then render the content entity outside of the XB UI itself or to render that entity in the XB UI for another entity besides itself. Would that be the case for the JS components? What would even happen if we tried to a render JS component config entity overridden by ConfigFactoryOverrideInterface in XB UI for another entity, say Page content entity, if the auto-save state is not valid?

For 2 do we have the same overhead concerns for JS Component config entities as we do for Content entities. If XB is going to be the default way some sites add new and edit existing Content and as well as JS Component, it still seems likely that sites will not be adding as many JS components as they would content entities.

wim leers’s picture

Are we sure long term we will need this at all if rely on wse_config?

You're right: in principle not, because we'd just rely on wse_config's ConfigFactoryOverrideInterface. But there's still much uncertainty AFAICT on all things "config in workspaces", so consider it just for the worst-case scenario, where wse_config does not get us as far as we hope. (@traviscarden is working this quarter on writing the test coverage to help raise the confidence level on that front!)

We have requirement out auto-save js components but this seem like more a UX requirement than a specification for how this would handled as far as backend storage

Is this referring to the description in the issue summary?

Everything else you wrote is actually closely related to what I just surfaced in our call, and which I captured at #3499931-6: HTTP API for code component config entities. I'll link to your excellent #6 from over there 👍

effulgentsia’s picture

Status: Postponed (maintainer needs more info) » Active

I agree that the scope of this issue should not be to solve it for the long term; the long term is workspaces.

For the short term, I think either the ComponentSource plugin inspecting the request and loading from the autosave record instead of the config, or implementing a ConfigFactoryOverrideInterface service that inspects the request and returns overridden config so that the ComponentSource plugin can stay in its lane, is a great option. The latter is probably better in terms of purity, so if it's the same amount of work to do either, might as well do the nicer thing even if it's short lived. "Short lived" could still be a few months, depending on how long it takes us to do all the work needed to integrate Workspaces into XB. If the config override is a lot of extra work or introduces various other headaches, then it's probably not worth it, and adding a bit of ugly code into the ComponentSource plugin isn't so bad.

effulgentsia’s picture

Issue tags: +sprint candidate
balintbrews’s picture

StatusFileSize
new174.77 KB

Asked by Wim, I'm attaching the diagram we've been using to discuss this problem space.

XB JS component states

wim leers’s picture

Issue summary: View changes

⚠️ This assumes that the auto-saved code component is working. If it doesn't, then it'd be the client-side equivalent of #3485878: Server-rendered component instances should NEVER result in a user-facing error, should fall back to a meaningful error instead (+ log), which needs its own issue. Raised this at #3499919-20: [Meta] Plan for in-browser code components.

wim leers’s picture

#6: FYI, the test coverage for wse_config has started over at #3505900: Add test coverage for staging simple config.

#7: AFAICT wse_config does not actually use ConfigFactoryOverrideInterface: no config.factory.override-tagged service in https://git.drupalcode.org/project/wse/-/blob/2.0.x/modules/wse_config/w..., nor in https://git.drupalcode.org/project/wse/-/blob/2.0.x/modules/wse_config/s.... Looks like it was never discussed either: https://www.drupal.org/project/issues/wse?text=ConfigFactoryOverrideInte... and https://www.drupal.org/project/issues/wse?text=config.factory.override&s... both find zero results.
That necessarily means it must do something much more heavy-handed instead.

Given my work almost a decade ago on PirateDayCacheabilityMetadataConfigOverride for #2524082: Config overrides should provide cacheability metadata, I'm 75% confident that what I added as option 2 to the issue summary will work fine. AFAICT we don't need \Drupal\Core\Config\ConfigFactoryOverrideBase.

nagwani’s picture

Issue tags: -sprint candidate +sprint
larowlan’s picture

Assigned: Unassigned » larowlan

Sounds like option 2 is the preferred approach, I'll start on that

larowlan’s picture

Assigned: larowlan » wim leers
Status: Active » Needs review

Implemented option 2

tedbow’s picture

Assigned: wim leers » larowlan
Status: Needs review » Needs work

The tests are failing because the experience_builder__autosave cache tag should be expected now in other tests

larowlan’s picture

Assigned: larowlan » Unassigned
Status: Needs work » Needs review

Tests are passing now

I added a test to address @tedbow's comment - but it appears it hasn't gone far enough

wim leers’s picture

Title: Code Components should render with their auto-saved state(if any) when rendered in the XB UI » Code Components should render with their auto-saved state (if any) when rendered in the XB UI
Assigned: Unassigned » tedbow
Status: Needs review » Reviewed & tested by the community

I think this is ready, with two exceptions:

AFAICT @tedbow's test concerns actually are already addressed: https://git.drupalcode.org/project/experience_builder/-/merge_requests/6.... @tedbow, can you double-check? 🙏

wim leers’s picture

The sort of mind-bending aspect of config overrides and cacheability bubbling, that actually proves @tedbow's concerns are tested: https://git.drupalcode.org/project/experience_builder/-/merge_requests/6...

tedbow’s picture

global-regions.cy.js e2e test failed but this appears random and it passes locally for me

  • tedbow committed 772db68c on 0.x authored by larowlan
    Issue #3500386 by larowlan, wim leers, tedbow, balintbrews, effulgentsia...
tedbow’s picture

Assigned: tedbow » Unassigned
Status: Reviewed & tested by the community » Fixed

👹 is in 🎉

larowlan’s picture

Status: Fixed » Needs work

Thought about this some more and we also need a way to have the Astro island load the draft JS and CSS

NW for that

Thanks for all the work overnight

wim leers’s picture

#24: D'oh! YES! 🙈

larowlan’s picture

Rough plan to address #24

* Emit an event from auto save manager
* Subscribe to event in \Drupal\experience_builder\EventSubscriber\AssetGenerator
* Either new method in \Drupal\experience_builder\AssetManager for autosave _ OR _ have the component return a different asset path if in preview in \Drupal\experience_builder\Entity\XbAssetInterface::getCssPath and \Drupal\experience_builder\Entity\XbAssetInterface::getJsPath

effulgentsia’s picture

I don't think we want the autosave (aka preview aka draft) JS and CSS files written to disk. It would mean a bunch of short-lived files that we'd need to purge at some point. What if we define a route for those to serve them dynamically? We'd need the path to contain the ID of the component in that case. And I think we don't need the hash, since I think we'd want the preview to just show whatever is latest in autosave storage even if that got updated between the time that the preview HTML is sent and when the browser makes the request to the asset.

It would be great if the preview asset URL was some/path/JS_COMPONENT_MACHINE_NAME. That would make a future issue with import maps nicer. For example, we'd be able to map @/components/ to some/path/ and code in one component that does import Button from '@/components/button' would work without needing every component to be listed separately in the import map. We don't need to add anything import map related to the scope here, I'm just giving context for why some/path/JS_COMPONENT_MACHINE_NAME is preferable to some/path/HASH.js, at least for the preview/autosave case. The current behavior of HASH.js written to disk is still good for the live site (real config save) case.

larowlan’s picture

Works for me 👍

larowlan’s picture

Assigned: Unassigned » larowlan

larowlan’s picture

Status: Needs work » Needs review

MR for using draft (autosave) version of the CSS/JS is up, implementing #27
Needed a fair bit of tinkering to get the astro island to support the #preview key, but that was why we added it originally.

f.mazeikis made their first commit to this issue’s fork.

f.mazeikis’s picture

Assigned: larowlan » f.mazeikis
f.mazeikis’s picture

Resolved merge conflict, tests passing and the code makes sense.
However, trying this out on local I see no draft CSS changes being applied to the actual preview. This seems to be regression, as few days ago at least saved CSS of the component would show in preview, but now neither saved nor draft CSS is being applied during preview. Looking into this.
False alarm, all good.

wim leers’s picture

Status: Needs review » Needs work

Needs work for #34 👍

In addition to that reason for changing the status: While working on #3505993: Code Components as Block Overrides, step 1, another oversight in addition to #24 became clear: \Drupal\experience_builder\Config\XbConfigOverrides should not only only apply to that route, but also only to users who have sufficient permissions: if they lack the administer code components permission, they should not be able to see draft (auto-saved) states of edited code components.
I think that can become a third MR here.

f.mazeikis’s picture

StatusFileSize
new29.69 MB

Actually, found an issue, posted comment on MR.
TL;DR: Until Code Component is not saved with _something_ in it's CSS field, preview doesn't include draft CSS changes.
Attaching screen recording for clarity.

wim leers’s picture

@f.mazeikis thought this MR (694) was ready to be merged, but I spotted several (small!) bugs: https://git.drupalcode.org/project/experience_builder/-/merge_requests/6.... One is quite interesting: the use of Cache-Control: private may result in the browser not actually using the latest auto-save draft, which would have been a pretty annoying front-end bug to track down! 😄

Pushed an (untested!) commit for the trickiest bit I'm contesting, but leaving the rest for @f.mazeikis to address.

I think #35 can become a separate MR, that can land separately, after !694 is merged.

effulgentsia’s picture

\Drupal\experience_builder\Config\XbConfigOverrides should not only only apply to that route, but also only to users who have sufficient permissions: if they lack the administer code components permission, they should not be able to see draft (auto-saved) states of edited code components.

I don't think that's correct. If they have permission to use the code component within XB's page builder (if we don't yet have granular permissions for this, this is currently the same as if they have permission to use XB's page builder at all), then they should see what their component instance looks like in their content, using the draft state of the JS and CSS.

wim leers’s picture

I don't think that's correct. If they have permission to use the code component within XB's page builder (if we don't yet have granular permissions for this, this is currently the same as if they have permission to use XB's page builder at all), then they should see what their component instance looks like in their content, using the draft state of the JS and CSS.

I'm referring not a Content Creator, but to an anonymous end user. We can handle that in #3508694: Permissions for XB config entity types 👍

larowlan’s picture

Assigned: f.mazeikis » wim leers
Status: Needs work » Needs review

The last open item here is how we communicate that a config entity is in preview state.
I originally had a flag on the config entity.
Wim pushed back on that and changed it to use cache max age of 0.
I don't agree with that change.
But I also see Wim's point about not putting it on the config entity because that communicates it is something you can set, when in fact its only a run-time flag.
I think we should look to how layout builder handles this in core - https://git.drupalcode.org/project/experience_builder/-/merge_requests/6...

tl;dr I think we should make buildRenderable have an explicit 'in preview' argument, and then use that to set a flag on the source plugin.
Then the source plugin can call take care of passing #preview TRUE along to the AstroIsland element.

This means we can also do this for the Block component source plugin and therefore respect the existing 'in preview' flag on block plugins and in the future layout plugins. I worked on the core issue to add this support for blocks and make use of it in client projects - it even filters through to block plugin templates.

I think that's a win-win.

I'll hold off implementing that until I get a +1 to that approach from Wim - assigning to him for a plus or minus one.

wim leers’s picture

Assigned: wim leers » larowlan
Status: Needs review » Needs work
larowlan’s picture

In relation to your interpretation

all entity loading remains unchanged compared to the state of 0.x prior to this issue — both content and config entities. This means removing XB's ConfigFactoryOverrideInterface implementation

If we remove that, then the source plugin will be responsible for loading the config entity from storage _or_ from auto save.
But we'd also need to change how AstroIsland worked (it too loads the entity). One approach could be instead of passing an ID for component ID, we'd have to pass the entity.
But I think this will get us into issues with render caching (we don't want the entity to be serialized in a render array).

So maybe that highlights a further issue with the AstroIsland element. Perhaps it is too tightly bound to the JavascriptComponent config entity. Perhaps it doesn't need to load the component. Looking at the process method, it uses the component to

a) get the component url
b) get the component label
c) attach the component library

All of these could be done from JsComponent::buildRenderable instead. This makes AstroIsland less bound to JavascriptComponent and means it could possibly be used by another source plugin.

And it means we can ditch the config override.

I'll push ahead on that basis.

larowlan’s picture

Assigned: larowlan » Unassigned
Status: Needs work » Needs review

Implemented #43 and it turned out pretty nice I think.

Back to NR

wim leers’s picture

Assigned: Unassigned » wim leers
wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs review » Reviewed & tested by the community

After a whole unexpected saga in https://git.drupalcode.org/project/experience_builder/-/merge_requests/6..., this is finally done!

  • wim leers committed 73f325bd on 0.x authored by larowlan
    Issue #3500386 by larowlan, wim leers, f.mazeikis, tedbow, balintbrews,...
wim leers’s picture

Status: Reviewed & tested by the community » Fixed
wim leers’s picture

nagwani’s picture

Issue tags: -sprint

Status: Fixed » Closed (fixed)

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