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

Nested facets for admin form #3035

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,13 @@ def facets_params
facets: [
:key,
:name,
:nested_facet,
:sub_facet,
:short_name,
:type,
:preposition,
:display_as_result_metadata,
:show_option_select_filter,
:filterable,
:allowed_values,
:_destroy,
Expand Down
9 changes: 9 additions & 0 deletions app/helpers/facet_select_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ def nested_facet_options(facet_value)
def facet_option(facet_value)
[[facet_value["label"], facet_value["value"]]]
end

def admin_facet_value_from_allowed_values(allowed_values, nested_facet:)
values = allowed_values&.map do |value|
value_output = "#{value['label']} {#{value['value']}}"
nested_facet ? "#{value_output}\n#{value['sub_facets'].map { |sub_facet| "#{sub_facet['label']} {#{sub_facet['value']}}" }&.join("\n")}" : value_output
end

nested_facet ? values&.join("\r\n\r\n") : values&.join("\n")
end
end
61 changes: 53 additions & 8 deletions app/models/facet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
attribute :filterable, :boolean
attribute :allowed_values
attribute :specialist_publisher_properties
attribute :nested_facet, :boolean
attribute :sub_facet_name
attribute :sub_facet_key
attribute :show_option_select_filter, :boolean

def to_finder_schema_attributes
{
Expand All @@ -19,9 +23,13 @@
filterable:,
key:,
name:,
nested_facet:,
preposition:,
specialist_publisher_properties:,
short_name:,
show_option_select_filter:,
specialist_publisher_properties:,
sub_facet_key:,
sub_facet_name:,
type:,
}.compact
end
Expand All @@ -36,8 +44,12 @@
facet.preposition = nil_if_blank(params["preposition"])
facet.display_as_result_metadata = params["display_as_result_metadata"]
facet.filterable = params["filterable"]
facet.allowed_values = facet_allowed_values(params["allowed_values"], params["type"])
facet.allowed_values = facet_allowed_values(params["allowed_values"], params["type"], nil_if_false(params["nested_facet"]))
facet.specialist_publisher_properties = facet_specialist_publisher_properties(params["type"], params["validations"])
facet.nested_facet = nil_if_false(params["nested_facet"])
facet.sub_facet_name = extract_label_and_value(params["sub_facet"], "_").first if params["sub_facet"].present?
facet.sub_facet_key = facet_key(extract_label_and_value(params["sub_facet"], "_").last, facet.sub_facet_name) if params["sub_facet"].present?
facet.show_option_select_filter = nil_if_false(params["show_option_select_filter"])
facet
end

Expand All @@ -51,22 +63,55 @@
str.presence
end

def nil_if_false(str)
str == "true" ? true : nil
end

def facet_type(type)
facet_types_that_allow_enum_values.include?(type) ? "text" : type
end

def facet_allowed_values(values, type)
def facet_allowed_values(values, type, nested_facet)
return nil if values.nil? || facet_types_that_allow_enum_values.exclude?(type)

values.split("\n").map do |str|
label = str.match(/^(.+){/)
label = label.nil? ? str.strip : label[1].strip
value = str.match(/{(.+)}/)
value = value.nil? ? str.strip.downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, "-") : value[1].strip
nested_facet ? extract_nested_allowed_values(values) : extract_allowed_values(values)
end

def extract_allowed_values(values_string)
values_string.split("\n").map do |str|
label, value = extract_label_and_value(str, "-")
{ label:, value: }
end
end

def extract_nested_allowed_values(values_string)
values_string.split("\r\n\r\n").map do |facet_block|
main_facet_str, *sub_facet_str = facet_block.split("\n")
main_facet_label, main_facet_value = extract_label_and_value(main_facet_str, "-")
allowed_value = { label: main_facet_label, value: main_facet_value }

next allowed_value unless sub_facet_str.any?

sub_facets = []
sub_facet_str.map do |str|
sub_facet_label, sub_facet_value = extract_label_and_value(str, "-")
sub_facets << { label: sub_facet_label, value: sub_facet_value }
end
allowed_value[:sub_facets] = sub_facets

allowed_value
end
end

def extract_label_and_value(str, gsub_character)
label = str.match(/^(.+){/)
label = label.nil? ? str.strip : label[1].strip
value = str.truncate(500, omission: "}").match(/{(.+)}/)
value = value.nil? ? str.strip.downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, gsub_character) : value[1].strip

[label, value]
end

def facet_specialist_publisher_properties(type, validations)
properties = facet_specialist_publisher_properties_select(type).merge(facet_specialist_publisher_properties_validations(validations))

Expand Down
98 changes: 74 additions & 24 deletions app/views/admin/_facet_fields.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<% facet ||= {} %>
<input type="hidden" name="facets[<%= index %>][key]" value="<%=facet["key"]%>">
<input type="hidden" name="facets[<%= index %>][key]" value="<%= facet["key"] %>">

<%= render "govuk_publishing_components/components/input", {
label: {
Expand All @@ -26,27 +26,27 @@
name: "facets[#{index}][type]",
label: "Type",
options: [
{
text: "Multiple select",
value: "enum_text_multiple", # Temporary value for the form only. It ends up as "text" in the schema, and "allowed_values" is retained
selected: facet.dig("specialist_publisher_properties", "select") == "multiple",
},
{
text: "One option",
value: "enum_text_single", # Temporary value for the form only. It ends up as "text" in the schema, and "allowed_values" is retained
selected: facet.dig("specialist_publisher_properties", "select") == "one",
},
{
text: "Free text",
value: "text", # Ends up as "text" in the schema, and deletes any submitted "allowed_values" value
selected: facet["type"] == "text" && facet["allowed_values"].nil?,
},
{
text: "Date",
value: "date",
selected: facet["type"] == "date",
}
]
{
text: "Multiple select",
value: "enum_text_multiple", # Temporary value for the form only. It ends up as "text" in the schema, and "allowed_values" is retained
selected: facet.dig("specialist_publisher_properties", "select") == "multiple",
},
{
text: "One option",
value: "enum_text_single", # Temporary value for the form only. It ends up as "text" in the schema, and "allowed_values" is retained
selected: facet.dig("specialist_publisher_properties", "select") == "one",
},
{
text: "Free text",
value: "text", # Ends up as "text" in the schema, and deletes any submitted "allowed_values" value
selected: facet["type"] == "text" && facet["allowed_values"].nil?,
},
{
text: "Date",
value: "date",
selected: facet["type"] == "date",
}
]
} %>

<%= render "govuk_publishing_components/components/checkboxes", {
Expand All @@ -64,15 +64,45 @@
]
} %>

<%= render "govuk_publishing_components/components/radio", {
heading: "Is this a nested facet?",
heading_size: "s",
hint: "Select if the facet will have any sub-facets, for example country > counties.",
name: "facets[#{index}][nested_facet]",
inline: true,
items: [
{
value: "true",
text: "Yes",
checked: facet["nested_facet"]
},
{
value: "false",
text: "No",
checked: !facet["nested_facet"]
}
]
} %>

<%= render "govuk_publishing_components/components/input", {
label: {
text: "Sub-facet",
heading_size: "s",
},
name: "facets[#{index}][sub_facet]",
value: facet["nested_facet"] ? "#{facet["sub_facet_name"]} {#{facet["sub_facet_key"]}}" : "",
hint: "The name of the sub-facet. Will show as 'name {key}'. Only input the name on a new form, the key will be autogenerated. Do not edit the key."
} %>

<%= render "govuk_publishing_components/components/textarea", {
label: {
text: "Filter options ('Multiple select' or 'One option' only)",
heading_size: "s",
},
hint: sanitize("Put each option on a new line. The underlying name for existing (live) options will appear in curly braces: please don't edit these, and don't add them for new options (they'll be created automatically later on in the process). Example:<br><strong>Pre-existing value {pre-existing-value}</strong><br><strong>New value</strong>"),
hint: sanitize("Put each option on a new line. The underlying name for existing (live) options will appear in curly braces: please don't edit these, and don't add them for new options (they'll be created automatically later on in the process). Example:<br><strong>Pre-existing value {pre-existing-value}</strong><br><strong>New value</strong><br>If this is a nested facet, add nested options on subsequent lines, and leave a blank line between main facets:<br><strong>Main Facet Value</strong><br><strong>Sub Facet Value</strong><br><em>this is a blank line</em><br><strong>Another Main Facet Value</strong>"),
name: "facets[#{index}][allowed_values]",
rows: 5,
value: facet["allowed_values"]&.map { |opt| "#{opt["label"]} {#{opt["value"]}}" }&.join("\n"),
value: admin_facet_value_from_allowed_values(facet["allowed_values"], nested_facet: facet["nested_facet"]),
} %>

<%= render "govuk_publishing_components/components/radio", {
Expand Down Expand Up @@ -123,3 +153,23 @@
value: facet["preposition"],
hint: "Example: In Find funding for land or farms, selecting an option in the filter ‘Area of interest’ displays the preposition ‘With’ before the selected option"
} %>

<%= render "govuk_publishing_components/components/radio", {
heading: "Should filter values be searchable?",
heading_size: "s",
hint: "This option provides a search box for users to filter the list of options.",
name: "facets[#{index}][show_option_select_filter]",
inline: true,
items: [
{
value: "true",
text: "Yes",
checked: facet["show_option_select_filter"]
},
{
value: "false",
text: "No",
checked: !facet["show_option_select_filter"]
}
]
} %>
4 changes: 2 additions & 2 deletions lib/documents/schemas/trademark_decisions.json
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,12 @@
"key": "trademark_decision_date",
"name": "Decision date",
"preposition": "Decision date",
"short_name": "Date",
"specialist_publisher_properties": {
"validations": {
"required": {}
}
},
"short_name": "Date",
"type": "date"
},
{
Expand Down Expand Up @@ -902,14 +902,14 @@
"key": "trademark_decision_appointed_person_hearing_officer",
"name": "Appointed person/hearing officer",
"preposition": "Appointed person/hearing officer",
"short_name": "Hearing officer",
"show_option_select_filter": true,
"specialist_publisher_properties": {
"select": "one",
"validations": {
"required": {}
}
},
"short_name": "Hearing officer",
"type": "text"
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
require "spec_helper"
require "gds_api/test_helpers/support_api"

RSpec.feature "Editing the Trademark Decisions finder filters and options", type: :feature do
include GdsApi::TestHelpers::SupportApi

let(:organisations) do
[
{ "content_id" => "5d6f9583-991f-413d-ae83-be7274e5eae4", "title" => "Intellectual Property Office (IPO)" },
]
end

before do
Capybara.current_driver = Capybara.javascript_driver
log_in_as_editor(:gds_editor)
stub_publishing_api_has_content([], hash_including(document_type: TrademarkDecision.document_type))
stub_publishing_api_has_content(organisations, hash_including(document_type: Organisation.document_type))
stub_any_support_api_call
end

scenario "editing, deleting and adding new filters" do
visit "admin/trademark-decisions"
within "#facets_summary_card" do
click_link "Request changes"
end

expect(page).to have_selector("span", text: "Trademark Decision finder")
expect(page).to have_selector("h1", text: "Request change: Filters and options")

# Delete all but the first filter
all(".js-add-another__remove-button").last.click while all(".js-add-another__remove-button").count > 1
click_button "Add another filter"
within find("fieldset", text: "Filter 2") do
# 2 in "Filter 2" is the visible index of the newly inserted filter, since we deleted all but the first one, and now we're adding a new one
inserted_facet = "facets[8]" # 8 is the array index of the newly inserted filter
fill_in "#{inserted_facet}[name]", with: "New filter"
fill_in "#{inserted_facet}[short_name]", with: "short name"
select "One option", from: "#{inserted_facet}[type]"
fill_in "#{inserted_facet}[allowed_values]", with: "Main Facet 1{main-facet-1}\nSub Facet 11{sub-facet-11}\nSub Facet 12 NEW\r\n\r\nMain Facet 2{main-facet-2}\nSub Facet 21{sub-facet-21}\nSub Facet 22{sub-facet-22}\r\n\r\nMain Facet 3{main-facet-3}"
choose "#{inserted_facet}[filterable]", option: "true", visible: false
choose "#{inserted_facet}[display_as_result_metadata]", option: "false", visible: false
fill_in "#{inserted_facet}[preposition]", with: "of type NEW"

check "Required", visible: false
choose "#{inserted_facet}[nested_facet]", option: "true", visible: false
end

click_button "Submit changes"

expect(page).to have_selector(".govuk-summary-list__row", text: "New filter Added (click on 'View diff' for details)")
expect(page).to have_selector(".govuk-summary-list__row", text: "Type of hearing Deleted")
expect(page).to have_selector(".govuk-summary-list__row", text: "Mark Deleted")
expect(page).to have_selector(".govuk-summary-list__row", text: "Class Deleted")
expect(page).to have_selector(".govuk-summary-list__row", text: "Decision date Deleted")
expect(page).to have_selector(".govuk-summary-list__row", text: "Appointed person/hearing officer Deleted")
expect(page).to have_selector(".govuk-summary-list__row", text: "Person or company involved Deleted")
expect(page).to have_selector(".govuk-summary-list__row", text: "Grounds Section Deleted")
expect(page).to have_selector("details summary", text: "View diff")
expect(page).to have_selector("details summary", text: "View generated schema")

click_button "Submit changes"
expect(page).to have_selector(".gem-c-success-alert__message", text: "Your changes have been submitted and Zendesk ticket created.")
end

scenario "the generated schema is outputted to a hidden input ready for form submission" do
visit "admin/facets/trademark-decisions"
click_button "Submit changes"
hidden_input = find("[name=proposed_schema]", visible: false)
expect(hidden_input.value).to eq(JSON.pretty_generate(JSON.parse(TrademarkDecision.finder_schema.to_json)))
end
end
Loading