Problem/Motivation

Apologies if I've misunderstood, but I was trying to figure out why the changes in #2500525: Time ago/hence date/time formatting breaks caching; needs appropriate max age didn't cause my view with a 'Time ago' field to only be cached for a short period. From my understanding of the code, it would never have fixed it:

  • FormattedDateDiff::$maxAge is now filled with a reasonable max age based on the granularity being displayed. This works perfectly.
  • TimestampAgoFormatter etc now get the time ago returned as an object and then call CacheableMetadata::createFromObject($result)->applyTo($build); to apply the max age from $result (the FormattedDateDiff object). This is reasonable.
  • CacheableMetadata::createFromObject recognises FormattedDateDiff as an implementation of CacheableDependencyInterface and so get's the max age from the object: meta->cacheMaxAge = $object->getCacheMaxAge();.
  • FormattedDateDiff::getCacheMaxAge() is actually inherited from UnchangingCacheableDependencyTrait and simply returns Cache::PERMANENT
  • So FormattedDateDiff::$maxAge is actually completely ignored and FormattedDateDiff::getMaxAge() which actually return FormattedDateDiff::$maxAge is never called in all of Drupal Core as far as I can tell.

Proposed resolution

Rename FormattedDateDiff::getMaxAge() to FormattedDateDiff::getCacheMaxAge() to override the trait.

Add proper coverage of the FormattedDateDiff() methods to prevent further regressions.

Remaining tasks

None.

User interface changes

None.

API changes

Technically, a public method that shouldn't be on FormattedDateDiff is being removed. Don't think this warrants a CR, as it was a typo in the original patch that slipped through.

Data model changes

None.

Patch / Beta Eligibility

Per https://www.drupal.org/core/d8-allowed-changes#beta this qualifies for inclusion in 8.1.x and 8.2.x this would qualify under

  • bug fixes and contributed project blockers, if they are non-disruptive, or if the impact outweighs the disruption
  • minor API additions or internal API changes to fix bugs (or for other prioritized changes), if the impact outweighs the disruption
CommentFileSizeAuthor
#49 2780549-2-48.patch2.61 KBalexpott
#49 34-48-interdiff.txt1.35 KBalexpott
#34 2780549-34-testFormattedDateDiff.patch2.13 KBandrewbelcher
#34 2780549-34-testFormattedDateDiff.test_only.patch1.67 KBandrewbelcher
#29 2780549-29-testFormattedDateDiff.interdiff.txt835 bytesandrewbelcher
#29 2780549-29-testFormattedDateDiff.patch2.14 KBandrewbelcher
#29 2780549-29-testFormattedDateDiff.test_only.patch1.67 KBandrewbelcher
#28 2780549-28-testFormattedDateDiff.interdiff.txt712 bytesandrewbelcher
#28 2780549-28-testFormattedDateDiff.patch2.14 KBandrewbelcher
#28 2780549-28-testFormattedDateDiff.test_only.patch1.67 KBandrewbelcher
#24 2780549-23-testFormattedDateDiff.patch1.99 KBandrewbelcher
#24 2780549-23-testFormattedDateDiff.test_only.patch1.52 KBandrewbelcher
#24 2780549-23-testformatDiff.patch2.38 KBandrewbelcher
#24 2780549-23-testformatDiff.test_only.patch1.92 KBandrewbelcher
#23 2780549-23-testFormattedDateDiff.interdiff.txt1.37 KBandrewbelcher
#17 2780549-16-testFormattedDateDiff.interdiff.txt2.44 KBandrewbelcher
#17 2780549-16-testFormattedDateDiff.patch2.75 KBandrewbelcher
#17 2780549-16-testFormattedDateDiff.test_only.patch1.99 KBandrewbelcher
#17 2780549-16-testformatDiff.interdiff.txt2.59 KBandrewbelcher
#17 2780549-16-testformatDiff.patch2.66 KBandrewbelcher
#17 2780549-16-testformatDiff.test_only.patch1.9 KBandrewbelcher
#13 2780549-test-only.patch1.82 KBmpdonadio
#12 2780549-12.patch2.26 KBandrewbelcher
#7 2780549-7.patch1.55 KBandrewbelcher
#7 2780549-7.test-only.patch1.11 KBandrewbelcher
#4 2780549-4.patch1.55 KBandrewbelcher
#4 2780549-4.test-only.patch1.11 KBandrewbelcher
#3 2780549-3.patch678 bytesalexpott
#3 2780549-3.test-only.patch493 bytesalexpott
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

andrewbelcher created an issue. See original summary.

alexpott’s picture

Issue tags: +Needs tests

@andrewbelcher your analysis looks spot on. Nice find. Looks like a patch and some tests are needed.

alexpott’s picture

Status: Active » Needs review
FileSize
493 bytes
678 bytes
andrewbelcher’s picture

Ah, you've already done a patch in the time it took me to put what I'd done into patches.

I've taken a slightly different approach with tests, not sure which approach is better...

alexpott’s picture

@andrewbelcher let's see what test bot says - your patch looks good. Because even if mine fail it would still need your added coverage.

andrewbelcher’s picture

I had a typo in mine so cancelled, just updating and waiting for composer to install so I can run it locally first.

andrewbelcher’s picture

Ok, these fail/pass appropriately locally.

The last submitted patch, 4: 2780549-4.test-only.patch, failed testing.

The last submitted patch, 4: 2780549-4.patch, failed testing.

alexpott’s picture

So it looks like we're missing proper test coverage to toRenderable too. Given that the test only patch in #3 passed.

The last submitted patch, 7: 2780549-7.test-only.patch, failed testing.

andrewbelcher’s picture

Here's some basic test coverage for FormattedDateDiff::toRenderable(). Don't think a data provider or anything more is necessary as it doesn't really do anything other than build the array.

mpdonadio’s picture

Nice catch. Let's make sure both new tests fail w/o the fix. This patch is small, and easy to review, but it helps to post an interdiff between different versions.

mpdonadio’s picture

Status: Needs review » Needs work

The last submitted patch, 13: 2780549-test-only.patch, failed testing.

mpdonadio’s picture

  1. +++ b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php
    @@ -53,7 +53,7 @@ public function getString() {
       /**
        * @return int
        */
    -  public function getMaxAge() {
    +  public function getCacheMaxAge() {
    

    This should now be an {@inheritdoc}.

  2. +++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
    @@ -393,6 +399,22 @@ public function providerTestFormatDiff() {
    +  public function testToRenderable() {
    +    $expected = [
    +      '#markup' => '10 minutes',
    +      '#cache' => [
    +        'max-age' => 60,
    +      ],
    +    ];
    +    $formatted_date_diff = new FormattedDateDiff($expected['#markup'], $expected['#cache']['max-age']);
    +    $this->assertArrayEquals($expected, $formatted_date_diff->toRenderable());
    +  }
    +
    

    Not sure if this is a valid test.

    I think this after this hunk:

    +++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
    @@ -270,7 +271,12 @@ public function testformatDiff($expected, $max_age, $timestamp1, $timestamp2, $o
    +    $build = [];
    +    CacheableMetadata::createFromObject($result)->applyTo($build);
    +    $this->assertEquals($max_age, $build['#cache']['max-age']);
    

    adding

    $build = $result->toRenderable();
    $this->assertEquals($max_age, $build['#cache']['max-age']);
    

    would be more appropriate since it relies on ::formatDiff() building something that can call ::toRenderable() and not building a FormattedDateDiff() object directly in which ::toRenderable() is essentially a pass-though getter. And add the additional @covers and some comments.

    ?

andrewbelcher’s picture

1. This is done, good spot.

2. I'm not quite sure why it's not a valid test. We are specifically testing the pass through of it, as the building of it is already tested when we assert the result object matches our expected object.

To that end, I've created 2 sets of patches, one which does as you've suggested of putting it into testformatDiff, the other moves all of it into testFormattedDateDiff adding in a check on getString as well. My preference is the latter, doesn't seem much point running that test multiple times with the provider for testformatDiff...

The last submitted patch, 17: 2780549-16-testformatDiff.test_only.patch, failed testing.

The last submitted patch, 17: 2780549-16-testFormattedDateDiff.test_only.patch, failed testing.

dawehner’s picture

mpdonadio’s picture

  1. diff --git a/.idea/misc.xml b/.idea/misc.xml
    new file mode 100644
    index 0000000..8662aa9
    --- /dev/null
    +++ b/.idea/misc.xml
    @@ -0,0 +1,4 @@
    +<?xml version="1.0" encoding="UTF-8"?>
    +<project version="4">
    +  <component name="ProjectRootManager" version="2" />
    +</project>
    \ No newline at end of file
    diff --git a/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php
    

    Looks like this got in by accident.

  2. +++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
    @@ -251,6 +252,8 @@ public function testFormatTimeDiffSince() {
    +   * @covers \Drupal\Core\Datetime\FormattedDateDiff::__construct::getCacheMaxAge
    +   * @covers \Drupal\Core\Datetime\FormattedDateDiff::__construct::toRenderable
        */
    

    I'm not sure if we normally allow this syntax, though phpunit didn't complain about it. I think we just need the two lines w/o eg,

     * @covers \Drupal\Core\Datetime\FormattedDateDiff::getCacheMaxAge
     * @covers \Drupal\Core\Datetime\FormattedDateDiff::toRenderable
    
  3. +++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
    @@ -270,7 +273,23 @@ public function testformatDiff($expected, $max_age, $timestamp1, $timestamp2, $o
    +    $result = $this->dateFormatter->formatDiff($timestamp1, $timestamp2, $options);
    +    $this->assertEquals($expected_object, $result);
    +
    +    // Test applying the cacheability data to an existing build.
    +    $build = [];
    +    CacheableMetadata::createFromObject($result)->applyTo($build);
    +    $this->assertEquals($max_age, $build['#cache']['max-age']);
    +
    +    // Test converting the result to a render array.
    +    $build = $result->toRenderable();
    +    $expected_build = [
    +      '#markup' => $expected,
    +      '#cache' => [
    +        'max-age' => $max_age,
    +      ],
    +    ];
    +    $this->assertArrayEquals($expected_build, $build);
    

    Even though the two approaches are similar, this is the approach that makes the most sense to me for the long run.

With those two small adjustments, this is RTBC.

andrewbelcher’s picture

Good spots. Not in front of my computer atm, will try and get the updated patch up today.

andrewbelcher’s picture

I've fixed the bits from #21. I've also added the coverage for FormattedDateDiff::getString to DateTest::testformatDiff so that both approaches have equal/full coverage.

andrewbelcher’s picture

The last submitted patch, 24: 2780549-23-testformatDiff.test_only.patch, failed testing.

The last submitted patch, 24: 2780549-23-testFormattedDateDiff.test_only.patch, failed testing.

mpdonadio’s picture

Issue summary: View changes
Status: Needs review » Needs work
+++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
@@ -393,6 +394,34 @@ public function providerTestFormatDiff() {
+   * Tests FormattedDateDiff.
+   *
+   * @covers \Drupal\Core\Datetime\FormattedDateDiff
+   */
+  public function testFormattedDataDiff() {
+    $string = '10 minutes';
+    $max_age = 60;
+    $object = new FormattedDateDiff($string, $max_age);
+
+    // Test conversion to a render array.
+    $expected = [
+      '#markup' => $string,
+      '#cache' => [
+        'max-age' => $max_age,
+      ],
+    ];
+    $this->assertArrayEquals($expected, $object->toRenderable());
+
+    // Test retrieving the formatted time difference string.
+    $this->assertEquals($string, $object->getString());
+
+    // Test applying cacheability data to an existing build.
+    $build = [];
+    CacheableMetadata::createFromObject($object)->applyTo($build);
+    $this->assertEquals($max_age, $build['#cache']['max-age']);
+  }

We are starting to get into scope creep on this now; the ::getString() coverage isn't really related to the bug at hand. I preferred the other approach, but am not necessarily opposed adding this coverage in this issue if we adding a new test method here which consolidates the coverage of these methods (a committer may disagree and push back on this).

This part

+++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
@@ -393,6 +394,34 @@ public function providerTestFormatDiff() {
+   * Tests FormattedDateDiff.
+   *
+   * @covers \Drupal\Core\Datetime\FormattedDateDiff
+   */

needs to be updated, though. We don't have @covers for whole classes in core, just individual methods. This needs to be

+   * @covers \Drupal\Core\Datetime\FormattedDateDiff::getCacheMaxAge()
+   * @covers \Drupal\Core\Datetime\FormattedDateDiff::getString()
+   * @covers \Drupal\Core\Datetime\FormattedDateDiff::toRenderable()

If we make that change, and just post one version of a new patch (ie, just update 2780549-23-testFormattedDateDiff.patch and post a test-only version), then I think we are good and I will set RTBC.

andrewbelcher’s picture

andrewbelcher’s picture

The last submitted patch, 28: 2780549-28-testFormattedDateDiff.test_only.patch, failed testing.

The last submitted patch, 28: 2780549-28-testFormattedDateDiff.patch, failed testing.

The last submitted patch, 29: 2780549-29-testFormattedDateDiff.test_only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 29: 2780549-29-testFormattedDateDiff.patch, failed testing.

andrewbelcher’s picture

andrewbelcher’s picture

Status: Needs work » Needs review

The last submitted patch, 34: 2780549-34-testFormattedDateDiff.test_only.patch, failed testing.

mpdonadio’s picture

Status: Needs review » Reviewed & tested by the community

Ok, looks good. Thanks.

alexpott’s picture

So theres a question of whether FormatterDateDiff::getMaxAge() is public API. I don't think so. This was just an error of implementation and maintaining a BC layer feels pointless.

alexpott’s picture

Version: 8.1.x-dev » 8.2.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed abe6951 to 8.3.x and f465ed3 to 8.2.x. Thanks!

Discussed with @catch we agreed that the getMaxAge() is dead code.

If we want this to go inot 8.1.x then we should reroll leaving getMaxAge() as a bc layer for the tiny chance that someone is using this as a public API.

  • alexpott committed abe6951 on 8.3.x
    Issue #2780549 by andrewbelcher, alexpott, mpdonadio: FormattedDateDiff...

  • alexpott committed f465ed3 on 8.2.x
    Issue #2780549 by andrewbelcher, alexpott, mpdonadio: FormattedDateDiff...
andrewbelcher’s picture

If we need a backward compatibility later for 8.1.x, do we not also need it all the way to 9? People will no more expect the change between minor as they would patch... Perhaps a change record would suffice? Would be great to get this into 8.1.x so the cache works as expected.

If you think we do need a BC layer or a change record, I'm happy to write.

xjm’s picture

I actually do think it would be good to make the old, incorrect method a wrapper for the correct one and deprecate it, in all branches, similar to what @andrewbelcher suggests.

mpdonadio’s picture

Version: 8.2.x-dev » 8.1.x-dev
Status: Fixed » Needs work
Issue tags: +Needs change record

Ok, so it sounds like the plan should be

- roll the patch against 8.1.X
- write change record
- apply final patch to 8.1.X
- revert f465ed3, apply new patch to 8.2.X
- revert f465ed3, apply new patch to 8.3.X

?

mpdonadio’s picture

Version: 8.1.x-dev » 8.2.x-dev
Status: Needs work » Fixed
Issue tags: -Needs change record

Made followup to handle this, #2783491: Add BC and Deprecate FormattedDateDiff::getMaxAge(), so the commit log is more better.

alexpott’s picture

Status: Fixed » Needs work

  • alexpott committed 0c0481e on 8.3.x
    Revert "Issue #2780549 by andrewbelcher, alexpott, mpdonadio:...

  • alexpott committed 6448749 on 8.2.x
    Revert "Issue #2780549 by andrewbelcher, alexpott, mpdonadio:...
alexpott’s picture

Status: Fixed » Needs review
FileSize
1.35 KB
2.61 KB

Here's a patch the adds the (tested) BC layer.

alexpott’s picture

I decided that the plan to revert was better because if we're going to add a BC layer it should be in the same commit as the fix.

mpdonadio’s picture

Status: Needs review » Reviewed & tested by the community
$ ../vendor/bin/phpunit tests/Drupal/Tests/Core/Datetime/DateTest.php
PHPUnit 4.8.11 by Sebastian Bergmann and contributors.

...............................................................  63 / 102 ( 61%)
.......................................

Time: 530 ms, Memory: 8.00Mb

OK (102 tests, 222 assertions)

Assuming this patch actually runs and come up green, the new version is good; the test passes locally for me.

The comment on the deprecated method matches CacheableDependencyInterface::getCacheMaxAge(). The BC shim calls the replacement function instead of just doing the same thing. The shim has test coverage that looks correct. The deprecation docs look good.

Made draft CR.

andrewbelcher’s picture

All looks great to me!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 1bd3a04 to 8.3.x and b845102 to 8.2.x and 76b777f to 8.1.x. Thanks!

Since we've added the BC and this issue would result in caching issues on site cherry-picked to 8.1.x too.

  • alexpott committed 1bd3a04 on 8.3.x
    Issue #2780549 by andrewbelcher, alexpott, mpdonadio: FormattedDateDiff...

  • alexpott committed b845102 on 8.2.x
    Issue #2780549 by andrewbelcher, alexpott, mpdonadio: FormattedDateDiff...

  • alexpott committed 76b777f on 8.1.x
    Issue #2780549 by andrewbelcher, alexpott, mpdonadio: FormattedDateDiff...

Status: Fixed » Closed (fixed)

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