Merge pull request #5276 from consuldemocracy/order-cards

Allow sorting homepage cards
This commit is contained in:
Sebastia
2024-03-22 10:56:33 +01:00
committed by GitHub
23 changed files with 143 additions and 20 deletions

View File

@@ -11,7 +11,7 @@ class Admin::Widget::Cards::EditComponent < ApplicationComponent
private private
def title def title
if card.header? if card.header_or_sdg_header?
t("admin.homepage.edit.header_title") t("admin.homepage.edit.header_title")
else else
t("admin.homepage.edit.card_title") t("admin.homepage.edit.card_title")

View File

@@ -11,7 +11,7 @@ class Admin::Widget::Cards::NewComponent < ApplicationComponent
private private
def title def title
if card.header? if card.header_or_sdg_header?
t("admin.homepage.new.header_title") t("admin.homepage.new.header_title")
else else
t("admin.homepage.new.card_title") t("admin.homepage.new.card_title")

View File

@@ -9,6 +9,12 @@
<%= card.link_url %> <%= card.link_url %>
</td> </td>
<% unless header_section? %>
<td>
<%= card.order %>
</td>
<% end %>
<!-- remove conditional once specs have image validations --> <!-- remove conditional once specs have image validations -->
<td> <td>
<% if card.image.present? %> <% if card.image.present? %>

View File

@@ -5,4 +5,10 @@ class Admin::Widget::Cards::RowComponent < ApplicationComponent
@card = card @card = card
@options = options @options = options
end end
private
def header_section?
card.header_or_sdg_header?
end
end end

View File

@@ -5,6 +5,9 @@
<th><%= attribute_name(:title) %></th> <th><%= attribute_name(:title) %></th>
<th class="small-4"><%= attribute_name(:description) %></th> <th class="small-4"><%= attribute_name(:description) %></th>
<th><%= attribute_name(:link_text) %> / <%= attribute_name(:link_url) %></th> <th><%= attribute_name(:link_text) %> / <%= attribute_name(:link_url) %></th>
<% unless header_section? %>
<th><%= attribute_name(:order) %></th>
<% end %>
<th><%= t("admin.shared.image") %></th> <th><%= t("admin.shared.image") %></th>
<th><%= t("admin.shared.actions") %></th> <th><%= t("admin.shared.actions") %></th>
</tr> </tr>

View File

@@ -12,4 +12,8 @@ class Admin::Widget::Cards::TableComponent < ApplicationComponent
def attribute_name(attribute) def attribute_name(attribute)
::Widget::Card.human_attribute_name(attribute) ::Widget::Card.human_attribute_name(attribute)
end end
def header_section?
cards.first.header_or_sdg_header?
end
end end

View File

@@ -19,7 +19,7 @@
<h2 class="title"><%= phase.title %></h2> <h2 class="title"><%= phase.title %></h2>
</header> </header>
<%= render "shared/cards", cards: phase.cards %> <%= render "shared/cards", cards: phase.cards.sort_by_order %>
</section> </section>
<% end %> <% end %>
</main> </main>

View File

@@ -45,7 +45,7 @@ module Admin::Widget::CardsActions
def allowed_params def allowed_params
[ [
:link_url, :button_text, :button_url, :alignment, :header, :columns, :link_url, :button_text, :button_url, :alignment, :header, :columns, :order,
translation_params(Widget::Card), translation_params(Widget::Card),
image_attributes: image_attributes image_attributes: image_attributes
] ]

View File

@@ -8,7 +8,7 @@ class PagesController < ApplicationController
@custom_page = SiteCustomization::Page.published.find_by(slug: params[:id]) @custom_page = SiteCustomization::Page.published.find_by(slug: params[:id])
if @custom_page.present? if @custom_page.present?
@cards = @custom_page.cards @cards = @custom_page.cards.sort_by_order
render action: :custom_page render action: :custom_page
else else
render action: params[:id].split(".").first render action: params[:id].split(".").first

View File

@@ -10,12 +10,23 @@ class Widget::Card < ApplicationRecord
validates_translation :title, presence: true validates_translation :title, presence: true
validates :link_url, presence: true, if: -> { !header? || link_text.present? } validates :link_url, presence: true, if: -> { !header? || link_text.present? }
validates :order, numericality: { greater_than_or_equal_to: 1 }
scope :sort_by_order, -> { order(:order, :created_at) }
def self.header def self.header
where(header: true) where(header: true)
end end
def self.body def self.body
where(header: false, cardable_id: nil).order(:created_at) where(header: false, cardable_id: nil).sort_by_order
end
def header_or_sdg_header?
header? || sdg_header?
end
def sdg_header?
cardable == WebSection.find_by!(name: "sdg")
end end
end end

View File

@@ -30,13 +30,13 @@
</div> </div>
<div class="row"> <div class="row">
<% unless card.header? %> <% unless card.header_or_sdg_header? %>
<div class="column"> <div class="small-12 medium-6 column">
<%= f.label :columns %> <%= f.select :columns, (1..12), hint: t("admin.site_customization.pages.cards.columns_help") %>
<p class="help-text"><%= t("admin.site_customization.pages.cards.columns_help") %></p> </div>
<div class="small-12 medium-4 large-2">
<%= f.select :columns, (1..12), label: false %> <div class="small-12 medium-6 column">
</div> <%= f.number_field :order, min: 1, hint: t("admin.site_customization.pages.cards.order_help") %>
</div> </div>
<% end %> <% end %>
</div> </div>
@@ -50,7 +50,7 @@
</div> </div>
<div class="column"> <div class="column">
<%= f.submit( <%= f.submit(
t("admin.homepage.#{admin_submit_action(card)}.#{card.header? ? "submit_header" : "submit_card"}"), t("admin.homepage.#{admin_submit_action(card)}.#{card.header_or_sdg_header? ? "submit_header" : "submit_card"}"),
class: "button success" class: "button success"
) %> ) %>
</div> </div>

View File

@@ -488,6 +488,7 @@ en:
link_text: Link text link_text: Link text
link_url: Link URL link_url: Link URL
columns: Number of columns columns: Number of columns
order: Position
widget/card/translation: widget/card/translation:
label: Label (optional) label: Label (optional)
title: Title title: Title

View File

@@ -1650,6 +1650,7 @@ en:
create_card: Create card create_card: Create card
no_cards: There are no cards. no_cards: There are no cards.
columns_help: "Width of the card in number of columns. On mobile screens it's always a width of 100%." columns_help: "Width of the card in number of columns. On mobile screens it's always a width of 100%."
order_help: "You can enter the position where this card will be shown."
create: create:
notice: "Card created successfully!" notice: "Card created successfully!"
update: update:

View File

@@ -488,6 +488,7 @@ es:
link_text: Texto del enlace link_text: Texto del enlace
link_url: URL del enlace link_url: URL del enlace
columns: Número de columnas columns: Número de columnas
order: Posición
widget/card/translation: widget/card/translation:
label: Etiqueta (opcional) label: Etiqueta (opcional)
title: Título title: Título

View File

@@ -1650,6 +1650,7 @@ es:
create_card: Crear tarjeta create_card: Crear tarjeta
no_cards: No hay tarjetas. no_cards: No hay tarjetas.
columns_help: "Ancho de la tarjeta en número de columnas. En pantallas móviles siempre es un ancho del 100%." columns_help: "Ancho de la tarjeta en número de columnas. En pantallas móviles siempre es un ancho del 100%."
order_help: "Puedes introducir la posición en la que se mostrará esta tarjeta."
create: create:
notice: "¡Tarjeta creada con éxito!" notice: "¡Tarjeta creada con éxito!"
update: update:

View File

@@ -0,0 +1,5 @@
class AddOrderToWidgetCards < ActiveRecord::Migration[6.1]
def change
add_column :widget_cards, :order, :integer, null: false, default: 1
end
end

View File

@@ -1768,6 +1768,7 @@ ActiveRecord::Schema.define(version: 2023_10_12_141318) do
t.integer "cardable_id" t.integer "cardable_id"
t.integer "columns", default: 4 t.integer "columns", default: 4
t.string "cardable_type", default: "SiteCustomization::Page" t.string "cardable_type", default: "SiteCustomization::Page"
t.integer "order", default: 1, null: false
t.index ["cardable_id"], name: "index_widget_cards_on_cardable_id" t.index ["cardable_id"], name: "index_widget_cards_on_cardable_id"
end end

View File

@@ -35,4 +35,28 @@ describe SDG::Goals::IndexComponent do
expect(page).to have_content "Planning" expect(page).to have_content "Planning"
expect(page).to have_content "Monitoring" expect(page).to have_content "Monitoring"
end end
describe "Cards are ordered" do
scenario "by order field" do
create(:widget_card, cardable: SDG::Phase["planning"], title: "Card One", order: 3)
create(:widget_card, cardable: SDG::Phase["planning"], title: "Card Two", order: 2)
create(:widget_card, cardable: SDG::Phase["planning"], title: "Card Three", order: 1)
render_inline component
expect("Card Three").to appear_before("Card Two")
expect("Card Two").to appear_before("Card One")
end
scenario "by created_at with cards have same order" do
create(:widget_card, cardable: SDG::Phase["planning"], title: "Card One", order: 1)
create(:widget_card, cardable: SDG::Phase["planning"], title: "Card Two", order: 1)
create(:widget_card, cardable: SDG::Phase["planning"], title: "Card Three", order: 1)
render_inline component
expect("Card One").to appear_before("Card Two")
expect("Card Two").to appear_before("Card Three")
end
end
end end

View File

@@ -18,6 +18,16 @@ describe Widget::Card do
expect(build(:widget_card, title: "")).not_to be_valid expect(build(:widget_card, title: "")).not_to be_valid
end end
describe "order" do
it "is not valid without an order" do
expect(build(:widget_card, order: nil)).not_to be_valid
end
it "is not valid with an order less than 1" do
expect(build(:widget_card, order: 0)).not_to be_valid
end
end
context "regular cards" do context "regular cards" do
it "is not valid without a link_url" do it "is not valid without a link_url" do
card = build(:widget_card, header: false, link_url: nil) card = build(:widget_card, header: false, link_url: nil)
@@ -76,5 +86,14 @@ describe Widget::Card do
expect(Widget::Card.body).not_to include(header) expect(Widget::Card.body).not_to include(header)
expect(Widget::Card.body).not_to include(page_card) expect(Widget::Card.body).not_to include(page_card)
end end
it "returns cards sorted by defined order, then by 'created_at' when order is equal" do
card1 = create(:widget_card, order: 1)
card2 = create(:widget_card, order: 3)
card3 = create(:widget_card, order: 2)
card4 = create(:widget_card, order: 3)
expect(Widget::Card.body).to eq [card1, card3, card2, card4]
end
end end
end end

View File

@@ -11,7 +11,8 @@ describe "Cards", :admin do
fill_in "Title", with: "Card text" fill_in "Title", with: "Card text"
fill_in "Description", with: "Card description" fill_in "Description", with: "Card description"
fill_in "Link text", with: "Link text" fill_in "Link text", with: "Link text"
fill_in "widget_card_link_url", with: "consul.dev" fill_in "Link URL", with: "consul.dev"
fill_in "Position", with: "12"
attach_image_to_card attach_image_to_card
click_button "Create card" click_button "Create card"
@@ -25,6 +26,7 @@ describe "Cards", :admin do
expect(page).to have_content "Card description" expect(page).to have_content "Card description"
expect(page).to have_content "Link text" expect(page).to have_content "Link text"
expect(page).to have_content "consul.dev" expect(page).to have_content "consul.dev"
expect(page).to have_css "td", exact_text: "12"
expect(page).to have_link "Show image", title: "clippy.jpg" expect(page).to have_link "Show image", title: "clippy.jpg"
end end
end end
@@ -87,7 +89,8 @@ describe "Cards", :admin do
fill_in "Link text", with: "Link text updated" fill_in "Link text", with: "Link text updated"
end end
fill_in "widget_card_link_url", with: "consul.dev updated" fill_in "Link URL", with: "consul.dev updated"
fill_in "Position", with: "2"
click_button "Save card" click_button "Save card"
expect(page).to have_content "Card updated successfully" expect(page).to have_content "Card updated successfully"
@@ -101,6 +104,7 @@ describe "Cards", :admin do
expect(page).to have_content "Card description updated" expect(page).to have_content "Card description updated"
expect(page).to have_content "Link text updated" expect(page).to have_content "Link text updated"
expect(page).to have_content "consul.dev updated" expect(page).to have_content "consul.dev updated"
expect(page).to have_css "td", exact_text: "2"
end end
end end
end end
@@ -125,6 +129,8 @@ describe "Cards", :admin do
visit admin_homepage_path visit admin_homepage_path
click_link "Create header" click_link "Create header"
expect(page).not_to have_field "Position"
fill_in "Label (optional)", with: "Header label" fill_in "Label (optional)", with: "Header label"
fill_in "Title", with: "Header text" fill_in "Title", with: "Header text"
fill_in "Description", with: "Header description" fill_in "Description", with: "Header description"
@@ -136,6 +142,7 @@ describe "Cards", :admin do
within("#header") do within("#header") do
expect(page).to have_css(".homepage-card", count: 1) expect(page).to have_css(".homepage-card", count: 1)
expect(page).not_to have_css "th", exact_text: "Position"
expect(page).to have_content "Header label" expect(page).to have_content "Header label"
expect(page).to have_content "Header text" expect(page).to have_content "Header text"
expect(page).to have_content "Header description" expect(page).to have_content "Header description"
@@ -174,10 +181,13 @@ describe "Cards", :admin do
fill_in "Title", with: "Card for a custom page" fill_in "Title", with: "Card for a custom page"
fill_in "Link URL", with: "/any_path" fill_in "Link URL", with: "/any_path"
fill_in "Position", with: "12"
click_button "Create card" click_button "Create card"
expect(page).to have_current_path admin_site_customization_page_widget_cards_path(custom_page) expect(page).to have_current_path admin_site_customization_page_widget_cards_path(custom_page)
expect(page).to have_content "Card for a custom page" expect(page).to have_content "Card for a custom page"
expect(page).to have_content "12"
expect(page).to have_css "th", exact_text: "Position"
end end
scenario "Show" do scenario "Show" do
@@ -239,12 +249,14 @@ describe "Cards", :admin do
within(".translatable-fields") do within(".translatable-fields") do
fill_in "Title", with: "Updated title" fill_in "Title", with: "Updated title"
end end
fill_in "Position", with: "2"
click_button "Save card" click_button "Save card"
expect(page).to have_current_path admin_site_customization_page_widget_cards_path(custom_page) expect(page).to have_current_path admin_site_customization_page_widget_cards_path(custom_page)
expect(page).to have_content "Updated title" expect(page).to have_content "Updated title"
expect(page).not_to have_content "Original title" expect(page).not_to have_content "Original title"
expect(page).to have_css "td", exact_text: "2"
end end
scenario "Destroy" do scenario "Destroy" do

View File

@@ -143,6 +143,21 @@ describe "Home" do
expect(page).not_to have_css(".title", text: "Featured") expect(page).not_to have_css(".title", text: "Featured")
end end
scenario "cards are first sorted by 'order' field, then by 'created_at' when order is equal" do
create(:widget_card, title: "Card one", order: 1)
create(:widget_card, title: "Card two", order: 3)
create(:widget_card, title: "Card three", order: 2)
create(:widget_card, title: "Card four", order: 3)
visit root_path
within(".cards-container") do
expect("CARD ONE").to appear_before("CARD THREE")
expect("CARD THREE").to appear_before("CARD TWO")
expect("CARD TWO").to appear_before("CARD FOUR")
end
end
describe "Header Card" do describe "Header Card" do
scenario "if there is header card with link, the link content is rendered" do scenario "if there is header card with link, the link content is rendered" do
create(:widget_card, :header, link_text: "Link text", link_url: "consul.dev") create(:widget_card, :header, link_text: "Link text", link_url: "consul.dev")

View File

@@ -21,12 +21,18 @@ describe "SDG homepage configuration" do
visit sdg_management_homepage_path visit sdg_management_homepage_path
click_link "Create planning card" click_link "Create planning card"
expect(page).to have_field "Number of columns"
expect(page).to have_field "Position"
within(".translatable-fields") { fill_in "Title", with: "My planning card" } within(".translatable-fields") { fill_in "Title", with: "My planning card" }
fill_in "Link URL", with: "/any_path" fill_in "Link URL", with: "/any_path"
fill_in "Position", with: "2"
click_button "Create card" click_button "Create card"
within(".planning-cards") do within(".planning-cards") do
expect(page).to have_content "My planning card" expect(page).to have_content "My planning card"
expect(page).to have_css "th", exact_text: "Position"
expect(page).to have_css "td", exact_text: "2"
end end
within(".sensitization-cards") do within(".sensitization-cards") do
@@ -54,13 +60,17 @@ describe "SDG homepage configuration" do
visit sdg_management_homepage_path visit sdg_management_homepage_path
click_link "Create header" click_link "Create header"
expect(page).not_to have_field "Number of columns"
expect(page).not_to have_field "Position"
within(".translatable-fields") { fill_in "Title", with: "My header" } within(".translatable-fields") { fill_in "Title", with: "My header" }
fill_in "Link URL", with: "/any_path" fill_in "Link URL", with: "/any_path"
click_button "Create card" click_button "Create header"
within(".sdg-header") do within(".sdg-header") do
expect(page).to have_content "My header" expect(page).to have_content "My header"
expect(page).not_to have_content "Create header" expect(page).not_to have_content "Create header"
expect(page).not_to have_css "th", exact_text: "Position"
end end
end end
@@ -72,7 +82,7 @@ describe "SDG homepage configuration" do
end end
within(".translatable-fields") { fill_in "Title", with: "My header update" } within(".translatable-fields") { fill_in "Title", with: "My header update" }
click_button "Save card" click_button "Save header"
expect(page).to have_content "My header update" expect(page).to have_content "My header update"
end end

View File

@@ -94,11 +94,14 @@ describe "Custom Pages" do
scenario "Show widget cards for that page" do scenario "Show widget cards for that page" do
custom_page = create(:site_customization_page, :published) custom_page = create(:site_customization_page, :published)
create(:widget_card, cardable: custom_page, title: "Card Highlights") create(:widget_card, cardable: custom_page, title: "Medium prominent card", order: 2)
create(:widget_card, cardable: custom_page, title: "Less prominent card", order: 2)
create(:widget_card, cardable: custom_page, title: "Card Highlights", order: 1)
visit custom_page.url visit custom_page.url
expect(page).to have_content "CARD HIGHLIGHTS" expect("CARD HIGHLIGHTS").to appear_before("MEDIUM PROMINENT CARD")
expect("MEDIUM PROMINENT CARD").to appear_before("LESS PROMINENT CARD")
end end
end end
end end