Skip to content

Commit 71a64d6

Browse files
GDSNewtChrisBAshton
authored andcommitted
WIP: Social media links
1 parent 30ade31 commit 71a64d6

10 files changed

Lines changed: 211 additions & 23 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module ConfigurableContentBlocks
2+
class DefaultArray
3+
attr_reader :block_factory
4+
5+
def initialize(block_factory)
6+
@block_factory = block_factory
7+
end
8+
9+
def to_partial_path
10+
"admin/configurable_content_blocks/default_array"
11+
end
12+
end
13+
end

app/models/configurable_content_blocks/factory.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ def build_block(block)
99
"default_string" => ->(_page) { ConfigurableContentBlocks::DefaultString.new },
1010
"govspeak" => ->(page) { ConfigurableContentBlocks::Govspeak.new(page.images, page.attachments) },
1111
"default_date" => ->(_page) { ConfigurableContentBlocks::DefaultDate.new },
12+
"social_media_service_select" => ->(_page) { ConfigurableContentBlocks::SocialMediaServiceSelect.new },
1213
"image_select" => ->(page) { ConfigurableContentBlocks::ImageSelect.new(page.valid_images) },
1314
"lead_image_select" => ->(page) { ConfigurableContentBlocks::LeadImageSelect.new(page.valid_lead_images, default_lead_image: page.default_lead_image, placeholder_image_url: page.placeholder_image_url) },
1415
"default_object" => ->(_page) { ConfigurableContentBlocks::DefaultObject.new(self) },
16+
"default_array" => ->(_page) { ConfigurableContentBlocks::DefaultArray.new(self) },
1517
}.freeze
1618

1719
raise "Block #{block} is not defined" if blocks[block].nil?
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module ConfigurableContentBlocks
2+
class SocialMediaServiceSelect
3+
attr_reader :services
4+
5+
def initialize
6+
@services = SocialMediaService.all
7+
end
8+
9+
def to_partial_path
10+
"admin/configurable_content_blocks/social_media_service_select"
11+
end
12+
end
13+
end

app/models/configurable_document_types/topical_event.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@
2323
"block": "default_date"
2424
}
2525
}
26+
},
27+
"social_media_links": {
28+
"title": "Social media accounts",
29+
"block": "default_array",
30+
"fields": {
31+
"social_media_service_id": {
32+
"title": "Service",
33+
"block": "social_media_service_select"
34+
},
35+
"url": {
36+
"title": "URL",
37+
"block": "default_string"
38+
}
39+
}
2640
}
2741
}
2842
}
@@ -37,6 +51,17 @@
3751
},
3852
"end_date": {
3953
"type": "date"
54+
},
55+
"social_media_links": {
56+
"type": "array",
57+
"attributes": {
58+
"social_media_service_id": {
59+
"type": "integer"
60+
},
61+
"url": {
62+
"type": "string"
63+
}
64+
}
4065
}
4166
},
4267
"validations": {
@@ -67,7 +92,8 @@
6792
"publishing_api": {
6893
"body": "govspeak",
6994
"start_date": "rfc3339_date",
70-
"end_date": "rfc3339_date"
95+
"end_date": "rfc3339_date",
96+
"social_media_links": "social_media_links"
7197
}
7298
},
7399
"associations": [

app/models/standard_edition/block_content.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ def attributes=(values)
2727
setter = "#{key}="
2828
if nested_schema["type"] == "date"
2929
public_send(setter, pre_validate_date_attribute(key, values[key]))
30+
elsif nested_schema["type"] == "array"
31+
#  Convert
32+
# { "0" => { "foo" => "bar" }, "1" => { "delete_me" => "something", "_destroy" => "1" } }
33+
# to
34+
# [ { "foo" => "bar" } ]
35+
vals = values[key].is_a?(Hash) ? values[key].values : values[key]
36+
public_send(setter, vals.reject { |h| h["_destroy"] == "1" })
3037
else
3138
public_send(setter, values[key])
3239
end
@@ -74,6 +81,8 @@ def attributes_class_for(attribute_config)
7481
case property_schema["type"]
7582
when "object"
7683
attribute key
84+
when "array"
85+
attribute key
7786
else
7887
attribute key, property_schema["type"].to_sym
7988
end

app/presenters/publishing_api/payload_builder/block_content.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,21 @@ def lead_image(attribute)
8080
url: item.placeholder_image_url,
8181
}
8282
end
83+
84+
def social_media_links(attribute)
85+
content = item.block_content&.public_send(attribute)
86+
return [] if content.blank?
87+
88+
content.map do |item|
89+
# `item` looks something like `{"url"=>"foo", "social_media_service_id"=>"3"}`
90+
service = SocialMediaService.find(item["social_media_service_id"].to_i)
91+
{
92+
title: service.name,
93+
service_type: service.name.parameterize, # "Google Plus" => "google-plus"
94+
href: item["url"],
95+
}
96+
end
97+
end
8398
end
8499
end
85100
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<% required ||= false
2+
content ||= {}
3+
translated_content ||= {}
4+
right_to_left ||= false
5+
errors ||= []
6+
required_attributes ||= []
7+
element_properties = schema.fetch("fields") %>
8+
9+
<%
10+
render_fields = ->(social_media_link, index) do
11+
element_fields = capture do
12+
element_properties.each do |property_key, property_schema|
13+
block = default_array.block_factory.build_block(property_schema["block"])
14+
%>
15+
<%= render block, {
16+
schema: property_schema,
17+
content: social_media_link[property_key],
18+
# TODO: support translations
19+
# translated_content: social_media_link[property_key],
20+
path: ConfigurableContentBlocks::Path.new(["social_media_links", index, property_key]),
21+
required: required_attributes.include?(property_key),
22+
required_attributes: property_schema["block"] == "default_array" ? required_attributes : [],
23+
right_to_left: right_to_left,
24+
errors: errors,
25+
} %>
26+
<%
27+
end # element_properties.each loop
28+
end # element_fields = capture
29+
end # render_fields definition
30+
%>
31+
32+
<%= render "govuk_publishing_components/components/fieldset",
33+
{ legend_text: "#{schema['title']}#{required ? ' (required)' : ''}",
34+
heading_size: "l",
35+
id: path.form_control_id } do %>
36+
<%
37+
# TODO: make the array key (`social_media_links`) dynamic
38+
items = content["social_media_links"].map.with_index do |social_media_link, index|
39+
# raise path.form_control_name.inspect # "edition[block_content][social_media_links]"
40+
{
41+
fields: render_fields.call(social_media_link, index),
42+
destroy_checkbox: render("govuk_publishing_components/components/checkboxes", {
43+
name: "#{path.form_control_name}[#{index}][_destroy]",
44+
items: [{ label: "Remove", value: "1" }],
45+
}),
46+
}
47+
end
48+
%>
49+
50+
<%= render "govuk_publishing_components/components/add_another", {
51+
fieldset_legend: schema["title"],
52+
add_button_text: "Add another",
53+
empty_fields: true,
54+
items: items,
55+
empty: render_fields.call({}, items.length),
56+
} %>
57+
<% end %>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<% required = required || false %>
2+
<% translated_content = translated_content || nil %>
3+
<% select_options = [
4+
{
5+
text: "No social media service selected",
6+
value: "",
7+
},
8+
] + social_media_service_select.services.map do |service|
9+
{
10+
text: service.name,
11+
value: service.id,
12+
selected: service.id == (translated_content || content).to_i,
13+
}
14+
end %>
15+
<% errors = errors || [] %>
16+
<%= render "govuk_publishing_components/components/select", {
17+
id: path.form_control_id,
18+
label: schema["title"] + (required ? " (required)" : ""),
19+
name: path.form_control_name,
20+
options: select_options,
21+
hint: schema["description"],
22+
error_items: errors_for(errors, path.validation_error_attribute.to_sym),
23+
} %>

public/configurable-document-type.schema.json

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@
5050
"default_date",
5151
"default_object",
5252
"default_string",
53+
"default_array",
5354
"govspeak",
5455
"image_select",
55-
"lead_image_select"
56+
"lead_image_select",
57+
"social_media_service_select"
5658
]
5759
},
5860
"fields": {
@@ -66,6 +68,49 @@
6668
},
6769
"required": ["title", "block"],
6870
"additionalProperties": false
71+
},
72+
"block_attributes": {
73+
"type": "object",
74+
"description": "Map of content block attribute definitions (minimal schema).",
75+
"patternProperties": {
76+
".*": { "$ref": "#/$defs/block_attribute" }
77+
},
78+
"additionalProperties": false
79+
},
80+
"block_attribute": {
81+
"type": "object",
82+
"description": "Minimal schema information for a single content block attribute.",
83+
"properties": {
84+
"type": {
85+
"type": "string",
86+
"description": "Data type for the content block. Should map to an active record type, with the exception of the object type.",
87+
"enum": ["string", "integer", "date", "array"]
88+
},
89+
"attributes": {
90+
"description": "For array types, defines the schema of each array item using the same minimal attribute format.",
91+
"$ref": "#/$defs/block_attributes"
92+
}
93+
},
94+
"required": ["type"],
95+
"additionalProperties": false,
96+
"allOf": [
97+
{
98+
"if": {
99+
"properties": { "type": { "const": "array" } },
100+
"required": ["type"]
101+
},
102+
"then": {
103+
"properties": {
104+
"attributes": { "$ref": "#/$defs/block_attributes" }
105+
}
106+
},
107+
"else": {
108+
"not": {
109+
"required": ["attributes"]
110+
}
111+
}
112+
}
113+
]
69114
}
70115
},
71116
"title": "Configurable Document Type",
@@ -90,26 +135,8 @@
90135
"description": "Defines the content structure for the document type. Each property represents a content block.",
91136
"properties": {
92137
"attributes": {
93-
"type": "object",
94-
"description": "Alternative to `properties`, defining content blocks with minimal schema information.",
95-
"patternProperties": {
96-
".*": {
97-
"type": "object",
98-
"properties": {
99-
"type": {
100-
"type": "string",
101-
"description": "Data type for the content block. Should map to an active record type, with the exception of the object type.",
102-
"enum": [
103-
"string",
104-
"integer",
105-
"date"
106-
]
107-
}
108-
},
109-
"required": ["type"],
110-
"additionalProperties": false
111-
}
112-
}
138+
"$ref": "#/$defs/block_attributes",
139+
"description": "Alternative to `properties`, defining content blocks with minimal schema information."
113140
},
114141
"validations": {
115142
"$ref": "#/$defs/validations"
@@ -175,7 +202,8 @@
175202
"rfc3339_date",
176203
"image",
177204
"lead_image",
178-
"string"
205+
"string",
206+
"social_media_links"
179207
]
180208
}
181209
},

test/test_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ class ActiveSupport::TestCase
7878
TaxonValidator.any_instance.stubs(:validate)
7979
# Prevent publishing API base path checks from interfering with tests
8080
Whitehall::PublishingApi.stubs(:ensure_base_path_is_associated_with_this_content_id!).returns(nil)
81+
# Set up default test type StandardEdition
82+
ConfigurableDocumentType.setup_test_types(build_configurable_document_type("test_type"))
8183
end
8284

8385
teardown do

0 commit comments

Comments
 (0)