From 5d0f6fa985177f73df32f5ac4b95bac16304aa9e Mon Sep 17 00:00:00 2001 From: pezholio Date: Mon, 11 Nov 2024 11:05:02 +0000 Subject: [PATCH 1/4] Add ADR to move rendering logic to external gem --- ...t-block-rendering-logic-to-external-gem.md | 92 ++++++++++++++++++ docs/arch/images/live-preview-1.png | Bin 0 -> 30828 bytes docs/arch/images/live-preview-2.png | Bin 0 -> 33872 bytes 3 files changed, 92 insertions(+) create mode 100644 docs/arch/adr-007-move-content-block-rendering-logic-to-external-gem.md create mode 100644 docs/arch/images/live-preview-1.png create mode 100644 docs/arch/images/live-preview-2.png diff --git a/docs/arch/adr-007-move-content-block-rendering-logic-to-external-gem.md b/docs/arch/adr-007-move-content-block-rendering-logic-to-external-gem.md new file mode 100644 index 000000000..16d9cda54 --- /dev/null +++ b/docs/arch/adr-007-move-content-block-rendering-logic-to-external-gem.md @@ -0,0 +1,92 @@ +# Decision Record: Move Content Block rendering logic to external gem + +## Context + +Content blocks within Publishing API are a relatively new concept, that mainly sits with the Content +Modelling Team. Whilst still in development, the basic concept is that a publishing user will be able +to embed a "block" of reusable content within a document, and it will then be rendered within the +document on publication. When the content of the block changes, all pages that use the block will +change to show the latest information. + +When a piece of content that uses a content block (or content blocks) is sent to the publishing API, +we first scan over each field within the content to find embed codes, which currently look like this: + +```markdown +{{embed:$embed_type:$uuid}} +``` + +Where `$embed_type` is the type of content to be embedded and `$uuid` is the content ID of the content +block. + +If there are any embed codes found, these are added as "links" to the content within Publishing API +([See here for more information about links and link expansion](https://github.com/alphagov/publishing-api/blob/main/docs/link-expansion.md)). + +When the piece of content is sent to the Content Store (either draft or live), we scan the content +again for embed codes, and replace the embed code with the actual content block content. + +At the moment, this is fairly simple, contacts are replaced with the Title (as contacts work is +still a work in progress) and email addresses are replaced with the email. However, as this work +progresses, it is envisioned that embedded content rendering will get significantly more complex. + +Additionally, there are places where we need access to the embed logic outside of Publishing API: + +### Whitehall Live Preview + +At the moment, when a user clicks on "Preview" within Whitehall, it takes the content from the +body field, and uses the Govspeak gem (and some additional Whitehall-specific logic) to +render the Govspeak. + +Currently, Whitehall or Govspeak does not understand the embed code, so embed codes are left +untouched: + +![Whitehall editor body field showing link to preview button](./images/live-preview-1.png) + +![Live preview showing embed code left inplace](./images/live-preview-2.png) + +We therefore need a way to handle the embed codes within live preview + +### "In-app" preview within Content Block Manager + +When editors are making changes to content blocks within the Content Block Manager, it's important +for users to be able to see the changes they make within the context of the pages that use the +blocks. + +We can't use the existing draft stack because: + +a) We want to show the content block changes in the context of the live documents; and +b) Publishing content block changes to the draft stack could cause confusion when a user +is previewing the changes to their own content and seeing unpublished changes to a content block + +With this in mind, we are proposing that we fetch the HTML of live content and then run a +find/replace against the HTML of a content block with the HTML of the current draft, then rendering +this HTML within our app. + +Both of these solutions require each application having knowledge of how a content block will be +rendered. + +Initially we considered having an API endpoint within Publishing API to "preview" a content block. +However, this was discounted because: + +a) We run the risk of overloading the Publishing API with extra API endpoints +b) There is not much rendering logic in Publishing API itself, and logic will potentially get more +complex as content blocks evolve +c) If there are a number of content blocks within a page, this may cause a performance issues when +making multiple calls to an API + +## Decisions + +We are proposing moving the Content Block rendering and parsing logic to a new [content_block_tools gem](https://github.com/alphagov/content_block_tools) + +This will give a single source of truth for how content blocks are rendered, which can be easily pulled +into other applications. + +## Consequences + +Once the work to move the logic into the gem is approved, we will then need to add content_block_tools as +a dependency in Publishing API to ensure the gem is the only source of truth. + +We will then be able to carry out the two pieces of work as detailed above. They are both contained within +the Whitehall application, which already has govuk_publishing_components as a dependency. + +One possible downside is we will need to release a new version of a gem and ensure it is used in each application +when a change occurs, but as we don't expect to be making changes regularly, we believe this risk is minimal. diff --git a/docs/arch/images/live-preview-1.png b/docs/arch/images/live-preview-1.png new file mode 100644 index 0000000000000000000000000000000000000000..825781bc0174f98196b9af048dab811f0c1ec9ac GIT binary patch literal 30828 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 D#3uP| literal 0 HcmV?d00001 From 557a70d9b13fea7fb33869572338adca016ae6db Mon Sep 17 00:00:00 2001 From: pezholio Date: Tue, 19 Nov 2024 10:40:44 +0000 Subject: [PATCH 2/4] Add `content_block_tools` gem --- Gemfile | 1 + Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 1ea1cccf3..32cb0279d 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ gem "rails", "7.2.2" gem "aws-sdk-s3" gem "bootsnap", require: false gem "bunny" +gem "content_block_tools" gem "dalli" gem "fuzzy_match" gem "gds-api-adapters" diff --git a/Gemfile.lock b/Gemfile.lock index 24dcc708f..37513c62f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -119,6 +119,8 @@ GEM climate_control (1.2.0) concurrent-ruby (1.3.4) connection_pool (2.4.1) + content_block_tools (0.2.0) + actionview (= 7.2.2) crack (1.0.0) bigdecimal rexml @@ -868,6 +870,7 @@ DEPENDENCIES bunny byebug climate_control + content_block_tools dalli database_cleaner factory_bot_rails From c8750f620b749c842bb2bc500e863f1abaa343a7 Mon Sep 17 00:00:00 2001 From: pezholio Date: Mon, 18 Nov 2024 10:14:27 +0000 Subject: [PATCH 3/4] Use gem to find references in a document --- app/services/embedded_content_finder_service.rb | 8 +------- spec/services/embedded_content_finder_service_spec.rb | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/services/embedded_content_finder_service.rb b/app/services/embedded_content_finder_service.rb index f56a91643..1c334b498 100644 --- a/app/services/embedded_content_finder_service.rb +++ b/app/services/embedded_content_finder_service.rb @@ -1,10 +1,4 @@ class EmbeddedContentFinderService - ContentReference = Data.define(:document_type, :content_id, :embed_code) - - SUPPORTED_DOCUMENT_TYPES = %w[contact content_block_email_address].freeze - UUID_REGEX = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/ - EMBED_REGEX = /({{embed:(#{SUPPORTED_DOCUMENT_TYPES.join('|')}):#{UUID_REGEX}}})/ - def fetch_linked_content_ids(details, locale) content_references = details.values.map { |value| find_content_references(value) @@ -21,7 +15,7 @@ def find_content_references(value) when Hash value.map { |_, v| find_content_references(v) }.flatten.uniq when String - value.scan(EMBED_REGEX).map { |match| ContentReference.new(document_type: match[1], content_id: match[2], embed_code: match[0]) } + ContentBlockTools::ContentBlockReference.find_all_in_document(value) else [] end diff --git a/spec/services/embedded_content_finder_service_spec.rb b/spec/services/embedded_content_finder_service_spec.rb index 8f6e4a304..2a29910f9 100644 --- a/spec/services/embedded_content_finder_service_spec.rb +++ b/spec/services/embedded_content_finder_service_spec.rb @@ -111,7 +111,7 @@ RSpec.describe EmbeddedContentFinderService do describe ".fetch_linked_content_ids" do - EmbeddedContentFinderService::SUPPORTED_DOCUMENT_TYPES.each do |document_type| + ContentBlockTools::ContentBlockReference::SUPPORTED_DOCUMENT_TYPES.each do |document_type| include_examples "finds references", document_type end From 523fc7821d954e8262d028d4a4c0505cbd52fe2c Mon Sep 17 00:00:00 2001 From: pezholio Date: Mon, 18 Nov 2024 11:43:34 +0000 Subject: [PATCH 4/4] Use ContentBlockTools when rendering This ensures there is one source of truth for how content blocks are rendered, and it can be used in other applications easily. --- app/presenters/content_embed_presenter.rb | 13 ++- .../content_with_embedded_content_spec.rb | 41 ++-------- .../content_embed_presenter_spec.rb | 81 +++++++++---------- spec/presenters/details_presenter_spec.rb | 6 +- .../queries/expanded_link_set_spec.rb | 4 +- spec/support/content_block_helpers.rb | 14 ++++ 6 files changed, 71 insertions(+), 88 deletions(-) create mode 100644 spec/support/content_block_helpers.rb diff --git a/app/presenters/content_embed_presenter.rb b/app/presenters/content_embed_presenter.rb index 7774bb609..f8ee89fb2 100644 --- a/app/presenters/content_embed_presenter.rb +++ b/app/presenters/content_embed_presenter.rb @@ -64,14 +64,13 @@ def render_embedded_editions(content) content end - # This is a temporary solution to get email address content blocks working - # while we agree on a long-term approach that works for everything. def get_content_for_edition(edition) - if edition.document_type == "content_block_email_address" - edition.details[:email_address] - else - edition.title - end + ContentBlockTools::ContentBlock.new( + document_type: edition.document_type, + content_id: edition.document.content_id, + title: edition.title, + details: edition.details, + ).render end end end diff --git a/spec/integration/put_content/content_with_embedded_content_spec.rb b/spec/integration/put_content/content_with_embedded_content_spec.rb index 0a77cf0d6..58e0c9fac 100644 --- a/spec/integration/put_content/content_with_embedded_content_spec.rb +++ b/spec/integration/put_content/content_with_embedded_content_spec.rb @@ -22,7 +22,7 @@ it "should send transformed content to the content store" do put "/v2/content/#{content_id}", params: payload.to_json - expect_content_store_to_have_received_details_including({ "body" => "#{first_contact.title} #{second_contact.title}" }) + expect_content_store_to_have_received_details_including({ "body" => "#{presented_details_for(first_contact)} #{presented_details_for(second_contact)}" }) end end @@ -52,7 +52,7 @@ it "should send transformed content to the content store" do put "/v2/content/#{content_id}", params: payload_for_multiple_field_embeds.to_json - expect_content_store_to_have_received_details_including({ "downtime_message" => first_contact.title.to_s }) + expect_content_store_to_have_received_details_including({ "downtime_message" => presented_details_for(first_contact) }) end end @@ -128,11 +128,11 @@ "body" => [ { "content_type" => "text/govspeak", - "content" => first_contact.title, + "content" => presented_details_for(first_contact), }, { "content_type" => "text/html", - "content" => "

#{first_contact.title}

", + "content" => "

#{presented_details_for(first_contact)}

", }, ], }, @@ -142,11 +142,11 @@ "body" => [ { "content_type" => "text/govspeak", - "content" => second_contact.title, + "content" => presented_details_for(second_contact), }, { "content_type" => "text/html", - "content" => "

#{second_contact.title}

", + "content" => "

#{presented_details_for(second_contact)}

", }, ], }, @@ -176,32 +176,7 @@ it "should send transformed content to the content store" do put "/v2/content/#{content_id}", params: payload.to_json - expect_content_store_to_have_received_details_including({ "body" => array_including({ "content_type" => "text/govspeak", "content" => "#{first_contact.title} #{second_contact.title}" }) }) - end - end - - context "with an embedded email address" do - let(:first_email_address) { create(:edition, state: "published", content_store: "live", document_type: "content_block_email_address", details: { email_address: "foo@example.com" }) } - let(:second_email_address) { create(:edition, state: "published", content_store: "live", document_type: "content_block_email_address", details: { email_address: "bar@example.com" }) } - let(:document) { create(:document, content_id:) } - - before do - payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:content_block_email_address:#{first_email_address.document.content_id}}} {{embed:content_block_email_address:#{second_email_address.document.content_id}}}" }) - end - - it "should create links" do - expect { - put "/v2/content/#{content_id}", params: payload.to_json - }.to change(Link, :count).by(2) - - expect(Link.find_by(target_content_id: first_email_address.content_id)).not_to be_nil - expect(Link.find_by(target_content_id: second_email_address.content_id)).not_to be_nil - end - - it "should send transformed content to the content store" do - put "/v2/content/#{content_id}", params: payload.to_json - - expect_content_store_to_have_received_details_including({ "body" => "#{first_email_address.details[:email_address]} #{second_email_address.details[:email_address]}" }) + expect_content_store_to_have_received_details_including({ "body" => array_including({ "content_type" => "text/govspeak", "content" => "#{presented_details_for(first_contact)} #{presented_details_for(second_contact)}" }) }) end end @@ -226,7 +201,7 @@ it "should send transformed content to the content store" do put "/v2/content/#{content_id}", params: payload.to_json - expect_content_store_to_have_received_details_including({ "body" => "#{email_address.details[:email_address]} #{contact.title}" }) + expect_content_store_to_have_received_details_including({ "body" => "#{presented_details_for(email_address)} #{presented_details_for(contact)}" }) end end diff --git a/spec/presenters/content_embed_presenter_spec.rb b/spec/presenters/content_embed_presenter_spec.rb index 84e2160e3..0c35765b8 100644 --- a/spec/presenters/content_embed_presenter_spec.rb +++ b/spec/presenters/content_embed_presenter_spec.rb @@ -16,7 +16,7 @@ end let(:details) { {} } - before do + let!(:embedded_edition) do embedded_document = create(:document, content_id: embedded_content_id) create( :edition, @@ -29,12 +29,24 @@ end describe "#render_embedded_content" do + let(:expected_value) { "VALUE" } + let(:stub_block) { double(ContentBlockTools::ContentBlock, render: expected_value) } + + before do + expect(ContentBlockTools::ContentBlock).to receive(:new).with( + document_type: embedded_edition.document_type, + content_id: embedded_edition.document.content_id, + title: embedded_edition.title, + details: embedded_edition.details, + ).at_least(:once).and_return(stub_block) + end + context "when body is a string" do let(:details) { { body: "some string with a reference: {{embed:contact:#{embedded_content_id}}}" } } it "returns embedded content references with values from their editions" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ - body: "some string with a reference: VALUE", + body: "some string with a reference: #{expected_value}", }) end end @@ -50,8 +62,8 @@ it "returns embedded content references with values from their editions" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ body: [ - { content_type: "text/govspeak", content: "some string with a reference: VALUE" }, - { content_type: "text/html", content: "some string with a reference: VALUE" }, + { content_type: "text/govspeak", content: "some string with a reference: #{expected_value}" }, + { content_type: "text/html", content: "some string with a reference: #{expected_value}" }, ], }) end @@ -64,7 +76,7 @@ it "returns embedded content references with values from their editions" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ - body: { title: "some string with a reference: VALUE" }, + body: { title: "some string with a reference: #{expected_value}" }, }) end end @@ -91,7 +103,7 @@ parts: [ body: [ { - content: "some string with a reference: VALUE", + content: "some string with a reference: #{expected_value}", content_type: "text/govspeak", }, ], @@ -106,7 +118,7 @@ context "when the embedded content is available in multiple locales" do let(:details) { { body: "some string with a reference: {{embed:contact:#{embedded_content_id}}}" } } - before do + let!(:welsh_edition) do embedded_document = create(:document, content_id: embedded_content_id, locale: "cy") create( :edition, @@ -121,17 +133,18 @@ context "when the document is in the default language" do it "returns embedded content references with values from the same language" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ - body: "some string with a reference: VALUE", + body: "some string with a reference: #{expected_value}", }) end end context "when the document is in an available locale" do let(:document) { create(:document, locale: "cy") } + let(:embedded_edition) { welsh_edition } it "returns embedded content references with values from the same language" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ - body: "some string with a reference: WELSH", + body: "some string with a reference: #{expected_value}", }) end end @@ -141,46 +154,16 @@ it "returns embedded content references with values from the default language" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ - body: "some string with a reference: VALUE", + body: "some string with a reference: #{expected_value}", }) end end end - context "when the document is an email address" do - let(:embedded_document) { create(:document) } - let(:links_hash) do - { - embed: [embedded_document.content_id], - } - end - - before do - create( - :edition, - document: embedded_document, - state: "published", - content_store: "live", - document_type: "content_block_email_address", - details: { - email_address: "foo@example.com", - }, - ) - end - - let(:details) { { body: "some string with a reference: {{embed:content_block_email_address:#{embedded_document.content_id}}}" } } - - it "returns an email address" do - expect(described_class.new(edition).render_embedded_content(details)).to eq({ - body: "some string with a reference: foo@example.com", - }) - end - end - context "when multiple documents are embedded in different parts of the document" do let(:other_embedded_content_id) { SecureRandom.uuid } - before do + let!(:other_embedded_edition) do embedded_document = create(:document, content_id: other_embedded_content_id) create( :edition, @@ -192,6 +175,18 @@ ) end + let(:other_expected_value) { "VALUE2" } + let(:other_stub_block) { double(ContentBlockTools::ContentBlock, render: other_expected_value) } + + before do + expect(ContentBlockTools::ContentBlock).to receive(:new).with( + document_type: other_embedded_edition.document_type, + content_id: other_embedded_edition.document.content_id, + title: other_embedded_edition.title, + details: other_embedded_edition.details, + ).at_least(:once).and_return(other_stub_block) + end + let(:links_hash) do { embed: [embedded_content_id, other_embedded_content_id], @@ -207,8 +202,8 @@ it "returns embedded content references with values from their editions" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ - title: "title string with reference: VALUE2", - body: "some string with a reference: VALUE", + title: "title string with reference: #{other_expected_value}", + body: "some string with a reference: #{expected_value}", }) end end diff --git a/spec/presenters/details_presenter_spec.rb b/spec/presenters/details_presenter_spec.rb index 1a4adfe3f..d687dfdf9 100644 --- a/spec/presenters/details_presenter_spec.rb +++ b/spec/presenters/details_presenter_spec.rb @@ -197,7 +197,7 @@ let(:edition_details) { edition.details } let(:expected_details) do { - field_name => embeddable_content.title, + field_name => presented_details_for(embeddable_content), }.symbolize_keys end @@ -219,8 +219,8 @@ let(:expected_details) do { field_name => [ - { content_type: "text/html", content: embeddable_content.title }, - { content_type: "text/govspeak", content: embeddable_content.title }, + { content_type: "text/html", content: presented_details_for(embeddable_content) }, + { content_type: "text/govspeak", content: presented_details_for(embeddable_content) }, ], }.symbolize_keys end diff --git a/spec/presenters/queries/expanded_link_set_spec.rb b/spec/presenters/queries/expanded_link_set_spec.rb index 577d5274f..acb0fb4d3 100644 --- a/spec/presenters/queries/expanded_link_set_spec.rb +++ b/spec/presenters/queries/expanded_link_set_spec.rb @@ -110,11 +110,11 @@ expect(c[:details][:body]).to match([ { content_type: "text/govspeak", - content: "Some contact", + content: presented_details_for(contact), }, { content_type: "text/html", - content: "

Some contact

\n", + content: "

#{presented_details_for(contact)}

\n", }, ]) end diff --git a/spec/support/content_block_helpers.rb b/spec/support/content_block_helpers.rb new file mode 100644 index 000000000..2c653fbaa --- /dev/null +++ b/spec/support/content_block_helpers.rb @@ -0,0 +1,14 @@ +module ContentBlockHelpers + def presented_details_for(edition) + ContentBlockTools::ContentBlock.new( + document_type: edition.document_type, + content_id: edition.document.content_id, + title: edition.title, + details: edition.details, + ).render + end +end + +RSpec.configure do |c| + c.include ContentBlockHelpers +end