-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2813 from alphagov/embedded-content-v2
Allow embedding of content within document body
- Loading branch information
Showing
11 changed files
with
540 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
module Presenters | ||
class ContentEmbedPresenter | ||
def initialize(edition) | ||
@edition = edition | ||
end | ||
|
||
def render_embedded_content(details) | ||
return details if details[:body].nil? | ||
|
||
details[:body] = if details[:body].is_a?(Array) | ||
details[:body].map do |content| | ||
{ | ||
content_type: content[:content_type], | ||
content: render_embedded_editions(content[:content]), | ||
} | ||
end | ||
else | ||
render_embedded_editions(details[:body]) | ||
end | ||
|
||
details | ||
end | ||
|
||
private | ||
|
||
def render_embedded_editions(content) | ||
embedded_content_references = EmbeddedContentFinderService.new.find_content_references(content) | ||
embedded_content_references_by_content_id = embedded_content_references.index_by(&:content_id) | ||
|
||
target_content_ids = @edition | ||
.links | ||
.where(link_type: "embed") | ||
.pluck(:target_content_id) | ||
|
||
embedded_edition_ids = ::Queries::GetEditionIdsWithFallbacks.call( | ||
target_content_ids, | ||
locale_fallback_order: [@edition.locale, Edition::DEFAULT_LOCALE].uniq, | ||
state_fallback_order: %w[published], | ||
) | ||
|
||
embedded_editions = Edition.where(id: embedded_edition_ids) | ||
|
||
embedded_editions.each do |embedded_edition| | ||
embed_code = embedded_content_references_by_content_id[embedded_edition.content_id].embed_code | ||
content = content.gsub( | ||
embed_code, | ||
embedded_edition.title, | ||
) | ||
end | ||
|
||
content | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
class EmbeddedContentFinderService | ||
ContentReference = Data.define(:document_type, :content_id, :embed_code) | ||
|
||
SUPPORTED_DOCUMENT_TYPES = %w[contact].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(body, locale) | ||
content_references = if body.is_a?(Array) | ||
body.map { |hash| find_content_references(hash[:content]) }.flatten | ||
else | ||
find_content_references(body) | ||
end | ||
return [] if content_references.empty? | ||
|
||
check_all_references_exist(content_references, locale) | ||
content_references.map(&:content_id) | ||
end | ||
|
||
def find_content_references(body) | ||
body.scan(EMBED_REGEX).map { |match| ContentReference.new(document_type: match[1], content_id: match[2], embed_code: match[0]) }.uniq | ||
end | ||
|
||
private | ||
|
||
def check_all_references_exist(content_references, locale) | ||
found_editions = live_editions(content_references, locale) | ||
if found_editions.count != content_references.count | ||
not_found_content_ids = content_references.map(&:content_id) - found_editions.map(&:content_id) | ||
raise CommandError.new( | ||
code: 422, | ||
message: "Could not find any live editions in locale #{locale} for: #{not_found_content_ids.join(', ')}, ", | ||
) | ||
end | ||
end | ||
|
||
def live_editions(content_references, locale) | ||
Edition.with_document.where( | ||
state: "published", | ||
content_store: "live", | ||
document_type: content_references.map(&:document_type), | ||
documents: { content_id: content_references.map(&:content_id), locale: }, | ||
) | ||
end | ||
end |
106 changes: 106 additions & 0 deletions
106
spec/integration/put_content/content_with_embedded_content_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
RSpec.describe "PUT /v2/content when embedded content is provided" do | ||
include_context "PutContent call" | ||
|
||
context "with embedded content" do | ||
let(:first_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } | ||
let(:second_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } | ||
let(:document) { create(:document, content_id:) } | ||
|
||
before do | ||
payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{first_contact.document.content_id}}} {{embed:contact:#{second_contact.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_contact.content_id)).not_to be_nil | ||
expect(Link.find_by(target_content_id: second_contact.content_id)).not_to be_nil | ||
end | ||
end | ||
|
||
context "without embedded content and embed links already existing on a draft edition" do | ||
let(:contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } | ||
let(:document) { create(:document, content_id:) } | ||
let(:edition) { create(:edition, document:) } | ||
|
||
before do | ||
stub_request(:put, %r{.*content-store.*/content/.*}) | ||
edition.links.create!({ | ||
link_type: "embed", | ||
target_content_id: contact.content_id, | ||
position: 0, | ||
}) | ||
payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "no embed links" }) | ||
end | ||
|
||
it "should remove embed links" do | ||
expect { | ||
put "/v2/content/#{content_id}", params: payload.to_json | ||
}.to change(Link, :count).by(-1) | ||
|
||
expect(Link.find_by(target_content_id: contact.content_id)).to be_nil | ||
end | ||
end | ||
|
||
context "with different embedded content and embed links already existing on a draft edition" do | ||
let(:first_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } | ||
let(:second_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } | ||
let(:document) { create(:document, content_id:) } | ||
let(:edition) { create(:edition, document:) } | ||
|
||
before do | ||
stub_request(:put, %r{.*content-store.*/content/.*}) | ||
edition.links.create!({ | ||
link_type: "embed", | ||
target_content_id: first_contact.content_id, | ||
position: 0, | ||
}) | ||
payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{second_contact.document.content_id}}}" }) | ||
end | ||
|
||
it "should replace the embed link" do | ||
expect { | ||
put "/v2/content/#{content_id}", params: payload.to_json | ||
}.to change(Link, :count).by(0) | ||
|
||
expect(Link.find_by(target_content_id: first_contact.content_id)).to be_nil | ||
expect(Link.find_by(target_content_id: second_contact.content_id)).not_to be_nil | ||
end | ||
end | ||
|
||
context "with embedded content that does not exist" do | ||
let(:document) { create(:document, content_id:) } | ||
let(:fake_content_id) { SecureRandom.uuid } | ||
|
||
before do | ||
payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{fake_content_id}}}" }) | ||
end | ||
|
||
it "should return a 422 error" do | ||
put "/v2/content/#{content_id}", params: payload.to_json | ||
|
||
expect(response).to be_unprocessable | ||
expect(response.body).to match(/Could not find any live editions in locale en for: #{fake_content_id}/) | ||
end | ||
end | ||
|
||
context "with a mixture of embedded content that does and does not exist" do | ||
let(:contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } | ||
let(:document) { create(:document, content_id:) } | ||
let(:first_fake_content_id) { SecureRandom.uuid } | ||
let(:second_fake_content_id) { SecureRandom.uuid } | ||
|
||
before do | ||
payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{contact.document.content_id}}} {{embed:contact:#{first_fake_content_id}}} {{embed:contact:#{second_fake_content_id}}}" }) | ||
end | ||
|
||
it "should return a 422 error" do | ||
put "/v2/content/#{content_id}", params: payload.to_json | ||
|
||
expect(response).to be_unprocessable | ||
expect(response.body).to match(/Could not find any live editions in locale en for: #{first_fake_content_id}, #{second_fake_content_id}/) | ||
end | ||
end | ||
end |
Oops, something went wrong.