Problem/Motivation

On the entity usage tab for a media item the status shows as published when actually the usage is in an old revision.

Steps to reproduce

  1. Enable media, paragraphs and entity_usage.
  2. Create media item (eg an image)
  3. Create a paragraph type and add a media field to it.
  4. Add a paragraph field to a content type
  5. Create a published node with a paragraph with the media item referenced in the media field.
  6. View the usage of the media item - it'll show correctly that the media item is used in published content.
  7. Edit the content and delete the paragraph.
  8. View the usage of the media item - it'll show incorrectly that the media item is used published content.

If you repeat the steps and instead of deleting the paragraph you remove the media item reference then the usage tab will correctly say that it is used in old revisions.

Proposed resolution

This is caused by paragraphs and what happens when one is deleted from a node. I think entity usage needs to ensure that the paragraph the entity is referenced from is actually in the paragraph field for the default revision for the parent entity.

Remaining tasks

User interface changes

API changes

Data model 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

alexpott created an issue. See original summary.

alexpott’s picture

Here's what you'll see on the usage tab if you follow the instructions in the issue summary:

I think the problem here is the it is quite confusing what each column is referring to.

  • Entity column links to the node
  • Type column says it is a paragraph
  • Status column says published which refers to the node

If you don't delete the paragraph but instead just remove or change media reference you'll see

And here you get a used in column which refers to the fact the media item is used in old revisions of the paragraph entity.

I'm not sure what the fix is but I think we need to be more clear about what each column is referring to as from user testing this completely confused people QAing a site I'm working on.

berdir’s picture

This a known issue and there's only so much that can be done about this with the current architecture. Published is actually the paragraph I think and not the node, unless there's custom code for that too.,

entity usage tracks paragraphs. And then the usage table attempts to backtrack that paragraph reference to the host entity which can involve multiple jumps, each of which can no longer be in active use. As in, the paragraph references to it's host, but the current revision of that host does no longer use that paragraph.

The code for that predates the "Used in" column and wasn't adjusted for that. It should be possible to improve it a bit and report it in the same way, but it will always be a complicated and slow approach that requires working up the parent tree and comparing the back-reference against the actual reference.

A long time ago, I proposed a different approach to tracking usages where such composite entities that don't really exist on their own are all assigned directly to the host, but that requires API changes to the plugin structure, see #3002332: Track composite (e.g. paragraph) entities directly and only as their host. There's some work there that I haven't reviewed yet.

berdir’s picture

Ah, partially misremembered. The previous revision bit actually comes from paragraphs, in its label method: \Drupal\paragraphs\Entity\Paragraph::label().

entity_usage would need to duplicate similar logic into the list controller that generates that table to pull that out of the label and instead display it in the Used in column.

alexpott’s picture

Published is actually the paragraph I think and not the node, unless there's custom code for that too.,

There is custom code for. this - see \Drupal\entity_usage\Controller\ListUsageController::getSourceEntityStatus() where it does:

    // Treat paragraph entities in a special manner. Paragraph entities
    // should get their host (parent) entity's status.
    if ($source_entity->getEntityTypeId() == 'paragraph') {
      assert($source_entity instanceof ParagraphInterface);
      $parent = $source_entity->getParentEntity();
      if (!empty($parent)) {
        return $this->getSourceEntityStatus($parent);
      }
    }
marcoscano’s picture

I agree that the root of the issue is that under the hood we track 1:1 all relationships without distinction, but on the UI we don't want to display all of them in the same way. This distinction makes the controller code complex, inefficient and prone to errors and edge cases.

That said, I agree there is a bug here we can fix to improve editorial experience of the scenario you describe. IMO the fix should be to:

1- Add the "Used in (Old revision's)" column also in your scenario 1
2- (unrelated, we can do this in a follow-up) Hide the "Status" column whenever the "Used in" column is displayed (or when the usage doesn't refer to the default revision of the host). To me it makes no sense to display "Published" or "Unpublished" for the host when the usage isn't in the default revision.

We may surface other edge cases by doing so, but I agree this is likely a step forward in improving things.

berdir’s picture

Agreed with the suggestion, you essentially need to add more paragraph specific checks, don't call label()

On 2, I think what that essentially means is combining Status and Used in into a single column, as it can be a mix of both for different usages. I think "Status: Used in old revisions" is a valid thing to say as "Status"

alexpott’s picture

Discussed this with @daniel.bosen. Here's what I think we should do for paragraphs...
1. The entity title column should be the host entity title unless the paragraph is orphaned.
2. The status column should be removed.
3. The type column should be paragraph type on host type (unless orphaned)
4. The usage column should always be present and we should list where the entity is used in which revisions of the host entity via the paragraph.
5. If the paragraph is on the default revision and references the entity and the host is published then then the used in text will contain "Published revision"

For non paragraph usages we:
1. Remove the status column
2. Always are the used in column and if the entity reference is used on the default revision and the default revision is published then the text will contain "Published revision".

berdir’s picture

Mostly makes sense, I'm not sure I fully follow your proposal in regards to status vs used in columns. "Published revision" doesn't add much value to me over just "Published" and you didn't really specify what it would say for a default revision that isn't published. I'd consider just a merged version that is Published/Unpublished/Old revision(s).

I also saw a recent comment from someone that the paragraph location bit in the title is actually useful information for them and I agree that it can be on longer, more complex entities. If we're going with custom logic for that, it could be a secondary row below the title, something like:

Linked Node Title
Paragraph: Container > Card

That's a pattern we use pretty often in our admin views to add more info without having too many columns.

marcoscano’s picture

I agree with the 2-row suggestion above, I too think that displaying the paragraph chain somehow is useful information for editors.

As for the status/used in column, I think any solution that combines them will improve things, and the wording should in all cases refer to the top-level host entity, not the immediate parent entity.

alexpott’s picture

Status: Active » Needs review

This issue could not be tackled without determining the target revision ID at entity usage save time. Well it could for paragraphs because we can go back and do a query to work this out. But it could not be done for non-reusable blocks placed using layout builder. This is because that information is embedded in a mass of serialized PHP. So I had to change tack at that point and when we are registering usage for paragraphs and content blocks embedded in layouts we record the revision ID in the entity usage table. We can then use this information to pair paragraph or content block with the correct revision of the thing (usually a node) that contains them.

I've also implemented what will probably turn out to be quite a big performance improvement on the entity usage tab in that once we work out that an entit is used in old revisions we stop processing once we will not display any further information. We can do this because we don't display any information about with older revisions the entity is used on.

In order to add the column to the entity usage table we need to rebuild it. Therefore the update path just drops and recreates the table with the new schema. For this reason I think this improvement belongs in a new major version of the module. What do you think @berdir and @marcoscano.

alexpott’s picture

Version: 8.x-2.x-dev » 5.x-dev
alexpott’s picture

StatusFileSize
new157.26 KB
new143.11 KB

Here's where I'm currently at. To test this:

  1. Create a paragraph type that has a media field
  2. Create a paragraph type that has a paragraph field
  3. Create a node type that has a paragraph field.
  4. Create a media entity and enable entity usage tab on media entities.
  5. Create a node with three paragraphs - one that directly links to the media, one that links to the media via another paragraph, and another that directly links to the media.
  6. Edit the node and delete the last paragraph that directly links to the media entity.

On HEAD this is what you see:

With the MR:

alexpott’s picture

So the one bit of information that you no longer get on the MR version is the paragraph type. But also the paragraph type information is incomplete even in HEAD because you don't know what the paragraph type is that has the paragraph field. Also the MR version is telling you the node type which I think is probably more useful in this context than the paragraph type.

@berdir what do you think?

marcoscano’s picture

I like the new approach, sounds good enough for most use cases 👍

alexpott changed the visibility of the branch 3547273-deleting-paragraphs-causes to hidden.

alexpott’s picture

Summarising a conversation between myself, @marcoscano and @berdir from a while back. We were discussing how to generalise the approach so the we can do the same for inline content entity blocks as the current MR is doing for paragraphs.

1. What is the best name for these plugins that allow an entity usage to determined by the host entity rather than itself? The best options were HostEntityUsageInterface or InlineEntityUsageInteraface

2. At the moment I’m stopping the user from being able to select paragraphs in the entity usage setting screen. Does this make sense? Should we ignore the case where a user has added a regular entity reference to paragraphs. If you create such a field in the UI you see a message saying Note: Regular paragraph fields should use the revision based reference fields, entity reference fields should only be used for cases when an existing paragraph is referenced from somewhere else. Should we allow the user to track that use-case? If so should we allow paragraphs to be selected on the UI but not track the case where a paragraph is referenced from an ERR field?

3. At the moment the UI allows you to enable and disable plugins but I think plugins that implement this new interface should not be able to be enabled or disabled by the user. They should be able to decide themselves - for example the ERR plugin should be enabled if the entity_reference_revisions module is installed.

4. How should we handle ERR fields to something other than a paragraph? Berder pointed out ERR itself doesn't hardcode paragraphs. it relies on the additional entity type definition keys for parent type/id to treat it accordingly, as does tmgmt. entity_usage could do the same, but maybe there are paragraph specific bits in the current language. the methods on paragraph entity at least aren't on an shared interface.

5. We already have hook_entity_usage_block_tracking for custom modules to implment complex requirements for when not to track. But I think we should replace that API with hooks from \Drupal\entity_usage\EntityUpdateManager::allowSourceEntityTracking() and \Drupal\entity_usage\EntityUpdateManager::allowTargetEntityTracking() as this would allow you to bail before doing the work. Imo this is a separate issue.

Given the discussion I'm going to try to abstract out the current approach to paragraphs and implement for inline content blocks so we can prove the new API can do this. Should have the side effect of fixing #3181406: Wrong text when entity is layout builder revision here.

alexpott’s picture

After discussions with @daniel.bosen and trying to fix the generic case I've gone back to making this issue only deal with the paragraph case. Doing the inline block case meant we'd have to load some target entities to check if they were inline and this feels wrong / something that will be easier to explore in follow-ups. This change now fixes things for entity types that paragraphs which really are a special field on another entity type and never should be considered as an entity from the perspective of the user.

@daniel.bosen and I also had a good discussion about the the used in and status column. The "Status" column was removed. Its information is now part of the "Used in" column on the entity usage list page.

Each cell in the "Used in" column contains one or more of:

  • "Published revision" — the source's default revision is published.
  • "Draft revision" — applies in two cases: the source's default revision is unpublished, or a pending revision exists that is newer than the default. Only the latest pending revision per translation is shown.
  • "1 old revision" or " N old revisions" — references exist in earlier revisions only. Pluralised based on the count of distinct prior revisions.
  • "Current revision" — the source entity does not have a published status.

When a row contains more than one of these, they render as a bulleted list.

cgmonroe’s picture

I know it's an old issue, but just saw this.

FWIW I had to deal with this a bit with my embedded paragraphs code. #3504984: Support embedded paragraphs/ Improve paragraph usage info I took the approach that embedded paragraphs and paragraphs were not linkable (i.e. no viewable /entity-type/id links) and should not be the endpoint displayed on the usage report.

I added some code to the getSourceEntityLinks to recurse thru the source info to find an actual linkable entity. So that if you had a trackable entity in a paragraph in an embedded paragraph on a node the usage info would show: Node title (linked) > paragraph. I think the status was based on the linkable parent so it would be 'old revision'

The other benefit of this is that content people want to know were to edit the location that uses the thing they need to change. Telling them a paragraph without a link doesn't work. Being able to go to the node and drill down does.

marcoscano’s picture

Status: Needs review » Reviewed & tested by the community

Nice work @alexpott 👏

I agree with dealing with paragraphs only for now, and the implementation doesn't hardcode things too strongly, we just happen to have a single implementation of ::getInlinePluginIds(), we can always expand that later.

Also agreed on the "Used in" simplification 👍

I went through the MR and haven't found anything worth improving, except for a minor nit. I have also tested this manually on a vanilla install and everything looks right to me.

When we merge this, I believe we should also mark #3002332: Track composite (e.g. paragraph) entities directly and only as their host as done, since that's effectively what's happening here.

Being this 5.x only, I think it's fine to get it in, and have real sites start to test this out slowly as the 5.x introduction. Feel free to merge when you think this is ready. Again, nice work!

  • alexpott committed 078aabf0 on 5.x
    fix: #3547273 Deleting paragraphs causes the entity usage tab to display...
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

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