Problem/Motivation

The Paragraph::label() logic tries to provide information if the parent entity is referring to the paragraph in a previous revision, but it doesn't work in some scenarios.

Steps to reproduce:
- Node A with a paragraphs field
- Create a node with a paragraph
- Create another revision of the node, where the paragraph item was removed
- Load the paragraph by code and call $paragraph->label()

You expect to see something like "Node title (previous revision)", but instead you have an empty string.

Proposed resolution

- Fix the logic to provide correct label in previous revisions
- Create an additional helper method ParagraphInterface::getOrphanStatus() which will help, among other things, to make that logic cleaner

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

marcoscano created an issue. See original summary.

marcoscano’s picture

Status: Active » Needs review
FileSize
2.4 KB
4.36 KB

First shot, just fixing the current logic without the helper method.

I've noticed that we are not using t() for the suffix added here. Is that intentional? (I did not change this).

Also, the test could probably just be a kernel test, but I'm not sure if the setup code for that justifies the effort?

The last submitted patch, 2: 2954039-2-test-only.patch, failed testing. View results

miro_dietiker’s picture

The missing t() is accidental.

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/src/Entity/Paragraph.php
    @@ -134,16 +134,28 @@ class Paragraph extends ContentEntityBase implements ParagraphInterface {
         if ($parent = $this->getParentEntity()) {
           $parent_field = $this->get('parent_field_name')->value;
    +      /** @var \Drupal\entity_reference_revisions\EntityReferenceRevisionsFieldItemList $values */
           $values = $parent->{$parent_field};
    

    While working on this, lets also make sure that we add an hasField() check. and mabe include the summary in the orphaned fallback label, like "Orphaned @type: @first_20_characters_of_summary" or so.

  2. +++ b/src/Entity/Paragraph.php
    @@ -134,16 +134,28 @@ class Paragraph extends ContentEntityBase implements ParagraphInterface {
    +        $previous_revision = TRUE;
    +        foreach ($values as $value) {
    +          if ($value->entity->id() == $this->id()) {
    +            $previous_revision = FALSE;
    +            $label = $parent->label() . ' > ' . $value->getFieldDefinition()->getLabel();
    +            break;
    +          }
    +        }
    

    I would invert it and do an early return inside the foreach if have a match. Then you can drop the if/else and just return the fallback title if the foreach never matched.

    And yes, both variants should use t() with placeholders and not just combine the strings together.

  3. +++ b/tests/src/Functional/ParagraphsLabelTest.php
    @@ -0,0 +1,77 @@
    +class ParagraphsLabelTest extends BrowserTestBase {
    

    Can't we make this a kernel test, I don't think this contains anything that is UI relevant and a kernel test would be a lot faster.

Berdir’s picture

1. On the other hand, 1) the orphaned stuff is hopefully temporary, so lets not do too much there, feel free to skip that.

marcoscano’s picture

@Berdir thanks for reviewing!

I was quite far in the orphan helper stuff when I saw your review, so here it is... :/
But feel free to indicate if you think it's definitely not worth doing, I can go back to the previous approach if it's the case. IMHO though I have the feeling that even if we solve the garbage collection soon, it may still be useful to differentiate "used in default revision" vs "used in previous revisions" scenarios? or it could also be helpful in implementing the garbage collection itself i guess...

Will work on refactoring the test next.

Berdir’s picture

To be clear, I just meant that a better label for a completely orphaned label might not be needed, as the goal is to prevent that from happening, Non-default revision is still useful, as it knowing what the status is.

marcoscano’s picture

Status: Needs work » Needs review
FileSize
2.4 KB
12.21 KB
9.21 KB

And here with the updated test.

The last submitted patch, 9: 2954039-2-test-only.patch, failed testing. View results

marcoscano’s picture

Title: Fix ::label() logic and add a ParagraphInterface::isOrphan() helper » Fix ::label() logic and add a ParagraphInterface::getOrphanStatus() helper
Issue summary: View changes
miro_dietiker’s picture

Status: Needs review » Needs work
+++ b/src/Entity/Paragraph.php
@@ -134,20 +136,73 @@ class Paragraph extends ContentEntityBase implements ParagraphInterface {
+      ->condition($parent->getEntityType()->getKey('id'), $parent->id())

This is a wrong condition.

A Paragraph could be moved with drag & drop from one parent host to some other. A previous revision of a previous parent would point to the paragraphs entity, but the parent id would have changed.

miro_dietiker’s picture

Wohoo, the key problem here is that these base fields are not revisionable...

    $fields['parent_id'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Parent ID'))
      ->setDescription(t('The ID of the parent entity of which this entity is referenced.'))
      ->setSetting('is_ascii', TRUE);

    $fields['parent_type'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Parent type'))
      ->setDescription(t('The entity parent type to which this entity is referenced.'))
      ->setSetting('is_ascii', TRUE)
      ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);

    $fields['parent_field_name'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Parent field name'))
      ->setDescription(t('The entity parent field name to which this entity is referenced.'))
      ->setSetting('is_ascii', TRUE)
      ->setSetting('max_length', FieldStorageConfig::NAME_MAX_LENGTH);

(And the parent pointer also doesn't point to the parent revision.)
That's where things fall apart.

This results in inability to simply query in revisions and figure out where the current paragraph is a child.
A workaround could be implemented (by looping and querying all paragraph fields) but that's not worth doing here.

Instead i would propose

  • only support the case where the parent didn't change, by checking the target id only, not the parent id
  • Document the status ORPHAN_STATUS_NOT_USED with a @todo as unreliable as a paragraph could report this status after moving from parent A to parent B and then deleting the field item (although it is still used in previous revisions of parent A).
  • follow-up: Making them revisionable, investigate if we can reconstruct the history on update
  • follow-up: Fix the orphan status so it can be reliably used for gc
marcoscano’s picture

Status: Needs work » Needs review
FileSize
12.78 KB
1.53 KB

@miro_dietiker true, thanks! :)

I've created #2954512: Store information about a paragraph's parent revision and #2954513: [PP-1] Properly detect if a paragraph is orphan as follow-ups, and documented the limitation of the current implementation.

marcoscano’s picture

askibinski’s picture

FileSize
12.56 KB

Here is the same patch as #14 but against 1.3, for anyone who needs it.

seanB’s picture

@askibinski You removed use StringTranslationTrait; which causes the patch to fail. Attached a new patch!

Status: Needs review » Needs work

The last submitted patch, 17: interdiff-16-17.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

seanB’s picture

Status: Needs work » Needs review

That interdiff was supposed to be a .txt file obviously...

Dennis Cohn’s picture

Here is the same patch as #17 but against the latest 8.x-1.x, for anyone who needs it.

neeravbm’s picture

Here's the same patch as #18 re-rolled against the latest version of 8.x-1.x-dev.

miro_dietiker’s picture

Status: Needs review » Needs work
+++ b/src/Entity/Paragraph.php
@@ -139,20 +141,76 @@ class Paragraph extends ContentEntityBase implements ParagraphInterface {
+    $parent = $this->getParentEntity();

We do not have a real guarantee that a parent is always set. See also below early exit for getOrphanStatus()

johnchque’s picture

Oh what happened with the tests from #17?

Let's add it back.

Berdir’s picture

Status: Needs work » Needs review
FileSize
5.61 KB

Refactored this (more accurately, started from scratch, so no interdiff), dropped the orphan API and just inlined the the checks. Did readd the tests and refactored them to only test label().

miro_dietiker’s picture

Status: Needs review » Fixed

Very nice progress and clean test cases, thus committed.

  • miro_dietiker committed bac92b0 on 8.x-1.x authored by Berdir
    Issue #2954039 by marcoscano, seanB, Berdir, askibinski, Dennis Cohn,...
chr.fritsch’s picture

Status: Needs review » Fixed

Fixing the status

Status: Fixed » Closed (fixed)

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