Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow embedding of content within document body #2813

Merged
merged 4 commits into from
Aug 7, 2024
Merged

Conversation

callumknights
Copy link
Contributor

@callumknights callumknights commented Jul 26, 2024

This allows content items to be embedded within other content, using the syntax {{embed:CONTENT_TYPE:UUID}} in the body field.

There are a number of assumptions here:

  • The content being embedded must exist in a live state in the same language as the content it is being embedded within.
  • Only the title will currently be embedded. Future work will extend to include other fields.
  • Only the body fields can contain embedded content. Future work will extend to include other fields.
  • Only documents of type contact can be embedded. This can be extended by updating the SUPPORTED_DOCUMENT_TYPES constant.

This is based on the spike by @pezholio in #2794.

Trello card

@brucebolt brucebolt force-pushed the embedded-content-v2 branch 2 times, most recently from 3162f46 to 4b7d52e Compare July 30, 2024 07:22
@brucebolt brucebolt marked this pull request as draft July 30, 2024 07:40
@brucebolt brucebolt force-pushed the embedded-content-v2 branch 4 times, most recently from d89ac67 to bf0f03e Compare July 31, 2024 12:30
@brucebolt brucebolt force-pushed the embedded-content-v2 branch 3 times, most recently from 594b841 to 6f41863 Compare July 31, 2024 15:56
@brucebolt brucebolt changed the title Embedded content v2 Allow embedding of content within document body Aug 1, 2024
@brucebolt brucebolt marked this pull request as ready for review August 1, 2024 07:06
Comment on lines 29 to 34
embedded_editions = @edition
.links
.where(link_type: "embed")
.map(&:target_documents)
.map { |target_documents| target_documents.find_by(locale: @edition.locale) || target_documents.find_by(locale: Edition::DEFAULT_LOCALE) }
.map(&:live)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bit of an n+1 here I think. I'd expect this to make:

  • 1 query to get links.where(link_type: "embded")
  • n queries to get target documents for each link
  • n queries to get the live edition for each target document

This is probably fine for now, as n probably won't be large (as I don't expect documents to have that more than a few embeds).

I think you could do it in one query that gets all the possible locales, and then find the correct locales in ruby.

Something like:

edition_ids = @edition
  .links
  .where(link_type: "embed")
  .joins(target_documents: :live)
  .where(target_documents: { locale: locales })
  .select("editions.id")
# Note ^ the use of select rather than pluck here means this is still
# an `ActiveRecord_AssociationRelation`, rather than an array of ids,
# so it won't make a separate SQL query
  
embedded_editions = Edition.where(id: edition_ids)
# Generates:
# SELECT "editions".* FROM "editions"
# WHERE "editions"."id" IN (
#   SELECT "editions"."id" FROM "links"
#   INNER JOIN "documents" "target_documents"
#     ON "target_documents"."content_id" = "links"."target_content_id"
#   INNER JOIN "editions"
#     ON "editions"."content_store" = 'live'
#     AND "editions"."document_id" = "target_documents"."id"
#   WHERE "links"."edition_id" = 1
#   AND "links"."link_type" = 'embed'
#   AND "target_documents"."locale" IN ('cy', 'en')
# )

# Exercise for the reader: we'll have multiple editions where there are multiple locales. Get the best edition for the locales we want.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, you could use the same trick as we do here:

.order(order_by_clause("documents", "locale", locale_fallback_order))
which is complicated but presumably works...

Could even use Queries::GetEditionIdsWithFallbacks directly I guess, but you might end up making one or two unnecessary queries there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone for the approach of using Queries::GetEditionIdsWithFallbacks directly, as that seems to be the least complicated and DRY-est way of getting the embedded editions.

I think that approach should also make it easier for us to deal with embedding draft content (should we wish to) in the future.

@@ -61,7 +61,7 @@ def present_expanded_link(link_hash)
end

if hash[:details]
hash[:details] = Presenters::DetailsPresenter.new(hash[:details], nil).details
hash[:details] = Presenters::DetailsPresenter.new(hash[:details], nil, nil).details
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this means that we won't expand embed references inside linked body elements, so we might end up with things like:

{
  content_type: "person",
  # ...
  links: {
    role: [{
      content_type: "role",
      # ...
      details: {
        body: "Some text where we haven't expanded an embed: {{embed:contact:00000000-0000-0000-0000-000000000000}}"
        # ...
      }
    }]
  }
}

I think that's fine for now, maybe we'll revisit it later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That'a a good spot and a consequence I hadn't considered. It seems easy enough to expand the embedded content in the links, so we might as well do it now. Have added this as a separate commit.

Copy link
Contributor

@Gweaton Gweaton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me - have left a few comments re: naming, documentation and testing but I like the approach here.

app/services/embedded_content_finder_service.rb Outdated Show resolved Hide resolved
app/presenters/details_presenter.rb Show resolved Hide resolved
spec/presenters/content_embed_presenter_spec.rb Outdated Show resolved Hide resolved
spec/presenters/details_presenter_spec.rb Outdated Show resolved Hide resolved
@brucebolt brucebolt force-pushed the embedded-content-v2 branch 3 times, most recently from dc60892 to f230e9f Compare August 5, 2024 07:19
@brucebolt brucebolt requested a review from richardTowers August 5, 2024 09:03
Copy link
Contributor

@richardTowers richardTowers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of nitpicks, and I'm afraid one actual issue - edition.title is not the value we want for the replacements.

I'm inclined to merge this even with this issue, as it won't cause any harm since the feature won't be used. We'll need to have a bit of a discussion about the best way forward on how to render the HTML for contact embeds I think.


embedded_edition_ids = ::Queries::GetEditionIdsWithFallbacks.call(
target_content_ids,
locale_fallback_order: [@edition.locale, Edition::DEFAULT_LOCALE],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could do

Suggested change
locale_fallback_order: [@edition.locale, Edition::DEFAULT_LOCALE],
locale_fallback_order: [@edition.locale, Edition::DEFAULT_LOCALE].uniq,

here to avoid passing in %w[en en] in the common case where these are both en (although I presume that wouldn't matter at all)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot, I'll use uniq here.

target_content_ids = @edition
.links
.where(link_type: "embed")
.map(&:target_content_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think you could do

Suggested change
.map(&:target_content_id)
.pluck(:target_content_id)

which would avoid allocating Link objects for each link and then calling #target_content_id on each one (instead just returning the content ids directly from the SQL query.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, will update this.

embed_code = embedded_content_references_by_content_id[embedded_edition.content_id].embed_code
content = content.gsub(
embed_code,
embedded_edition.title,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think this is actually not the right field on contact to be replacing things with. title will be something like "Government Digital Service", rather than "10 Whitechapel High Street" like we probably want.

The way Whitehall does it is:

🤔 we could merge this with it just using title, but it's probably not actually what we want. On the other hand, I don't think publishing-api having a bunch of knowledge of how to render addresses is desirable - I'd be somewhat resistant to copying the templating code over from Whitehall.

I think the simplest option I can think of would be to add an "embeddable_html" field to Contact when Whitehall presents Contacts to publishing-api, and then use that. That way Whitehall can continue doing the rendering of Contact HTML (even though that's not very nice either), and publishing-api doesn't need to get rendering app capabilities.

Longer term with the design of this feature, I wonder if this is a case for passing the {{embed:...}} syntax all the way through to the frontend, and having the rendering app do the rendering of contact data into HTML (having followed the expanded link).

I'm sort of leaning towards getting this PR merged and then fixing this in a subsequent PR though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the embeddable_html idea. I struggled with this option in my initial spike too. I agree that we probably should have the rendering app do the actual rendering, but I imagine this will be tricky.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll leave this as title for now (on the basis that no-one will actually be embedding anything into live content) but will write up a card to implement embedded_html in the schemas and include that in the document instead.

Longer term with the design of this feature, I wonder if this is a case for passing the {{embed:...}} syntax all the way through to the frontend, and having the rendering app do the rendering of contact data into HTML (having followed the expanded link).

This makes a lot of sense to me. Particularly as the frontend app is better placed to do any conversion to HTML (rather than the current situation of the publishing app or Publishing API doing this, then any frontend changes needing all content to be republished).

brucebolt and others added 4 commits August 7, 2024 11:46
This will be used in later commits to allow us to find embedded content
within a block of content.

Co-authored-by: Callum Knights <[email protected]>
Co-authored-by: Richard Towers <[email protected]>
Co-authored-by: Stuart Harrison <[email protected]>
Embedded content within the `body` of a document needs to be presented
to Content Store after being substituted with the actual content.

Therefore adding functionality to the details presenter to do this.

Co-authored-by: Callum Knights <[email protected]>
Co-authored-by: Richard Towers <[email protected]>
Co-authored-by: Stuart Harrison <[email protected]>
When a publishing application sends a PUT content request to Publishing
API, we need to parse the body for embedded content and add these to the
links for the edition.

Depends on #2819.

Co-authored-by: Callum Knights <[email protected]>
Co-authored-by: Richard Towers <[email protected]>
Co-authored-by: Stuart Harrison <[email protected]>
When expanding links, any embedded content within the body of the linked
item should also be expanded to include the full value.
@brucebolt brucebolt force-pushed the embedded-content-v2 branch from f230e9f to 0cf721c Compare August 7, 2024 10:52
@brucebolt brucebolt merged commit e96a7d1 into main Aug 7, 2024
12 checks passed
@brucebolt brucebolt deleted the embedded-content-v2 branch August 7, 2024 12:12
tahb added a commit that referenced this pull request Aug 28, 2024
This will set up `details` in and a `link` in an expected state, as set out in the support of embedded content[1].

[1] #2813
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants