Merge pull request #4367 from consul/sdg_header

Allow to add header cards to SDG homepage
This commit is contained in:
Javi Martín
2021-02-26 16:35:43 +01:00
committed by GitHub
33 changed files with 172 additions and 32 deletions

View File

@@ -47,4 +47,8 @@
@include grid-row-nest;
}
}
.background-header {
margin-bottom: $line-height;
}
}

View File

@@ -1,3 +1,4 @@
.sdg-header,
.phase-cards {
> header {
align-items: flex-start;

View File

@@ -1,3 +1,3 @@
<%= back_link_to index_path %>
<%= header %>
<%= render "admin/widget/cards/form", card: card %>
<%= render "admin/widget/cards/form", card: card, url: form_path %>

View File

@@ -1,10 +1,11 @@
class Admin::Widget::Cards::EditComponent < ApplicationComponent
include Header
attr_reader :card, :index_path
attr_reader :card, :index_path, :form_path
def initialize(card, index_path:)
def initialize(card, index_path:, form_path: nil)
@card = card
@index_path = index_path
@form_path = form_path
end
private

View File

@@ -1,3 +1,3 @@
<%= back_link_to index_path %>
<%= header %>
<%= render "admin/widget/cards/form", card: card %>
<%= render "admin/widget/cards/form", card: card, url: form_path %>

View File

@@ -1,10 +1,11 @@
class Admin::Widget::Cards::NewComponent < ApplicationComponent
include Header
attr_reader :card, :index_path
attr_reader :card, :index_path, :form_path
def initialize(card, index_path:)
def initialize(card, index_path:, form_path: nil)
@card = card
@index_path = index_path
@form_path = form_path
end
private

View File

@@ -17,6 +17,6 @@
<% end %>
</td>
<td>
<%= render Admin::TableActionsComponent.new(card) %>
<%= render Admin::TableActionsComponent.new(card, options) %>
</td>
</tr>

View File

@@ -1,7 +1,8 @@
class Admin::Widget::Cards::RowComponent < ApplicationComponent
attr_reader :card
attr_reader :card, :options
def initialize(card)
def initialize(card, **options)
@card = card
@options = options
end
end

View File

@@ -11,7 +11,7 @@
</thead>
<tbody>
<% cards.each do |card| %>
<%= render Admin::Widget::Cards::RowComponent.new(card) %>
<%= render Admin::Widget::Cards::RowComponent.new(card, options) %>
<% end %>
</tbody>
</table>

View File

@@ -1,9 +1,10 @@
class Admin::Widget::Cards::TableComponent < ApplicationComponent
attr_reader :cards, :no_cards_message
attr_reader :cards, :no_cards_message, :options
def initialize(cards, no_cards_message:)
def initialize(cards, no_cards_message:, **options)
@cards = cards
@no_cards_message = no_cards_message
@options = options
end
private

View File

@@ -1,9 +1,13 @@
<% provide(:title) { title } %>
<main class="sdg-goals-index">
<header class="section-header">
<h1><%= title %></h1>
</header>
<% if header.present? %>
<%= render "shared/header", header: header %>
<% else %>
<header class="section-header">
<h1><%= title %></h1>
</header>
<% end %>
<%= render Shared::BannerComponent.new("sdg") %>

View File

@@ -1,9 +1,10 @@
class SDG::Goals::IndexComponent < ApplicationComponent
attr_reader :goals, :phases
attr_reader :goals, :header, :phases
delegate :link_list, to: :helpers
def initialize(goals, phases)
def initialize(goals, header:, phases:)
@goals = goals
@header = header
@phases = phases
end

View File

@@ -1,5 +1,22 @@
<%= header %>
<section class="sdg-header">
<header>
<h3><%= t("sdg_management.homepage.header.title") %></h3>
<% unless header_card %>
<%= link_to t("sdg_management.homepage.header.create"), new_sdg_management_homepage_header_path %>
<% end %>
</header>
<%= render Admin::Widget::Cards::TableComponent.new(
[header_card],
edit_path: edit_sdg_management_homepage_header_path,
destroy_path: sdg_management_homepage_header_path,
no_cards_message: t("sdg_management.homepage.header.no_cards")
) %>
</section>
<% phases.each do |phase| %>
<section class="phase-cards <%= phase.kind %>-cards">
<header>

View File

@@ -1,9 +1,10 @@
class SDGManagement::Homepage::ShowComponent < ApplicationComponent
include Header
attr_reader :phases
attr_reader :header_card, :phases
def initialize(phases)
def initialize(header_card, phases)
@header_card = header_card
@phases = phases
end

View File

@@ -3,6 +3,10 @@ module Admin::Widget::CardsActions
include Translatable
include ImageAttributes
included do
helper_method :form_path
end
def new
@card.header = header_card?
render template: "#{cards_view_path}/new"
@@ -56,4 +60,8 @@ module Admin::Widget::CardsActions
def cards_view_path
"admin/widget/cards"
end
def form_path
nil
end
end

View File

@@ -6,6 +6,7 @@ class SDG::GoalsController < ApplicationController
def index
@goals = @goals.order(:code)
@phases = SDG::Phase.accessible_by(current_ability).order(:kind)
@header = WebSection.find_by!(name: "sdg").header
end
def show

View File

@@ -0,0 +1,29 @@
class SDGManagement::HeaderController < SDGManagement::BaseController
include Admin::Widget::CardsActions
helper_method :index_path
before_action :load_cardable
load_and_authorize_resource :header,
class: "Widget::Card",
through: :cardable,
singleton: true,
instance_name: :card
private
def load_cardable
@cardable = WebSection.find_by!(name: "sdg")
end
def index_path
sdg_management_homepage_path
end
def form_path
sdg_management_homepage_header_path
end
def header_params
card_params
end
end

View File

@@ -1,5 +1,6 @@
class SDGManagement::HomepageController < SDGManagement::BaseController
def show
@phases = SDG::Phase.accessible_by(current_ability).order(:kind)
@card = WebSection.find_by!(name: "sdg").header
end
end

View File

@@ -6,7 +6,9 @@ class Abilities::SDG::Manager
can :read, ::SDG::Target
can :manage, ::SDG::LocalTarget
can [:read, :update, :destroy], Widget::Card, cardable_type: "SDG::Phase"
can(:create, Widget::Card) { |card| card.cardable_type == "SDG::Phase" }
can :read, WebSection, name: "sdg"
can [:create, :update, :destroy], Widget::Card do |card|
card.cardable_type == "SDG::Phase" || card.cardable&.name == "sdg"
end
end
end

View File

@@ -1,4 +1,5 @@
class WebSection < ApplicationRecord
has_many :sections
has_many :banners, through: :sections
has_one :header, class_name: "Widget::Card", as: :cardable, dependent: :destroy
end

View File

@@ -1,6 +1,6 @@
<%= render "shared/globalize_locales", resource: card %>
<%= translatable_form_for [namespace, card.cardable, card] do |f| %>
<%= translatable_form_for [namespace, card.cardable, card], url: local_assigns[:url] do |f| %>
<%= render "shared/errors", resource: card %>
<div class="row">

View File

@@ -1 +1 @@
<%= render Admin::Widget::Cards::EditComponent.new(@card, index_path: index_path) %>
<%= render Admin::Widget::Cards::EditComponent.new(@card, index_path: index_path, form_path: form_path) %>

View File

@@ -1 +1 @@
<%= render Admin::Widget::Cards::NewComponent.new(@card, index_path: index_path) %>
<%= render Admin::Widget::Cards::NewComponent.new(@card, index_path: index_path, form_path: form_path) %>

View File

@@ -1 +1 @@
<%= render SDG::Goals::IndexComponent.new(@goals, @phases) %>
<%= render SDG::Goals::IndexComponent.new(@goals, header: @header, phases: @phases) %>

View File

@@ -1 +1 @@
<%= render SDGManagement::Homepage::ShowComponent.new(@phases) %>
<%= render SDGManagement::Homepage::ShowComponent.new(@card, @phases) %>

View File

@@ -11,7 +11,7 @@
social_url: root_url %>
<% end %>
<%= render "header", header: @header %>
<%= render "shared/header", header: @header %>
<main>
<%= render "feeds" %>

View File

@@ -6,6 +6,10 @@ en:
title: "SDG content"
homepage:
title: "Homepage configuration"
header:
create: "Create header"
no_cards: "There is no header"
title: "Header"
create_card: "Create %{phase} card"
no_cards: "There are no cards for this phase"
menu:

View File

@@ -6,6 +6,10 @@ es:
title: "Contenido ODS"
homepage:
title: "Configuración de la página de inicio"
header:
create: "Crear cabecera"
no_cards: "No hay cabecera"
title: "Cabecera"
create_card: "Crear tarjeta de %{phase}"
no_cards: "No hay tarjetas para esta fase"
menu:

View File

@@ -4,7 +4,9 @@ namespace :sdg_management do
resources :goals, only: [:index]
resources :targets, only: [:index]
resources :local_targets, except: [:show]
resource :homepage, controller: :homepage, only: [:show]
resource :homepage, controller: :homepage, only: [:show] do
resource :header, controller: :header, only: [:new, :create, :edit, :update, :destroy]
end
resources :phases, only: [], as: :sdg_phases do
resources :cards, except: [:index, :show], as: :widget_cards

View File

@@ -3,16 +3,29 @@ require "rails_helper"
describe SDG::Goals::IndexComponent, type: :component do
let!(:goals) { SDG::Goal.all }
let!(:phases) { SDG::Phase.all }
let!(:component) { SDG::Goals::IndexComponent.new(goals, phases) }
let!(:component) { SDG::Goals::IndexComponent.new(goals, header: nil, phases: phases) }
before do
Setting["feature.sdg"] = true
end
it "renders a heading" do
render_inline component
describe "header" do
it "renders the default header when a custom one is not defined" do
render_inline component
expect(page).to have_css "h1", exact_text: "Sustainable Development Goals"
expect(page).to have_css "h1", exact_text: "Sustainable Development Goals"
end
it "renders a custom header" do
sdg_web_section = WebSection.find_by!(name: "sdg")
header = create(:widget_card, cardable: sdg_web_section)
component = SDG::Goals::IndexComponent.new(goals, header: header, phases: phases)
render_inline component
expect(page).to have_content header.title
expect(page).not_to have_css "h1", exact_text: "Sustainable Development Goals"
end
end
it "renders phases" do

View File

@@ -9,13 +9,19 @@ describe "Abilities::SDG::Manager" do
it { should be_able_to(:read, SDG::Target) }
it { should be_able_to(:manage, SDG::LocalTarget) }
it { should be_able_to(:read, WebSection.find_by!(name: "sdg")) }
it { should_not be_able_to(:read, SDG::Manager) }
it { should_not be_able_to(:create, SDG::Manager) }
it { should_not be_able_to(:delete, SDG::Manager) }
it { should_not be_able_to(:read, WebSection.find_by!(name: "homepage")) }
it { should_not be_able_to(:update, create(:widget_card)) }
it { should be_able_to(:update, create(:widget_card, cardable: SDG::Phase.sample)) }
it { should be_able_to(:update, create(:widget_card, cardable: WebSection.find_by!(name: "sdg"))) }
it { should_not be_able_to(:update, create(:widget_card, cardable: WebSection.find_by!(name: "homepage"))) }
it { should_not be_able_to(:create, build(:widget_card)) }
it { should be_able_to(:create, build(:widget_card, cardable: SDG::Phase.sample)) }
it { should be_able_to(:create, build(:widget_card, cardable: WebSection.find_by!(name: "sdg"))) }
it { should_not be_able_to(:create, build(:widget_card, cardable: WebSection.find_by!(name: "homepage"))) }
end

View File

@@ -48,5 +48,42 @@ describe "SDG homepage configuration", :js do
expect(page).not_to have_content "My monitoring card"
end
end
scenario "Create header card" do
visit sdg_management_homepage_path
click_link "Create header"
within(".translatable-fields") { fill_in "Title", with: "My header" }
click_button "Create card"
within(".sdg-header") do
expect(page).to have_content "My header"
expect(page).not_to have_content "Create header"
end
end
scenario "Update header card" do
create(:widget_card, cardable: WebSection.find_by!(name: "sdg"))
visit sdg_management_homepage_path
within ".sdg-header" do
click_link "Edit"
end
within(".translatable-fields") { fill_in "Title", with: "My header update" }
click_button "Save card"
expect(page).to have_content "My header update"
end
scenario "Remove header card" do
create(:widget_card, title: "SDG Header", cardable: WebSection.find_by!(name: "sdg"))
visit sdg_management_homepage_path
within ".sdg-header" do
accept_confirm { click_link "Delete" }
end
expect(page).not_to have_content "SDG Header"
end
end
end