Fix Field cache for anonymous users using JS callback in Status formatter

Created on 30 March 2023, over 1 year ago
Updated 5 April 2024, 9 months ago

This is a followup from 🐛 Add Field cache for 'Currently Open/Closed' Status formatter Fixed .

Problem/Motivation

The cache invalidation on the Status value is not updating despite many efforts. It has been discussed in 🐛 Add Field cache for 'Currently Open/Closed' Status formatter Fixed , 🐛 Fix Field cache for anonymous users using Cron job in Status formatter Closed: won't fix , 🐛 Cron for cache handling breaks badly Fixed at least.

Steps to reproduce

* have all Drupal caching on
* set the closing time to be something that happens soon and let the time pass
* reload page. The status flag should change to "closed" but it doesn't. Neither for authenticated nor unauthenticated requests.

Proposed resolution

Keep the initial value, but update it dynamically using JS and a Controller which gives the current field value updated.

This is resolved in a similar way in core for DateTime formatter: 🐛 Allow TimestampFormatter to show as a fully cacheable time difference with JS Fixed

Remaining tasks

Clean up non-working code.

Feature request
Status

Fixed

Version

1.11

Component

Code - formatter

Created by

🇫🇮Finland jhuhta

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

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • Issue created by @jhuhta
  • 🇫🇮Finland jhuhta

    The patch is the same than my last one in 🐛 Add Field cache for 'Currently Open/Closed' Status formatter Fixed .

  • Status changed to Needs review over 1 year ago
  • 🇳🇱Netherlands johnv

    Thank you, I will review the patch.
    For reference, and finding the best solution:

    Alterna2993639tive 1: the current cron job. which is a poor work-around. Even when garbage collection works fine (in a cron job)
    Alternative 2: a JS call, like proposed patch.
    Alternative 3: The following Change Record describes that 'cacheability metadata' for 'normalized compute fields' is now handled properly: Computed fields now bubble cacheability metadata to serializer
    We could make the Open/Closed status a Computed Field, too.

    Reference: The following Change Record describes how the Time Formatter 'time ago' now also is a JS call: The default timestamp formatter has an option to show the date as a time difference

  • 🇳🇱Netherlands johnv

    As a maintainer, I am entering new grounds here.
    I stumbled upon Alternative 4: adding '#lazy_builder' to the StatusFormatter.
    Can you assess how your solution using JS compares with lazy building?
    (Your solution is similar to the recent core commit #2921810-247: Allow TimestampFormatter to show as a fully cacheable time difference with JS )

    Some references for '#lazy_builder':
    - https://www.drupal.org/docs/drupal-apis/render-api/auto-placeholdering#s...
    - https://www.hashbangcode.com/article/drupal-9-using-lazy-builders
    - https://www.qed42.com/insights/coe/drupal/lazy-builders-drupal-8-caching...

  • 🇫🇮Finland jhuhta

    When I originally developed this thing, I first took a look at lazy building. Its problem here is that the lazy built value will be eventually cached by page cache, and it won't be loaded for the anonymous end user anymore, so we would end up in the same situation.

    By reading the core commit, I got an idea of optimizing my code to avoid reloading the value from the backend, by duplicating the calculation in the JS end based on the data that could be sent as element attributes for instance. But that could make it quite complicated so I don't know if it's worth it.

  • 🇫🇮Finland jhuhta

    Sorry for the accidental title change, we we're editing this simultaneously.

  • 🇳🇱Netherlands johnv

    Indeed, lazy building an element will have the same problem as building an entire page but may be more performant, when only this small part is not cached at all.
    🐛 Fix field cache for anonymous users by disabling caching in Status formatter Fixed just disables render caching for anonymous users.

    Your JS might calculate from the given data set, but the calculation open/closed is complicated. (It may require copying the calculation from php to js?)

    Anyhow, let us postpone this until it seems that the above solution is not OK.

  • Status changed to Postponed over 1 year ago
  • 🇫🇷France O'Briat Nantes

    My 2c, either :
    - (simplest but overkill) add a max-age to your entity that could match the maximum delay that is acceptable: 1mn, 5mn, 15mn ?
    - (next simplest) modify status with pure js (no ajax callback), the script just reads the opening/closing time in html and update/toggle a class to the change the display.
    - (complex) add a cron that query all the office_hours field data, determine which entities had a status change and clear their cache atomically id by id.
    - (my favorite) mark entities with tags matching opening/closing hours, ex: opening at 8m and closing at 11:45am, opening at 2pm and closing at 6pm ["office_hours:update_on:08_00", "office_hours:update_on:11_45", "office_hours:update_on:14_00", "office_hours:update_on:18_00"], then create a cron (choose step 1mn, 5mn, 15mn, 30mn,...) that clear tags matching the current hour:minutes, ex: \Drupal::service('cache_tags.invalidator')->invalidateTags(["office_hours:update_on:11_45"]);

  • 🇳🇱Netherlands johnv

    @O'Briat,
    all your options have been evaluated and discussed, either in 🐛 Cron for cache handling breaks badly Fixed or in oth er'Fix field cache' issues.

    Problem is with anonymous users, not for authenticated users.
    - option 1 does not work. That is exaclty what we want to work around.
    - option 2 could do the trick, but provided js is too intrusive for the task to be done. Even the patch in this issue.
    - option 3 is committed, but gives problems, as discussed.
    - option 4 is evaluated, but how to find elegantly the tags to be invalidated?

    So current approach is to not cache for anonymous users if a status is displayed.

  • 🇫🇷France O'Briat Nantes

    For option 4, you don't have much choice, it is linked to the minimum chosen granularity of the field or the maximum duration during stale data are allowed to be return. I think 5min is a safe value.

    For each value (opening or closing) you add the tag "office_hours:update_on:HH_MM" to the entity, where MM is the next 5mn step and HH is just here to avoid to much cache invalidation. Also add a common tag "office_hours" (see below).

    The cron job should clear all matching tags since the last run:

    1. Check office_hours:cache_clear_datetime state variable => if never run or last run is very old (say 1 day) clear all tags "office_hours"
    2. If not, make diff with the last run datetime and clear all "steps", ex: now 16:22, last run 16:00 => clear office_hours:update_on::16_05, office_hours:update_on::16_10, office_hours:update_on::16_15, office_hours:update_on::16_20
    3. Set the state variable with the date+time of this cache clear
      \Drupal::state()->set('office_hours:cache_clear_datetime', \Drupal::service('datetime.time')->getRequestTime(););
      

    For the javascript solution, I propose a much simpler solution that not involve ajax call:
    Just always add the html code of the "closed status" in the twig with a css class that hide the content and a attribute that store open/close timestamps.
    The javascript runs on setInterval() and just checks the html tags with matching attributes, if there are out of range : toogle the css class.

    Just add proper seo/metadata attributes to not mess with search engine indexation.

  • 🇺🇸United States daddison

    I re-rolled the patch against 8.x-1.11.

  • 🇳🇱Netherlands johnv

    @daddison, did you test the latest version with 🐛 Fix field cache for anonymous users by disabling caching in Status formatter Fixed implemented?
    Can you share your use case where the status is outdated?

  • 🇺🇸United States daddison

    @johnv Yes, I did test with 🐛 Fix field cache for anonymous users by disabling caching in Status formatter Fixed implemented.

    My use case is a university dining website with open/closing times on various locations. They still show up as open for anonymous users after they close, and vice-versa.

    For sites that have page-level caching and, especially for sites that also use edge-caching via a reverse proxy such as CloudFlare or Fastly, the proposal to disable caching for anonymous users is not viable.

    We need a different solution because we have to use caching.

  • Status changed to Needs work over 1 year ago
  • 🇳🇱Netherlands johnv

    I am adding some code to the patch:
    - only call js for anonymous users (should we add a currentUser to a function?)
    - also add for the 'normal' field formatter, since there, you can include the status as well.

    I guess I broke something, since there is an Uncaught SyntaxError: JSON.parse: unexpected keyword at line 1 column 1 of the JSON data
    in office_hours_status_update.js:12:39

  • Status changed to Needs review over 1 year ago
  • Open in Jenkins → Open on Drupal.org →
    Core: 10.0.7 + Environment: PHP 8.1 & MySQL 5.7
    last update over 1 year ago
    Patch Failed to Apply
  • 🇳🇱Netherlands johnv

    Please find attached a new version.
    This is tested for all formatters (since all can contain a status indicator, or a general change in hours can occur, due to season and exception days).
    It is only activated for anonymous users, since authenticated user caching should work properly. (please correct me if I am wrong)

    I assume you only tested for the 'status' formatter.
    Since 8.x-1.8, this is the first element of an array, like the other formatters, in 🐛 Office hours Status formatter is blank in Views Fixed .
    Hence, the patch uses a different array depth in several locations.

    Please test.
    A general js error 'Uncaught TypeError: props. Overlay is null' occurs.

  • 🇳🇱Netherlands johnv

    Please also check 🐛 Fix Field cache for Anonymous users, only if Internal Page Cache is enabled Fixed

    The cache max-age should be correctly calculated.

    I am still not sure of the root cause. Is this wrong bubbling-up of the cache metadata?

  • 🇳🇱Netherlands johnv

    From #2499321-4: Page Cache does not respect 'max-age' = 0 "

    The anonymous page cache does not support cache contexts and doesn't respect max-age. By design.

    Adding this would mean that you would completely break the page cache for the whole site. Obviously, that would be a very bad idea.

    Again, the page cache is no different to Drupal 7, you had exactly the same problems there, you just didn't notice as it wasn't enabled in the tests.

    There is only one way to make the DNT header work when combined with page cache: You need to do it in JS.

    The issue then is set to be a duplicate of: #2352009: [pp-3] Bubbling of elements' max-age to the page's headers and the page cache

    • johnv committed a708a4d3 on 8.x-1.x
      Issue #3351280 by johnv, jhuhta, daddison: Fix Field cache for anonymous...
  • Status changed to Fixed about 1 year ago
  • 🇳🇱Netherlands johnv

    okay, okay,

    i still think it should not be the task of every contrib module to fix cache problems.
    Please see the commit.

    The js is only appended for anonymous users, to fix core's page_cache module.
    I do not know about external cache systems.

    Also, the field is now rendered twice. Perhaps anyone knows how to solve that.

    • johnv committed be633e82 on 8.x-1.x
      Issue #3351280: Fix Field cache for anonymous users using JS callback in...
  • Automatically closed - issue fixed for 2 weeks with no activity.

  • Status changed to Fixed about 1 year ago
  • 🇳🇱Netherlands johnv

    This feature was improved here: 🐛 Avoid redundant JS callback in Status formatter Active

  • 🇧🇪Belgium Mav_fly

    I encounter the same problem in one of our projects.
    I made a view (block) from my customer office hours and displayed that on their website as status option. When they are open or closed the status doesn’t change from “currently open” or “currently closed” for not logged in users (anonymous). When you clear the cache the status is correct.

    We tried the patch in #22 but this patch failed with our configuration :

    D10.2.4
    PHP 8.2.16

    Can someone give us some tips where we can find any solution to solve that problem ?

  • 🇳🇱Netherlands johnv

    Let's continue here: 🐛 Views with Open/Closed status are not updated Needs review
    Please use version 8.x-1.17. for Views, the caching must be disabled, (under 'Advanced)'

  • 🇫🇷France O'Briat Nantes

    Beware that patch #22 contains debug functions: console.log & dpm

Production build 0.71.5 2024