Problem/Motivation

Current Block library for entities will list a lot of not needed entity_field and some entity_reference. This is a "normal" UI Patterns behavior to list sources without condition.

Problem is a lot of them are irrelevant inflating the list of blocks with noise, and other can provide unwanted behavior and even fatal or memory crash from recursion.

Current way is to use exclude provider and exclude_id, which works great for block source but not entity_field, and using it for entity_field generate a heavy and hard to maintain list like on the Bootstrap recipe.

entity_field
  field:node:article:nid >> Probably not relevant
  field:node:article:uuid >> Probably not relevant
  field:node:article:vid >> Probably not relevant
  field:node:article:langcode >> Probably not relevant
  field:node:article:type >> Probably not relevant
  field:node:article:revision_timestamp >> Not relevant
  field:node:article:revision_uid >> Not relevant
  field:node:article:revision_log >> Not relevant
  field:node:article:status >> Not relevant
  field:node:article:uid >> Probably not relevant
  field:node:article:promote >> Not relevant
  field:node:article:sticky >> Not relevant
  field:node:article:default_langcode >> Not relevant
  field:node:article:revision_default >> Not relevant
  field:node:article:revision_translation_affected >> Not relevant
  field:node:article:path >> Probably not relevant
  field:node:article:menu_link >> Probably not relevant
  field:node:article:field_display_full >> On entity override will CRASH
  field:node:article:field_display_teaser >> On entity override will CRASH
  field:comment:comment:thread >> Not relevant
  field:comment:comment:pid >> Not relevant
  field:comment:comment:cid >> Not relevant
  field:comment:comment:field_name >> Not relevant
  field:comment:comment:langcode >> Not relevant
  field:comment:comment:default_langcode >> Not relevant
  field:comment:comment:entity_id >> Not relevant
  field:comment:comment:langcode >> Not relevant
  field:comment:comment:status >> Not relevant
  field:comment:comment:thread >> Not relevant
  field:comment:comment:uid >> Not relevant
  field:comment:comment:uuid >> Not relevant
  field:user:user:langcode >> Not relevant
  field:user:user:default_langcode >> Not relevant
  field:user:user:init >> Not relevant
  field:user:user:preferred_langcode >> Not relevant
  field:user:user:preferred_admin_langcode >> Not relevant
  field:user:user:uid >> Not relevant
  field:user:user:status >> Not relevant
  field:user:user:uuid >> Not relevant
entity_reference
  entity_reference:node:article:menu_link:menu_link_content:menu_link_content >> Not relevant
  entity_reference:node:article:revision_uid:user:user >> Not relevant

We should find a way to exclude easily these blocks without a manual config.

Main source loader is BlockLibraryPanel::getSources with usage of Drupal\ui_patterns\SourcePluginManager and rely on SourceWithChoicesInterface.

Proposed resolution

As UI Patterns is meant to be generic this probably must be resolved within Display Builder.

Some propistion:

  • Extend filter sources in BlockLibrary with a more efficient filtering
  • Add our own SourcePluginManager which implement our logic of filtering and will provide 'free' cache.
  • ..?

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

mogtofu33 created an issue. See original summary.

mogtofu33’s picture

pdureau’s picture

Each base field already has an attribute telling if it is suitable for display building or not: FieldDefinition::isDisplayConfigurable() (more precisely: BaseFieldDefinition::isDisplayConfigurable()).

Checking each field against this method will be efficient to remove the fields we don't want without maintaining our own "black" list.

However, we will lose a few fields we want, like node:title, so we may need a "white" list instead.

mogtofu33’s picture

Guess it have to be from UI Patterns, I see some other places like EntityFieldSourceDeriverBase where FieldDefinition is yield. Can you create an UI Patterns issue?

pdureau’s picture

Can you create an UI Patterns issue?

We can propose to annotate the source plugin definitions generated by EntityFieldSourceDeriverBase with a ui_hidden boolean attribute: true only for fields:

  • with:isDisplayConfigurable() === TRUE
  • some selected fields: "Label/Name/Title", "Authored on", "Authored by", "Changed by"... which ones exactly?

And then do the filter in BlockLibraryPanel.

(Layout Builder is doing such annotation with _block_ui_hidden but is not using it in UI)

Or, we can use the "standard" no_ui if it is not hiding them also for UI Patterns source selector for slots.

Are you OK with such proposal?

mogtofu33’s picture

Great, I am ok, I like the no_ui for consistency with components.

mogtofu33’s picture

Issue tags: -beta blocker +RC blocker
mogtofu33’s picture

Do we have an issue on UI Patterns or not yet?

mogtofu33’s picture

Issue tags: +UI Patterns
pdureau’s picture

mogtofu33’s picture

mogtofu33’s picture

Assigned: Unassigned » pdureau
Status: Active » Needs review
pdureau’s picture

Tested with the current MR & https://git.drupalcode.org/project/ui_patterns/-/merge_requests/573

So, if my understanding is correct, we are keeping both:

  • the fields with FieldDefinition::isDisplayConfigurable('view') === TRUE
  • the fields with created & changed

So, I need to check:

For example, would we want to keep the field marked with "⚠️" or "❓" ?

Bundle Kept Removed
node:article
  • Authored by
  • Authored on
  • Body
  • Changed
  • Extra field
  • Image
  • Tags
  • Title
  • Content type ❓
  • Default revision
  • Default translation
  • ID
  • Promoted to front page
  • Published (boolean) ❓
  • Revision create time
  • Revision ID
  • Revision log message
  • Revision translation affected
  • Revision user
  • Sticky at top of lists
  • URL alias
  • UUID
user:user
  • Changed
  • Created
  • Extra field
  • Picture
  • Default translation
  • Email (email) ⚠️
  • Initial email
  • Language code
  • Last access
  • Last login (timestamp) ⚠️
  • Name (string) ⚠️
  • Preferred admin language code
  • Preferred language code
  • Roles (entity_reference) ❓
  • Timezone
  • User ID
  • User status (boolean) ❓
  • UUID
taxonomy_term:tags
  • Changed
  • Description
  • Extra field
  • Name
  • Default revision
  • Default translation
  • Language
  • Published (boolean) ❓
  • Revision create time
  • Revision ID
  • Revision log message
  • Revision translation affected
  • Revision user
  • Term ID
  • Term Parents
  • URL alias
  • UUID
  • Vocabulary ❓
  • Weight
media:audio
  • Audio file
  • Authored by
  • Authored on
  • Changed
  • Extra field
  • Name
  • Thumbnail
  • Default revision
  • Default translation
  • ID
  • Language
  • Media type ❓
  • Published ❓
  • Revision create time
  • Revision ID
  • Revision log message
  • Revision translation affected
  • Revision user
  • URL alias
  • UUID

It is particularly sensible for Users entities. It would be hard to build a display with just changed, created & picture fields.

pdureau’s picture

Assigned: pdureau » mogtofu33

It is particularly sensible for Users entities. It would be hard to build a display with just changed, created & picture fields.

A solution may be to finally not rely on FieldStorageDefinitionInterface->isDisplayConfigurable('view) for base fields but to filter them out according to where they come from, like:

  • the ones from RevisionLogEntityTrait::ownerBaseFieldDefinitions(): revision_created, revision_user, revision_log_message
  • the one from EntityPublishedTrait::publishedBaseFieldDefinitions(): published
  • those ones from ContentEntityBase::baseFieldDefinitions(): id, uuid, revision (Revision ID), langcode (Language)
  • those ones from EntityFieldManager::buildBaseFieldDefinitions(): revision_default, default_langcode (Default translation), revision_translation_affected
  • this one found from some entity types: weight (boolean)
  • and path (URL alias), but i don't know where its is added

It is relevant in my opinion because we know those fields already, they are strictly defined in code (careful, they are sometimes aliased, see ContentEntityTypeInterface::getRevisionMetadataKey()).

So, we will get those results:

Bundle Kept Removed
node:article
  • Authored by
  • Authored on
  • Body
  • Changed
  • Image
  • Tags
  • Title
  • Content type
  • Promoted to front page
  • Published (boolean)
  • Sticky at top of lists
  • ID
  • Default revision
  • Default translation
  • Revision ID
  • Revision create time
  • Revision log message
  • Revision translation affected
  • Revision user
  • UUID
  • URL alias
user:user
  • Changed
  • Created
  • Picture
  • Email (email)
  • Initial email
  • Language code
  • Last access
  • Last login (timestamp)
  • Name (string)
  • Preferred admin language code
  • Preferred language code
  • Roles (entity_reference)
  • Timezone
  • User status (boolean)
  • User ID
  • UUID
  • Default translation
taxonomy_term:tags
  • Changed
  • Description
  • Name
  • Published (boolean)
  • Term Parents
  • Vocabulary
  • Term ID
  • Default revision
  • Default translation
  • Language
  • Revision create time
  • Revision ID
  • Revision log message
  • Revision translation affected
  • Revision user
  • UUID
  • URL alias
  • Weight
media:audio
  • Audio file
  • Authored by
  • Authored on
  • Changed
  • Name
  • Thumbnail
  • Media type
  • Published
  • ID
  • Default revision
  • Default translation
  • Language
  • Revision create time
  • Revision ID
  • Revision log message
  • Revision translation affected
  • Revision user
  • UUID
  • URL alias

@mogtofu: is it good enough? It is already cleaner :)

I can init a MR if you are OK with the proposal.

pdureau’s picture

Assigned: mogtofu33 » pdureau
Status: Needs review » Needs work

Giving a quick try.

pdureau’s picture

Assigned: pdureau » mogtofu33
Status: Needs work » Needs review

Hello Jean

What do you think about this proposal without ::isDisplayConfigurable()?

I am the one who proposed to use in #3 but I know believe it was a bad idea.

I didn't' inject new service but I reused the one already adding in the first MR.

What do you think?

mogtofu33’s picture

Assigned: mogtofu33 » pdureau
Status: Needs review » Needs work

This is a good proposition and cleaner, but the same fields also show in the contextual form, through the UI Patterns field select.

Proposal in #3624220: Add ui_patterns_source_choices hook: a no_ui flag on derivable contexts, set by Display Builder in hook_derivable_context_info_alter(). exclude_id stays for the library fine tuning.
I will add a comment on the ui_pattern issue.

For the proposition here:

Tested the rules of !429 on every viewable content entity type (node, user, term, media, comment, block_content, file, menu_link_content...). Entity keys and revision metadata keys work well: they cover all entity types without naming fields. Some points:

  • Published: the code hides it which is right for Display Builder: status is not something to display. You missed it in your table.
  • Weight: 'weight' => 'boolean' never matches, term and menu link weight are integer. So Weight is still listed.
  • Language: langcode is hidden by its key, but preferred_langcode and preferred_admin_langcode on user stay, and other entities have their own. Hiding the language field type covers all of them.
  • Crash: field_display_full and field_display_teaser are still listed. They are ui_patterns_source fields, the recursion crash of this issue. Hiding that field type covers them whatever their name.
  • Possible fatal: $definitions[$field]->getType() assumes the base field exists. Using getFieldStorageDefinitions() with a null check avoids it, and also sees config fields.
  • The test is still named testHideNotDisplayConfigurableFields.

Not related here but a collateral that could be fixed in the same time for security:

  • Password: HIDE_CHOICE_ID only matches field:user:user:pass, the bundle-less field:user::pass (Views context) leaks. Hiding the password field type covers both, and any custom entity.

Then the entity specific ones: Promoted to front page and Sticky on node, Initial email on user, thread / hostname / entity_type / field_name on comment. Email is also personal data, so I would hide it by default. Leaving them to exclude_id brings back the original problem: one line per bundle, see the Bootstrap profile. I would rather keep a short list per core entity type in code, and leave exclude_id for site specific fine tuning.

Proposal for the generic part:

  • Entity keys: id, uuid, revision, langcode, published, default_langcode, revision_translation_affected
  • Revision metadata keys (already includes revision_default)
  • Field types: password, path, language, ui_patterns_source
  • Per entity type: node: promote, sticky. user: init, mail. comment: thread, hostname, entity_type, field_name. Plus weight by name.
mogtofu33’s picture

Status: Needs work » Needs review

Follow-up of the discussion in #3624220: Add ui_patterns_source_choices hook: proposed there, an alter hook on the source choices, e.g. hook_ui_patterns_source_choices_alter(). UI Patterns stays generic, consumers decide.

If accepted, !429 changes shape:

  • The rules move from BlockLibrarySources to a hook implementation in UiPatternsHooks. BlockLibrarySources goes back to exclude_id and provider fine tuning only.
  • Same rules for the block library and the contextual form (UI Patterns field select), no class swap.
  • No filtering in a Views context: ID, status or language are often wanted in a listing.
  • A kernel test on a real entity_field source instead of mocks.

The fixes listed above (weight, language, password, ui_patterns_source, storage definitions) still apply, wherever the rules live.

pdureau’s picture

Status: Needs review » Needs work

Published: the code hides it which is right for Display Builder: status is not something to display. You missed it in your table.

I kept it in purpose, because i was picturing displays rendering the published status as an icon or a label.

Weight: 'weight' => 'boolean' never matches, term and menu link weight are integer. So Weight is still listed.

👌 It was done for reorderable entity types like Taxonomy Term, i will add a comment.

Language: langcode is hidden by its key, but preferred_langcode and preferred_admin_langcode on user stay, and other entities have their own. Hiding the language field type covers all of them.

Done in purpose too. preferred_langcode and preferred_admin_langcode are "business" fields, telling stuff about the user, not internal/technical fields to make the translation API work. I would advice to keep it, administrators building User entity displays are supposed to be knowledgeable site builders.

Crash: field_display_full and field_display_teaser are still listed. They are ui_patterns_source fields, the recursion crash of this issue. Hiding that field type covers them whatever their name.

👌 The logic address only base fields now, but i can add a check by type, which will be useful for this use case.

Possible fatal: $definitions[$field]->getType() assumes the base field exists. Using getFieldStorageDefinitions() with a null check avoids it, and also sees config fields.

👌 OK, will do

The test is still named testHideNotDisplayConfigurableFields.

👌 Ok, will rename.

Password: HIDE_CHOICE_ID only matches field:user:user:pass, the bundle-less field:user::pass (Views context) leaks. Hiding the password field type covers both, and any custom entity.

👌 Ok, this is the opportunity to merge the 2 mechanisms.

Per entity type: node: promote, sticky. user: init, mail. comment: thread, hostname, entity_type, field_name.

I would like to avoid checks by entity types, to not open the Pandora box. I will check comment entity type anyway.

Email is also personal data, so I would hide it by default.

Is it the scenario are we trying to avoid?

  • Someone with "Administer account settings" and "View user information" permissions but not with "Administer users" or "View user email addresses permission wants to know the mail of an user
  • They add the email field in a display and publish (they need to publish because they are working with a fake entity while building)
  • They look in the user page to get the email
  • They rollback the display to the previous state
mogtofu33’s picture

I do not have precise scenario in mind, I see more the frontend building part and not really admin yet, because of postponed #3575891: Allow administration page display.

I was defensive until we are clear on admin pages, but I can revise that based on Drupal core and Views which allow to do it and the responsibility is on the builder.
As we are not opening something not possible before, then it's ok for me to not be so defensive. At the end only password is critical, other are more UX disturbance, and the crashing ones are handled so it's ok.

pdureau’s picture

Assigned: pdureau » mogtofu33
Status: Needs work » Needs review

As we are not opening something not possible before, then it's ok for me to not be so defensive.

I didn't see your comment and I have hidden email in my update: https://git.drupalcode.org/project/display_builder/-/merge_requests/429/...

Entity keys: id, uuid, revision, langcode, published, default_langcode, revision_translation_affected

All covered except published because I can picture uses case when we want to add a renderable (a chip, a macaron..) to show the content publishing status. But I may be wrong and it is easy to add.

Revision metadata keys (already includes revision_default)

Already the case, I guess.

Field types: password, path, language, ui_patterns_source

Done.

Per entity type: node: promote, sticky. user: init, mail. comment: thread, hostname, entity_type, field_name.

Mail hidden. Others kept.

Plus weight by name.

Covered.

mogtofu33’s picture

Assigned: mogtofu33 » pdureau

Pushed some fixes on top of !429, tested on every viewable content entity type.

Config fields were not hidden without a bundle

checkField() used getFieldDefinitions($type, $bundle). On a derivative without a bundle, like field:node::field_display_full in a Views context, the bundle is an empty string and that call returns the base fields only, so no configurable field was ever found. The override fields were still offered there, which is the recursion crash of this issue.

Now the type check uses the base field definitions merged with the storage definitions, which cover every bundle.

A trap while doing it: storage definitions leave out the computed base fields, so path was no longer found and "URL alias" came back in the library. The merge with the base fields is what keeps it hidden, and there is a comment about it.

weight

Changed to 'weight' => 'integer'. Checked on a full site: the only core content entities with a weight base field are taxonomy_term and menu_link_content, both integer, so the entry matched nothing. The type guard still prevents the rule from firing on another field of the same name.

Possible warning

$definitions[$field]?->getType() still emits "Undefined array key" when the key is absent: only the call is guarded, not the lookup. Wrapped both lookups with ?? NULL.

Cleanup

$bundle became unused, so it is gone from checkField(). In isHiddenField() the list destructuring with an empty slot was replaced by named parts, which also removes the substr_count() guard: with fewer than four segments the field name is empty, which the same condition catches.

Tests

testHiddenFields now covers each case: the integer weight, the computed path, the password, and a ui_patterns_source field with and without a bundle. A kept field_weight guards against the type rule firing on a name with no base field. The mock builder gained the storage definitions.

Result on a Bootstrap recipe, no override field, no URL alias, no weight, no pass, in both contexts: node:article 15 choices with a bundle and 17 without, user 13 and 13, taxonomy_term 5 and 5, media 10 and 14, comment 17 and 17.

pdureau’s picture

Assigned: pdureau » mogtofu33
Status: Needs review » Reviewed & tested by the community

Changed to 'weight' => 'integer'.

Oopsy. Thanks.

mogtofu33’s picture

Assigned: mogtofu33 » Unassigned
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.

  • mogtofu33 committed c7eaba01 on 1.0.x authored by pdureau
    task: #3620150 Block library content filtering
    
    By: mogtofu33
    By:...
pdureau’s picture

I can't believe I removed published base field in one of my commits 🤦

Status: Fixed » Closed (fixed)

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