Lazily create draft revisions from valid auto-saved states on demand rather than on save or only on schedule

Created on 20 October 2024, 2 months ago

Overview

In 🌱 Research: Possible backend implementations of auto-save, drafts, and publishing Active , I think we're converging on the following goals:

  • Ideally, we want the latest valid auto-saved state to behave as though it's a real draft revision. For example:
    • We want the content author or someone else with appropriate permissions to be able to view the corresponding workspace outside of XB and see the content from that latest valid auto-saved state just like what would happen for a real draft revision.
    • We want the entity/field storage's hasData() method to return TRUE if there's corresponding data in a valid auto-saved state just as it would if it that state were in a real draft revision, so as to prevent field configuration changes that would break that data.
    • And so on for all behaviors of draft revisions.
  • We want auto-saves to occur as frequently as possible, ideally within a few seconds or less of a content change getting made. People are increasingly used to this kind of "always saved" user experience from Google Docs and various other modern apps.
  • We don't want to retain a revision for every past auto-saved state. We still need to decide whether to only retain the latest state, or whether to retain some reasonable set of snapshots. For example, #3481006-5: Implement time travel (undo/redo/revert) of auto-saved states across devices and users β†’ proposes log-spaced snapshots. But regardless, the idea is we only retain a small fraction of auto-saved states.
  • We don't want to incur the performance overhead of creating real revisions and deleting them a little while (e.g., a few seconds or a few minutes) later, without them having provided any value to anyone during their short lifetime.

Proposed resolution

A few ideas have been discussed previously, such as creating a real draft revision every once in a while (such as once every X auto-saves or once every Y minutes). However, this wouldn't result in exactly the behavior that's desired where valid auto-saved states are indistinguishable to the user from real draft revisions. For example, someone might view a workspace and see content that's a few minutes behind the actual latest auto-saved state. This would result in a system that's hard to reason about. One would have to know the difference between auto-saved states and real revisions to make sense of what's happening.

Another idea is to try to implement for auto-saved states all of the desired behaviors that real draft revisions have. For example, when someone is viewing a workspace, to intercept all relevant entity loads and override it with what's in the auto-saved state. This seems like it would be difficult to implement and end up being fragile. There would probably be a constant game of whack-a-mole trying to identify all of the places that would need to get shimmed in this way.

Since the above ideas all have their shortfalls, instead I recommend an on-demand revision creation approach. The idea is to only convert auto-saved states to real draft revisions when someone goes looking for those revisions. The exact technical approach would still need to be figured out, but for example, perhaps we can implement hook_query_entity_query_alter(), inspect the query to determine if it's looking for draft revisions, and if so, then create them from the auto-saved states. Then when that query proceeds it will find those draft revisions just as though they've been there the whole time. This would result in:

  • When someone is editing in XB, but no one else is viewing the workspace, then we're saving auto-saved states but not creating real revisions.
  • But when someone (whether that's the editor themself wanting to preview what their work is looking like when viewed outside of XB or whether it's a different person) goes to view the workspace or somewhere else where draft revisions are queried, then those revisions get created on-demand. But when we create those revisions on-demand we don't need to create all of them, only the latest auto-saved state or a log-spaced set of snapshots or some other small fraction, depending on what we decide in ✨ Implement time travel (undo/redo/revert) of auto-saved states across devices and users Active .

I think this would result in a simplified mental model both for the content editors and for our process of designing the desired XB UX, since we can start thinking of it and discussing it as "just" draft revisions, with the details of whether/when the revisions actually get created being an encapsulated implementation detail that doesn't leak into the UX.

There would still continue to be the distinction between valid auto-saved states and invalid ones. Where invalid ones don't get transferred to real revisions, and then everything that results from that, but I think that's okay, since the UX can convey whether an auto-saved state has validation errors and what the impact of that it.

User interface changes

✨ Feature request
Status

Active

Version

0.0

Component

Page builder

Created by

πŸ‡ΊπŸ‡ΈUnited States effulgentsia

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @effulgentsia
  • πŸ‡¬πŸ‡§United Kingdom catch

    The idea is to only convert auto-saved states to real draft revisions when someone goes looking for those revisions.

    This is very sneaky and I like it.

  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA

    UPDATE: my comment below goes on the assumption that we would using hook_entity_query_alter but since @effulgentsia mentioned hasData() we would need to use something like hook_query_alter so maybe this won't be done on the entity query level but the lower level which probably means there would be less edge cases.

    Original comment

    By `hook_query_entity_query_alter` do you mean `hook_entity_query_alter`?

    ?

    Does that get triggered for the node x when viewing node/X? I test but didn't seem to for me? Does it get triggered for views? I would have thought so but in debugging it didn't seems to(I did see some hits for that hook).

    Could we reliably use just entity query altering? Would we have to implement hook_entity_preload and make sure ours runs after `workspaces_entity_preload`?

    Would we have to implement `hook_views_query_alter` also? Since workspaces `\Drupal\workspaces\ViewsQueryAlter::alterQuery` seems to have. Otherwise the filters would not work correctly on a View, right?

    I like the idea in theory I like the idea but I wonder if we end playing whack-a-mole to have all the places we need lazy load our entities before.

    but maybe looking at all the hooks in workspaces.module all the works has already been done for a lot of these problem because Workspaces needs for instance make sure the right revision is used in workspaces_entity_preload, so in that case we would also need to check if there is an auto-save state.

  • πŸ‡ΊπŸ‡ΈUnited States effulgentsia

    By `hook_query_entity_query_alter` do you mean `hook_entity_query_alter`?

    I think I mean the former. Or in other words, hook_query_TAG_alter(), where entity_query is the TAG.

    Just to be clear though, I'm not suggesting to actually alter the query. I'm suggesting instead that before the query is executed, that we invoke the code to save auto-saved states into revisions, and then let the original query proceed as normal.

    Not sure yet about how much that will cover vs. needing additional hooks to also trigger the revision creations.

  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA
    1. Just to be clear though, I'm not suggesting to actually alter the query. I'm suggesting instead that before the query is executed, that we invoke the code to save auto-saved states into revisions, and then let the original query proceed as normal.

      '
      Yep I got that. I think we would also have to do this on hook_entity_preload() has that appears to not result in a query uses the `entity_query` Instead it uses this $query->addTag($this->entityTypeId . '_load_multiple');.

    2. I think we could do first try at this that implements hook_entity_preload(), hook_views_query_alterand hook_query_TAG_alter we know might miss some edge cases where the system query the tables on a lower level but then work from there.

      We could keep of track of the ids of the entities that have auto-save states in the current workspace and if a query might return those entities then when convert the auto-saves to real revisions. We could see how the performance and then make the system better if need be. For example we could determine which bundles currently have auto-save states and inspect queries to see if they filter by bundle and will not return the bundles that auto-saves and then we know we don't need to do any conversions

    3. Since the above ideas all have their shortfalls, instead I recommend an on-demand revision creation approach

      (emphasis mine)
      While I see the benefit of the lazy saving approach I don't see how it can replace saving revisions every once in a while.

      For instance if 1 user is working on page in XB for many hours and nobody else is viewing the workspace then we would never make a revision of that page and then the history would be lost. I guess that won't be true if ✨ Implement time travel (undo/redo/revert) of auto-saved states across devices and users Active is done without relying revisions at all(I am not convinced of that, are the any UI mock-ups for going through hundreds or thousands of auto-save states?)

      So the this lazy-saving has to be an add-on functionality to solve the problem of seeing the workspace with the very latest XB changes, it doesn't help keeping a history, at least it can't be relied on for that

Production build 0.71.5 2024