Overview

#3545859: Add a `host-entity-url` prop source for linking to the host entity introduced HostEntityUrlPropSource but it supports only the host entity. However, there are many valid use cases to link to entities that are other than the host entity; for example, if you are rendering tags, you may want to link to the taxonomy term canonical page to list more content with the same tag or you may want to link to the users profile.

The end user goal makes sense, the proposed technical implementation does not, and led this issue into some chaos due to how #3545859: Add a `host-entity-url` prop source for linking to the host entity landed. Fortunately, we quickly recovered from the chaos by having conversations between @Wim Leers, @lauriii, @phenaproxima and @effulgentsia. 🤝

Proposed resolution

Expand this feature to ensure all entities that may be accessed in content templates, not just the host entity, can be used in link props.

See #3545859-48: Add a `host-entity-url` prop source for linking to the host entity.

User interface changes

Tested while editing the ContentTemplate for article Nodes, using the type: string, format: uri-reference prop in the "all props" test SDC:

Before
After

Issue fork canvas-3555413

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

lauriii created an issue. See original summary.

phenaproxima’s picture

Well, wait a sec. Wouldn't that just be a normal entity reference field-powered link? Surely that would be done with a dynamic prop source (perhaps with an adapter on top of it), since those can target any property of any field at any level of a reference chain.

Don't get me wrong, we do need the ability to link to referenced entities for sure, but I don't think that's the intention of HostEntityUrlPropSource.

wim leers’s picture

  1. 🙏 Please link to related/relevant issues next time. This is essentially a dangling issue right now. I shouldn't have to go and fix all issue metadata to convey 1) what feature/functionality this is part of, especially if it's a feature you've prioritized (→ #3541000: [META] Content templates UI for 1.0: only nodes, no exposed slots, no replacement for the view mode/display UI), 2) what prior issue introduced the thing that you think is incomplete but which lacked sufficient precision in expectations/requirements (→ #3545859: Add a `host-entity-url` prop source for linking to the host entity).

    Did that for you this time.

  2. Also, this is not part of RC2, because the second MR for #3545859: Add a `host-entity-url` prop source for linking to the host entity literally landed yesterday so can't be part of RC2. Plus, until #3555068: Linking a `HostEntityUrlPropSource` to populate a `type: string, format: uri|uri-reference` in a `ContentTemplate` has no effect is fixed, this actually won't work at all in the ContentTemplate UI.
  3. ⚠️Note that this means we'll need to rename HostEntityUrlPropSource to EntityUrlPropSource, because it would no longer be limited to the host entity.
  4. There's way too much handwaving in this issue. Exactly the kind of handwaving that causes this issue to exist at all, because if #3545859: Add a `host-entity-url` prop source for linking to the host entity had been more precise, this follow-up would've been avoided 😬

    The number of ambiguities in

    Expand this feature to ensure all entities that may be accessed in content templates, not just the host entity, can be used in link props.

    is staggering:

    1. Depth/level of indirection for suggested entity URLs: I think you mean only URLs for the entities referenced directly by the host entity? So, for example, assuming we're looking at the content template for some Node bundle:
      - suggest URL to the author (User) of a Node (1 reference), but NOT URL to the user picture of the author (User) of the Node (2 references)
      - suggest URL to the "primary topic" (Term) of a Node (1 reference), but NOT URL to the "resident expert" (User) of a "primary topic" (Term) of a Node (2 references)

      Or did you really want the entity URL to be available at every one of those levels? (Which your phrasing seems to imply.)

    2. If the answer to the above is "yes, every level", then what about the levels you previously asked to omit? You requested in #3547598: Refine API response with DynamicPropSource suggestions to provide better UX that we hide/omit the "intermediary Files", which also means linking to it becomes harder. Or did you want for a media entity both the "media item canonical URL" and "media item image file canonical URL" to be surfaced?
    3. Speaking of that: what do you expect the labels to become? No guidance existed for this in #3545859: Add a `host-entity-url` prop source for linking to the host entity (because it landed with zero product requirements or design), so I did the simplest possible thing that would clearly need to be improved later: I labeled it Canonical absolute URL — because it's factually true, and because it'd guarantee that we'd improve it later. But if many such suggestions would start appearing, then it'd become a problem, doubly so if to the prior point you'd answer that you expect both suggestions (which would appear as siblings) to get a unique label.
    4. What about multiple cardinality? The typical "Tags" field is multiple-cardinality, and so it's impossible to offer a single URL for it. (This is basically the same problem as #3522718: [later phase] [needs design] UX for associating mismatched cardinality field instance (too little or too much) with a higher cardinality SDC prop (e.g. `type: array, maxItems: 5`) or lower cardinality (e.g. `type: string`).)
      EDIT: or, I guess you would've expected this to return an array of links (type: array, items: {type: string, format: uri|uri-reference}? Even though that's still not yet supported for the much simpler "static value" case: #3546869: Editing support for `type: array`: support multiple-cardinality Field Widgets (`StaticPropSource`s).)
    5. The intent is per #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options that you would get multiple choices — right now it's limited to "the canonical absolute URL", but in the future it might be with/without path alias, other link relations (e.g. the "edit form"), etc. How will what you ask scale to support all of those?

    … I'm pretty sure there's more things still, but those are the immediate ones that come to mind.

🙏🙏🙏🙏 Please create a mock-up of what you expect, such as the one in #3547598-23: Refine API response with DynamicPropSource suggestions to provide better UX. Please think through all the above aspects and indicate what is in scope vs what is not.

wim leers’s picture

d.o's "cross-posting causes unpublishing" bug strikes again 🫣

wim leers’s picture

Issue tags: +stable blocker

@phenaproxima in #2:

Wouldn't that just be a normal entity reference field-powered link?

Conceptually, @lauriii is asking to make for every entity reference encountered (not sure if at the top level or also deeper, see my prior comment), we also offer a HostEntityUrlPropSource for the referenced entity.

\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::propertyDefinitions() does not have a property that generates a URL, so relying on the existing reference fields is insufficient.


🤔 But … perhaps that is reasonable to do? #3545859: Add a `host-entity-url` prop source for linking to the host entity did what it did because we didn't want to add a computed field to all of the entities. But for an entity reference to provide a canonical URL to the referenced empty … that seems kinda reasonable? Probably only the canonical and relative (so: type: string, format: uri-reference) one, and that'd be sufficient for 99% of cases?

(That would also be compatible with #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options, because you'd be unlikely to ever want to link to "the edit form of the referenced user/term" — that'd be more likely to only occur for the host entity itself.)

wim leers’s picture

FYI: tagged stable blocker because of

⚠️Note that this means we'll need to rename HostEntityUrlPropSource to EntityUrlPropSource, because it would no longer be limited to the host entity.

i.e. we shouldn't be doing such a significant change after 1.0. That'd be too disruptive.

lauriii’s picture

Assigned: lauriii » Unassigned
Issue tags: -Needs product manager review

Depth/level of indirection for suggested entity URLs: I think you mean only URLs for the entities referenced directly by the host entity? So, for example, assuming we're looking at the content template for some Node bundle:
- suggest URL to the author (User) of a Node (1 reference), but NOT URL to the user picture of the author (User) of the Node (2 references)
- suggest URL to the "primary topic" (Term) of a Node (1 reference), but NOT URL to the "resident expert" (User) of a "primary topic" (Term) of a Node (2 references)
Or did you really want the entity URL to be available at every one of those levels? (Which your phrasing seems to imply.)

I would expect this to inherit our existing limitations for level of depth, i.e. the link should be available for all of the entity references that are accessible there. So if user can map an image field to a user picture, they should be able to map a link field to the user pictures URL. Same is true for the resident expert for the primary topic.

If the answer to the above is "yes, every level", then what about the levels you previously asked to omit? You requested in #3547598: Refine API response with DynamicPropSource suggestions to provide better UX that we hide/omit the "intermediary Files", which also means linking to it becomes harder. Or did you want for a media entity both the "media item canonical URL" and "media item image file canonical URL" to be surfaced?

For images this is already working as expected. It doesn't look like mapping to media references is working yet but I would expect that it should be possible to link to the media item image file canonical URL.

Speaking of that: what do you expect the labels to become? No guidance existed for this in #3545859: Add a `host-entity-url` prop source for linking to the host entity (because it landed with zero product requirements or design), so I did the simplest possible thing that would clearly need to be improved later: I labeled it Canonical absolute URL — because it's factually true, and because it'd guarantee that we'd improve it later. But if many such suggestions would start appearing, then it'd become a problem, doubly so if to the prior point you'd answer that you expect both suggestions (which would appear as siblings) to get a unique label.

Could we just call it URL and Slug? They should not appear as siblings; they should appear under a child menu. For example, Image => URL or Authored by => URL.

lauriii’s picture

Status: Postponed (maintainer needs more info) » Active
wim leers’s picture

Assigned: Unassigned » lauriii
Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs product manager review

I would expect this to inherit our existing limitations for level of depth, i.e. the link should be available for all of the entity references that are accessible there. So if user can map an image field to a user picture, they should be able to map a link field to the user pictures URL. Same is true for the resident expert for the primary topic.

This will require a significant refactor, then:

  1. HostEntityUrlPropSourceEntityUrlPropSource and for anything except the host entity, we'd require an expression (that explains how to traverse the Typed Data tree), just like for DynamicPropSource
  2. the logic to suggest HostEntityUrlPropSource today is stupid simple, because it doesn't need to traverse Typed Data. That will need to be refactored to share a subset of that infrastructure with DynamicPropSource's matching logic.

You casually say "inherit our existing limitations of level of depth", but this is quite complex to achieve, because these URLs that are offered to users are NOT part of Typed Data! If on #3545859: Add a `host-entity-url` prop source for linking to the host entity these product requirements had been conveyed, I would never even have suggested this new prop source type! 😬 Because if we expect such close integration, just adding new computed fields would be a LOT simpler 😅 In fact, it might still be preferable to backtrack/revert our prior decision then…

Do you expect this all to be done before 1.0, or after?

Could we just call it URL and Slug? They should not appear as siblings; they should appear under a child menu. For example, Image => URL or Authored by => URL.

I think by "slug" you refer to path alias and by "URL" you refer to /node/<nid>? If so, please chime in on #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options for that.

But that's not what I was saying/asking there. I was asking specifically about the collapsing/simplifying you asked us to implement for File entities into the containing Media entity. Which starts to fall apart when there's then 2 URLs that are supposed to appear: how do you expect those to be labeled and ordered relative to other suggestions?

wim leers’s picture

So, to make #9 very concrete, I am proposing to remove HostEntityUrlPropSource and add computed fields to all entities instead:

IOW: why go through the pains of simulating the presence of entity URLs in the right place in the Typed Data tree (which is the path we're currently on) when we could much more simply just expose it as a computed field with multiple properties to offer multiple kinds of URLs?

— me, at #3545859-41: Add a `host-entity-url` prop source for linking to the host entity

wim leers’s picture

Postponed #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options and #3555068: Linking a `HostEntityUrlPropSource` to populate a `type: string, format: uri|uri-reference` in a `ContentTemplate` has no effect on #10, to avoid even more potentially wasted effort.

Meeting with @lauriii on Monday. Pinged @effulgentsia and @phenaproxima, who led the charge on landing #3545859: Add a `host-entity-url` prop source for linking to the host entity, and so should definitely chime in.

penyaskito’s picture

After reading this #10 makes sense to me. And if this needs to be solved for Drupal CMS, I wonder if providing that computed field should be the responsibility of canvas or a different specific contrib module.

wim leers’s picture

wim leers’s picture

Title: HostEntityUrlPropSource shouldn't be specific to the host entity » Allow linking to referenced entities: add `url` property to `EntityReferenceItem::propertyDefinitions()`
Assigned: lauriii » wim leers
Priority: Normal » Critical
Status: Postponed (maintainer needs more info) » Active
Issue tags: -Needs product manager review

Discussed yesterday with @lauriii, and then with @effulgentsia.

Conclusion: a hybrid between what we have in HEAD (we'd keep HostEntityUrlPropSource), but add a new computed field property to entity reference fields.

Details:

  1. Let's keep HostEntityUrlPropSource this issue added, only for the host entity. There is indeed a likely future need for e.g. linking to the edit-form, or with/without path processing (path aliases), etc. → we have #3551455: HostEntityUrlPropSource should be able to support absolute or relative URLs, URL options for that.
  2. Its position in the suggestions available to the Site Builder when constructing a ContentTemplate is fine as-is (at the very top of the root level — only if the target prop shape is type: string, format: uri|uri-reference, of course).
  3. Let's add a url field property to \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::propertyDefinitions(), and make it only ever return the canonical URL. This would then simply reuse the existing Typed Data-based DynamicPropSource matching+suggesting logic — no changes needed.
  4. Rationale: a clash of field names (which we'd need to add if reverted HostEntityUrlPropSource and added a computed field instead) is far more likely than adding a computed field property. Any module could be altering base fields, but only one module can alter a field type's properties, which also exceedingly few modules do. This would mean it is feasible to bring this part into core. (Whereas making core auto-add a new (computed) base field to all (content) entity types would be fairly unlikely.)

#3545859-48: Add a `host-entity-url` prop source for linking to the host entity

AFAICT this addresses all concerns by both @lauriii from a product POV and the technical/feasibility concerns I raised here 😊

MR up with a first commit that starts building this. The first commit gets us to the point where indeed all entity reference fields are suggested, including nonsensical ones:

🐛 REQUIRED, type=string&format=uri-reference — either extraneous field instance matches found, or missing expectations
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
-Array &0 []
+Array &0 [
+    2 => 'ℹ︎␜entity:media:baby_videos␝bundle␞␟url',
+    5 => 'ℹ︎␜entity:media:press_releases␝bundle␞␟url',
+    6 => 'ℹ︎␜entity:media:vacation_videos␝bundle␞␟url',
+    13 => 'ℹ︎␜entity:node:foo␝marketing_docs␞␟entity␜␜entity:media␝bundle␞␟url',
+    14 => 'ℹ︎␜entity:node:foo␝marketing_docs␞␟url',
+    19 => 'ℹ︎␜entity:node:foo␝media_video_field␞␟entity␜␜entity:media␝bundle␞␟url',
+    20 => 'ℹ︎␜entity:node:foo␝media_video_field␞␟url',
+    21 => 'ℹ︎␜entity:node:foo␝type␞␟url',
+]
wim leers’s picture

Assigned: wim leers » Unassigned
Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new101.51 KB
new162.12 KB

It works.

Tested while editing the ContentTemplate for article Nodes, using the type: string, format: uri-reference prop in the "all props" test SDC:

Before
After
wim leers’s picture

Issue summary: View changes
phenaproxima’s picture

Status: Needs review » Needs work

I have almost nothing left to complain about; just moving a couple of comments around, and adding a little more high-level explanation to one thing. But otherwise, I say SHIP THIS CHUNGUS!

lauriii’s picture

Testing this and this is definitely a really nice improvement already! A bit of feedback / questions:


  1. Images seem to introduce some interesting. It looks like we could map the image to "image" but there's also URI and root-relative URL. What's the difference between these different mappings?

  2. This might be something for a follow-up but I don't think we should be exposing the author of the file entity in the UI at all.
  3. Why does the URL show up on the second level, i.e. Authored by => URL, and not on the third level inside the entity, i.e. Authored by => User => URL?
wim leers’s picture

Assigned: Unassigned » lauriii
Status: Needs work » Needs review
  1. That is best explained by quoting the test coverage, which makes this very explicit:
                'Silly image 🤡 → URI → Root-relative file URL' => 'ℹ︎␜entity:node:foo␝field_silly_image␞␟entity␜␜entity:file␝uri␞␟url',
                'Silly image 🤡 → URI' => 'ℹ︎␜entity:node:foo␝field_silly_image␞␟entity␜␜entity:file␝uri␞␟value',
                "Silly image 🤡" => 'ℹ︎␜entity:node:foo␝field_silly_image␞␟src_with_alternate_widths',
    
    1. The first ("URI → root-relative file URL"): the image field's reference to the File entity is followed, then the "url" property on the "uri" field on the File entity is retrieved.
    2. The second ("URI"): the image field's reference to the File entity is followed, then the "value" property on the "uri" field on the File entity is retrieved — aka the main property on that field, which is why there's one less level in the hierarchy for this one.
    3. The third ("just the image field itself"): the image field's src_with_alternate_widths computed property that Canvas added.

    You can only see all 3 if you have an SDC/code component prop with a very open-ended prop, because the second one returns a public:// URI. but, arguably, it does not make sense to expose "root-relative file URL" for an image field, either, because src_with_alternate_widths is likely preferable. Except … if you want to link to the original image, because then the query string is just noise.

    ⚠️ This is confusing because of the simplification you requested previously: the omitting of intermediary levels (which I referred to in #3.2). Because if it'd said "Image → File → URI → Root-relative file URL", it'd have been much clearer that "URI" was a field on the File entity. This is tricky to get just right from a UX/Product POV 😬

    Proposal: special-case the "URI" field on the File entity type to always show the specific description of the field property, even for the main property. And possibly also change the label of that base field to "File URI". Which would then result in:

                'Silly image 🤡 → File URI → File URL' => 'ℹ︎␜entity:node:foo␝field_silly_image␞␟entity␜␜entity:file␝uri␞␟url',
                'Silly image 🤡 → File URI → Stream wrapper URI ' => 'ℹ︎␜entity:node:foo␝field_silly_image␞␟entity␜␜entity:file␝uri␞␟value',
                "Silly image 🤡" => 'ℹ︎␜entity:node:foo␝field_silly_image␞␟src_with_alternate_widths',
    

    What do you think?

  2. That's an excellent addition for #3551339: Suggest only relevant DynamicPropSources — where I'd been waiting for exactly this kind of product decision 😊👍
  3. Because "Authored by" is the label of the entity reference field pointing to the User entity, and it's the entity reference field itself that provides the new url property. To achieve Authored by => User => URL, we'd need to add a computed base field to all entity types, which is exactly the thing that is so problematic: likely clashes with contrib/custom modules, as @effulgentsia and I explained at #3545859-48: Add a `host-entity-url` prop source for linking to the host entity.
phenaproxima’s picture

Assigned: lauriii » Unassigned
Status: Needs review » Reviewed & tested by the community

This is great, and useful, and clear as a bell. I'm shipping it with my newfound commit powers! Mwahahahahahaha!!!

phenaproxima’s picture

Assigned: Unassigned » wim leers

Okay, looks like I can't merge this without it being approved by the correct code owners. Boo!

effulgentsia’s picture

RTBC+1. I approved for the code groups that I'm a code owner of but that still leaves a couple groups that need approval.

larowlan’s picture

Left two comments that I think can likely be followups given this is RTBC and critical

lauriii’s picture

StatusFileSize
new89.23 KB

#20.1I think with the improved labels it would have made sense to me what was being offered there. FWIW, I was testing this with code components so maybe the prop type in code components should be setting stronger restrictions for the stream wrapper URL to not be offered?

#20.2: Will add it there 👍

#20.3: I see. 👍 I don't think the current solution of displaying the URL outside of the entity is perfect but I'm not convinced it's bad enough to warrant significant rework. It's a bit strange when you see both levels there but people will probably just figure it out.

wim leers’s picture

@phenaproxima & @effulgentsia: thanks both!

@larowlan in #24:

Left two comments that I think can likely be followups given this is RTBC and critical

Addressed both 😊 Keeping technical debt/issue queue sprawl under control by spending 15 mins addressing those here is worth it IMHO 👍

@lauriii in #25:

  1. The code component editor is correct, it's just coarse. I think we need to expand the code component editor UI (and the JSON schema for props that code components are allowed to have) to allow for optional granularity. This is especially true for links. Because the hyperlink is literally the foundation of the web, and it's also literally what a CMS is about: managing content and relations between them. So it's important that the author of a code component can actually specify what kind of link they expect. Because only then can Canvas offer relevant links!

    This is what that UI currently looks like, annotated to convey what the coarse prop shapes are that you're allowed to pick:

    👉 Detailed proposal in new issue: #3556144: Code components "link" props can only be coarse, resulting in irrelevant shape matches when building `ContentTemplate`s

  2. 👍
  3. I believe here too labels could make all the difference. Instead of just "URL", I could change it to "Entity URL" or even something relevant to the target entity type: "User URL", "Content URL", etc.
wim leers’s picture

Assigned: wim leers » Unassigned

This is now finally fully ready.

Note that manually testing this in a ContentTemplate when the referenced entity is inaccessible reveals that the resulting NULL value causes an SDC render crash.

The reason: \Drupal\Core\Theme\Component\ComponentValidator::validateProps() requires optional SDC props to be ABSENT or have a valid value. So what is NOT allowed is a value sometimes resolving to a a string (e.g. node 5's referenced entity is accessible ⇒ result = URL) and sometimes not (e.g. node 6's referenced entity being inaccessible ⇒ result = NULL).

We've already got #3541361: Find optional field instance matches for `type: object` props (images + videos), including for optional fields on bundleless entity types (e.g. `User`'s `user_picture`) for that, and that already has test infrastructure for it. So keeping that out of the scope here — this MR is already plenty complex as-is 😅

phenaproxima’s picture

Gave this another look. The comments make fairly clear what's going on here, and the MaybeUrl value object makes sense as something we can adapt in the future if we run into a similar need for a "this could a useful value, or it could NULL because of an access problem" case. I appreciate the added clarity (in the class doc comment) about how Evaluator will handle a required value that fails to produce a value. These are really complex problems, and I hope that core will be able to improve the status quo over time, but for now...

...ship it.

  • wim leers committed 00b1ef4c on 1.x
    [#3555413] feat: Allow linking to referenced entities: add `url`...
wim leers’s picture

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.

penyaskito’s picture

#27 would also be alleviated if #3531905: Validation error on optional properties. lands

wim leers’s picture

mayur-sose’s picture

StatusFileSize
new580.58 KB

Verified below scenarios on 1.x :

Please check TC05, failed test case.

ID Test Scenario Steps Expected Result Pass/Fail
TC01 Can select and link to the url property of a referenced entity in the ContentTemplate UI
  1. Edit or create a ContentTemplate for Article nodes.
  2. Add/use a component (e.g., all-props SDC) with a prop: type: string, format: uri-reference.
  3. Attempt to link this prop to a referenced entity’s url property (e.g., link to a related node or media item).
The url property of referenced entity is available as a selectable/assignable option in the field/link UI. Pass
TC02 Linked prop correctly pulls the URL of the referenced entity
  1. After TC01, preview or render the template on a sample article with a referenced entity.
  2. Inspect the rendered component or the prop value.
The prop displays or outputs the actual URL of the referenced entity (e.g., correct node/media/file link appears or is generated). Pass
TC03 UI displays linked entity's URL as property value
  1. After linking as in TC01, open prop editor or details UI.
  2. Observe what value is shown for the prop.
The linked value is the resolved URL of the referenced entity, shown in preview/details/field as expected. Pass
TC04 Changing the referenced entity updates the linked prop’s URL
  1. Change the article’s referenced entity (e.g., select a different related item).
  2. Reload/preview the template/component.
The prop updates to the new referenced entity’s URL.

Output updates accordingly.
Pass
TC05 Regression: Other, non-url reference properties remain linkable as before
  1. Attempt to link the same prop to other supported fields/properties on the referenced entity.
After selecting “Canonical absolute URL“ In console 500 error throwing. Fail
wim leers’s picture

TC05 (selecting Canonical absolute URL) is explicitly not in scope of this issue.

The fix for that landed in the past hour: #3555068: Linking a `HostEntityUrlPropSource` to populate a `type: string, format: uri|uri-reference` in a `ContentTemplate` has no effect.

Status: Fixed » Closed (fixed)

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