Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/models/configurable_content_blocks/default_textarea.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ConfigurableContentBlocks
class DefaultTextarea < BaseBlock
private

def template_name
"default_textarea"
end
end
end
5 changes: 5 additions & 0 deletions app/models/configurable_document_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class ConfigurableDocumentType

CONTENT_BLOCKS = {
"default_string" => ConfigurableContentBlocks::DefaultString,
"default_textarea" => ConfigurableContentBlocks::DefaultTextarea,
"govspeak" => ConfigurableContentBlocks::Govspeak,
"default_date" => ConfigurableContentBlocks::DefaultDate,
"default_select" => ConfigurableContentBlocks::DefaultSelect,
Expand Down Expand Up @@ -86,6 +87,10 @@ def properties
@schema["attributes"] || {}
end

def parts
(presenter("publishing_api")["details"]["parts"] || []).map { |part| part["slug"]["hardcoded_value"] }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This parts method is now only called from the payload builder right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Indirectly - it's only called by the additional_routes method, which is called by the presenter only in determining what routes to send.
The actual content for the parts is now just using our standard payload methods (raw etc)

end

def form(key = nil)
return nil if @forms.empty?

Expand Down
2 changes: 1 addition & 1 deletion app/models/configurable_document_types/case_study.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak"
"body": { "field": "body", "type": "compiled_govspeak" }
},
"links": [
"world_locations",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak"
"body": { "field": "body", "type": "compiled_govspeak" }
},
"links": [
"ministerial_role_appointments",
Expand Down
4 changes: 2 additions & 2 deletions app/models/configurable_document_types/history_page.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak",
"lead_paragraph": "raw"
"body": { "field": "body", "type": "compiled_govspeak" },
"lead_paragraph": { "field": "lead_paragraph", "type": "raw" }
},
"links": []
}
Expand Down
2 changes: 1 addition & 1 deletion app/models/configurable_document_types/news_story.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak"
"body": { "field": "body", "type": "compiled_govspeak" }
},
"links": [
"ministerial_role_appointments",
Expand Down
2 changes: 1 addition & 1 deletion app/models/configurable_document_types/press_release.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak"
"body": { "field": "body", "type": "compiled_govspeak" }
},
"links": [
"ministerial_role_appointments",
Expand Down
46 changes: 44 additions & 2 deletions app/models/configurable_document_types/topical_event.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@
}
}
},
"about": {

@lauraghiorghisor-tw lauraghiorghisor-tw Jun 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

but you can imagine how we'd extend this to support all possible Corporate Information Pages, for example.

It appears I cannot, in fact, imagine 😅

How would you extend this for multiple corporate information pages? Add all the types there as new forms, with their own fields, and part "/corporate-thing-blah"? 💀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, exactly! Or every corporate information page on one tab (like we have for Travel Advice pages):

"corporate_information_pages": {
  "dynamic": true,
  "label": "Corporate information pages",
  "fields": {
    "recruitment_summary": {
      "title": "Recruitment page summary (leave blank if not needed)",
      "block": "govspeak",
      "attribute_path": ["block_content", "recruitment_summary"],
      "part": "/recruitment",
      "part_name": "summary",
      "translatable": true
    },
    "recruitment_body": {
      "title": "Recruitment page body (leave blank if not needed)",
      "block": "govspeak",
      "attribute_path": ["block_content", "recruitment_body"],
      "part": "/recruitment",
      "part_name": "body",
      "translatable": true
    },
    "accessibility_statement_summary": {
      "title": "Accessibility statement summary (leave blank if not needed)",
       ...etc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's really busy 😵‍💫
The array solution might work better, and then you can just let the user add... another.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Less bad in latest commits (each 'part' would be its own nested default_object, and there are fewer properties needed, e.g. part_name/part). But agree it's still not great.
As we touched on in 1:1 this morning, not entirely convinced this would scale to corporate information pages now I know CIP pages have an Attachments tab requirement too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Though could very well scale to Travel Advice 🤔

"dynamic": true,
"label": "About page",
"fields": {
"about": {
"block": "default_object",
"attribute_path": ["block_content", "about"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What does the block_content save as, now, in wh? You seem to call block_content -> namespace -> field in the builder so I suppose the "about" is now stored in the block_content.

Sorry don't seem to be able to save an edition when I run the app - still getting pub api errors even with the branch checked out, not sure if that was meant to work now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, it should work locally 🤔

It stores as:

block_content: {
  about: {
    title: ...,
    body: ...,
  }
}

"fields": {
"title": {
"title": "Title",
"block": "default_string",
"attribute_path": ["title"],
"translatable": true
},
"body": {
"title": "Body",
"block": "govspeak",
"attribute_path": ["body"],
"translatable": true
}
}
}
}
},
"social_media_accounts": {
"dynamic": true,
"label": "Social media accounts",
Expand Down Expand Up @@ -128,6 +152,17 @@
"body": {
"type": "string"
},
"about": {
"type": "object",
"attributes": {
"title": {
"type": "string"
},
"body": {
"type": "string"
}
}
},
"social_media_links": {
"type": "array",
"attributes": {
Expand Down Expand Up @@ -174,8 +209,15 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak",
"social_media_links": "social_media_links"
"body": { "field": "body", "type": "compiled_govspeak" },
"social_media_links": { "field": "social_media_links", "type": "social_media_links" },
"parts": [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is parts what we want to go for? Doesn't read very semantically. The way this is defined above in the forms, where you just have the single "about", doesn't even imply it is a plural. And then we come here in the presenters and it's parts all of a sudden.

I appreciate it's handy to fetch the parts for the additional routes as well, but could we try something like this, for the actual implementation?

presenters > publishing_api > details >
"about": {
            "title":  {...}
            "body":  {...}
            "slug": {...}
          }

And then you'd have, say, pages here instead of parts for a thing that might genuinely have a tab called pages, and multiple such pages. And then that could map to an array.

Maybe trying to fix the recursion in the payload builder would make this easy to do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I went with parts since that's what Frontend expects.

{
"title": { "field": "about.title", "type": "raw" },
"body": { "field": "about.body", "type": "compiled_and_raw_govspeak" },
"slug": { "hardcoded_value": "/about" }
}
]
},
"links": [
"organisations"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak"
"body": { "field": "body", "type": "compiled_govspeak" }
},
"links": [
"topical_events",
Expand Down
4 changes: 4 additions & 0 deletions app/models/edition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,10 @@ def public_url(options = {})
website_root + public_path(options)
end

def additional_routes
[]
end

def force_scheduled?
force_published? && state == "scheduled"
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/standard_edition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ def base_path
"#{type_instance.settings['base_path_prefix']}/#{slug}"
end

def additional_routes
type_instance.parts
end

def type_instance
ConfigurableDocumentType.find(configurable_document_type)
end
Expand Down
2 changes: 1 addition & 1 deletion app/presenters/publishing_api/organisation_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def use_prefix_route?
def additional_routes
return [] if court_or_tribunal?

%w[atom]
%w[.atom]
end

def details
Expand Down
44 changes: 37 additions & 7 deletions app/presenters/publishing_api/payload_builder/block_content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,24 @@ def call
return {} unless mapping

mapping.each_with_object({}) { |(attribute, builder), details|
details[attribute.to_sym] = send(builder, attribute)
details[attribute.to_sym] = if builder.is_a?(Array)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For the implementation version:

  • I'd probably rename some of these variables, feels weird to have an array as a builder. But I imagine that will change anyway when you rewrite it to be recursive.
  • Also let's rename some of the arg names elsewhere in the class - attribute -> now content.

builder.map do |part_builder|
part_builder.each_with_object({}) do |(part_attribute, part_builder_type), part_details|
if part_builder_type["hardcoded_value"]
part_details[part_attribute.to_sym] = part_builder_type["hardcoded_value"]
elsif part_builder_type["field"].include?(".")
# TODO: again, support recursion for infinite depths
namespace = part_builder_type["field"].split(".").first
field = part_builder_type["field"].split(".").last
part_details[part_attribute.to_sym] = send(part_builder_type["type"], item.block_content&.public_send(namespace)&.[](field))
else
part_details[part_attribute.to_sym] = send(part_builder_type["type"], item.block_content&.public_send(part_builder_type["field"]))
end
end
end
else
send(builder["type"], item.block_content&.public_send(attribute))
end
}.compact
end

Expand All @@ -25,22 +42,35 @@ def call
attr_reader :item

def raw(attribute)
item.block_content&.public_send(attribute)
attribute
end

def govspeak(attribute)
content = item.block_content&.public_send(attribute)
def compiled_govspeak(content)
return nil if content.nil?

govspeak_to_html(content, images: item.images, attachments: item.attachments)
end

def compiled_and_raw_govspeak(content)
return nil if content.nil?

[
{
content_type: "text/html",
content: compiled_govspeak(content),
},
{
content_type: "text/govspeak",
content: content,
},
]
end

def rfc3339_date(attribute)
item.block_content&.public_send(attribute)&.rfc3339
attribute&.rfc3339
end

def social_media_links(attribute)
content = item.block_content&.public_send(attribute)
def social_media_links(content)
return [] if content.blank?

content.map do |item|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ def initialize(item)
end

def call
{ base_path: }.merge(PayloadBuilder::Routes.for(base_path))
routes = if item.additional_routes.any?
PayloadBuilder::Routes.for(base_path, additional_routes: item.additional_routes)
else
PayloadBuilder::Routes.for(base_path)
end
{ base_path: }.merge(routes)
end

private
Expand Down
2 changes: 1 addition & 1 deletion app/presenters/publishing_api/payload_builder/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def call
routes = []
routes << { path: base_path, type: }
additional_routes.each do |additional_route|
routes << { path: "#{base_path}.#{additional_route}", type: "exact" }
routes << { path: "#{base_path}#{additional_route}", type: "exact" }
end
{ routes: }
end
Expand Down
2 changes: 1 addition & 1 deletion app/presenters/publishing_api/topical_event_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def content
rendering_app: Whitehall::RenderingApp::FRONTEND,
schema_name: "topical_event",
)
content.merge!(PayloadBuilder::PolymorphicPath.for(item, additional_routes: %w[atom]))
content.merge!(PayloadBuilder::PolymorphicPath.for(item, additional_routes: %w[.atom]))
end

def links
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<%= render "govuk_publishing_components/components/input", {
id: block.path.form_control_id,
label: { text: block.title + (block.required ? " (required)" : "") },
label: {
text: block.title + (block.required ? " (required)" : ""),
heading_size: "m",
},
name: block.path.form_control_name,
value: block.value,
hint: block.hint_text,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%= render "govuk_publishing_components/components/textarea", {
textarea_id: block.path.form_control_id,
label: {
text: block.title + (block.required ? " (required)" : ""),
heading_size: "m",
},
name: block.path.form_control_name,
value: block.value,
hint: block.hint_text,
right_to_left: block.edition.translation_locale.rtl?,
error_items: errors_for(block.edition.errors, block.path.validation_error_attribute.to_sym),
} %>
<% if block.primary_locale_value.present? %>
<%= render "govuk_publishing_components/components/details", {
title: "Primary locale content for #{block.title}",
} do %>
<%= block.primary_locale_value %>
<% end %>
<% end %>
6 changes: 3 additions & 3 deletions features/fixtures/test_configurable_document_type.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak",
"date_field": "rfc3339_date",
"list_of_foods": "raw"
"body": { "field": "body", "type": "compiled_govspeak" },
"date_field": { "field": "date_field", "type": "rfc3339_date" },
"list_of_foods": { "field": "list_of_foods", "type": "raw" }
},
"links": []
}
Expand Down
4 changes: 2 additions & 2 deletions features/fixtures/test_configurable_document_type_group.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak"
"body": { "field": "body", "type": "compiled_govspeak" }
},
"links": []
}
Expand Down Expand Up @@ -83,7 +83,7 @@
"presenters": {
"publishing_api": {
"details": {
"body": "govspeak"
"body": { "field": "body", "type": "compiled_govspeak" }
},
"links": []
}
Expand Down
Loading