Merge pull request #3099 from consul/backport-2740-add_milestones_to_proposals
Add milestones to proposals
This commit is contained in:
@@ -284,10 +284,6 @@ $sidebar-active: #f4fcd0;
|
||||
.proposal-form {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.proposal-show {
|
||||
padding-top: rem-calc(54);
|
||||
}
|
||||
}
|
||||
|
||||
.is-featured {
|
||||
|
||||
8
app/controllers/admin/proposal_milestones_controller.rb
Normal file
8
app/controllers/admin/proposal_milestones_controller.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class Admin::ProposalMilestonesController < Admin::MilestonesController
|
||||
|
||||
private
|
||||
|
||||
def milestoneable
|
||||
Proposal.find(params[:proposal_id])
|
||||
end
|
||||
end
|
||||
18
app/controllers/admin/proposals_controller.rb
Normal file
18
app/controllers/admin/proposals_controller.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
class Admin::ProposalsController < Admin::BaseController
|
||||
include HasOrders
|
||||
include CommentableActions
|
||||
include FeatureFlags
|
||||
feature_flag :proposals
|
||||
|
||||
has_orders %w[created_at]
|
||||
|
||||
def show
|
||||
@proposal = Proposal.find(params[:id])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_model
|
||||
Proposal
|
||||
end
|
||||
end
|
||||
@@ -20,6 +20,7 @@ class Proposal < ActiveRecord::Base
|
||||
accepted_content_types: [ "application/pdf" ]
|
||||
include EmbedVideosHelper
|
||||
include Relationable
|
||||
include Milestoneable
|
||||
|
||||
acts_as_votable
|
||||
acts_as_paranoid column: :hidden_at
|
||||
|
||||
@@ -79,6 +79,15 @@
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if feature?(:proposals) %>
|
||||
<li class="section-title">
|
||||
<%= link_to admin_proposals_path do %>
|
||||
<span class="icon-proposals"></span>
|
||||
<strong><%= t("admin.menu.proposals") %></strong>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% messages_sections = %w(newsletters emails_download admin_notifications system_emails) %>
|
||||
<% messages_menu_active = messages_sections.include?(controller_name) %>
|
||||
<li class="section-title" <%= "class=is-active" if messages_menu_active %>>
|
||||
|
||||
39
app/views/admin/proposals/index.html.erb
Normal file
39
app/views/admin/proposals/index.html.erb
Normal file
@@ -0,0 +1,39 @@
|
||||
<% provide(:title) do %>
|
||||
<%= t("admin.header.title") %> - <%= t("admin.proposals.index.title") %>
|
||||
<% end %>
|
||||
|
||||
<h2><%= t("admin.proposals.index.title") %></h2>
|
||||
|
||||
<% if @proposals.any? %>
|
||||
<%= render "/admin/shared/proposal_search", url: admin_proposals_path %>
|
||||
|
||||
<h3><%= page_entries_info @proposals %></h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><%= t("admin.proposals.index.id") %></th>
|
||||
<th><%= t("admin.proposals.index.title") %></th>
|
||||
<th><%= t("admin.proposals.index.author") %></th>
|
||||
<th><%= t("admin.proposals.index.milestones") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% @proposals.each do |proposal| %>
|
||||
<tr id="<%= dom_id(proposal) %>" class="proposal">
|
||||
<td class="text-center"><%= proposal.id %></td>
|
||||
<td><%= link_to proposal.title, admin_proposal_path(proposal) %></td>
|
||||
<td><%= proposal.author.username %></td>
|
||||
<td><%= proposal.milestones.count %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<%= paginate @proposals %>
|
||||
<% else %>
|
||||
<div class="callout primary">
|
||||
<%= t("admin.proposals.index.no_proposals") %>
|
||||
</div>
|
||||
<% end %>
|
||||
11
app/views/admin/proposals/show.html.erb
Normal file
11
app/views/admin/proposals/show.html.erb
Normal file
@@ -0,0 +1,11 @@
|
||||
<% provide :title do %>
|
||||
<%= t("admin.header.title") %> - <%= t("admin.menu.proposals") %> - <%= @proposal.title %>
|
||||
<% end %>
|
||||
|
||||
<div class="proposal-show">
|
||||
<h2><%= @proposal.title %></h2>
|
||||
|
||||
<%= render "proposals/info", proposal: @proposal %>
|
||||
</div>
|
||||
|
||||
<%= render "admin/milestones/milestones", milestoneable: @proposal %>
|
||||
@@ -21,4 +21,12 @@
|
||||
</h3>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="tabs-title">
|
||||
<%= link_to "#tab-milestones" do %>
|
||||
<h3>
|
||||
<%= t("proposals.show.milestones_tab") %>
|
||||
(<%= @proposal.milestones.count %>)
|
||||
</h3>
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
84
app/views/proposals/_info.html.erb
Normal file
84
app/views/proposals/_info.html.erb
Normal file
@@ -0,0 +1,84 @@
|
||||
<div class="proposal-info">
|
||||
<%= render '/shared/author_info', resource: @proposal %>
|
||||
|
||||
<span class="bullet"> • </span>
|
||||
<%= l @proposal.created_at.to_date %>
|
||||
<span class="bullet"> • </span>
|
||||
<span class="icon-comments"></span>
|
||||
<%= link_to t("proposals.show.comments", count: @proposal.comments_count), "#comments" %>
|
||||
|
||||
<% if current_user %>
|
||||
<span class="bullet"> • </span>
|
||||
<span class="js-flag-actions">
|
||||
<%= render 'proposals/flag_actions', proposal: @proposal %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<%= render_image(@proposal.image, :large, true) if @proposal.image.present? %>
|
||||
|
||||
<br>
|
||||
<p>
|
||||
<%= t("proposals.show.code") %>
|
||||
<strong><%= @proposal.code %></strong>
|
||||
</p>
|
||||
|
||||
<blockquote><%= @proposal.summary %></blockquote>
|
||||
|
||||
<% if @proposal.video_url.present? %>
|
||||
<div class="small-12 medium-7 small-centered">
|
||||
<div class="flex-video">
|
||||
<div id="js-embedded-video" data-video-code="<%= embedded_video_code %>"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= safe_html_with_links @proposal.description %>
|
||||
|
||||
<% if feature?(:map) && map_location_available?(@proposal.map_location) %>
|
||||
<div class="margin">
|
||||
<%= render_map(@proposal.map_location, "proposal", false, nil) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @proposal.external_url.present? %>
|
||||
<div class="document-link">
|
||||
<p>
|
||||
<span class="icon-document"></span>
|
||||
<strong><%= t('proposals.show.title_external_url') %></strong>
|
||||
</p>
|
||||
<%= text_with_links @proposal.external_url %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @proposal.video_url.present? %>
|
||||
<div class="video-link">
|
||||
<p>
|
||||
<span class="icon-video"></span>
|
||||
<strong><%= t('proposals.show.title_video_url') %></strong>
|
||||
</p>
|
||||
<%= text_with_links @proposal.video_url %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
<h4><%= @proposal.question %></h4>
|
||||
|
||||
<% if @proposal.retired? %>
|
||||
<div id="retired_explanation" class="callout">
|
||||
<h2>
|
||||
<%= t("proposals.show.retired") %>:
|
||||
<%= t("proposals.retire_options.#{@proposal.retired_reason}") unless @proposal.retired_reason == 'other' %>
|
||||
</h2>
|
||||
<%= simple_format text_with_links(@proposal.retired_explanation), {}, sanitize: false %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if feature?(:allow_attached_documents) %>
|
||||
<%= render 'documents/documents',
|
||||
documents: @proposal.documents,
|
||||
max_documents_allowed: Proposal.max_documents_allowed %>
|
||||
<% end %>
|
||||
|
||||
<%= render 'shared/tags', taggable: @proposal %>
|
||||
@@ -37,93 +37,8 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="proposal-info">
|
||||
<%= render '/shared/author_info', resource: @proposal %>
|
||||
|
||||
<span class="bullet"> • </span>
|
||||
<%= l @proposal.created_at.to_date %>
|
||||
<span class="bullet"> • </span>
|
||||
<span class="icon-comments"></span>
|
||||
<%= link_to t("proposals.show.comments", count: @proposal.comments_count), "#comments" %>
|
||||
|
||||
<% if current_user %>
|
||||
<span class="bullet"> • </span>
|
||||
<span class="js-flag-actions">
|
||||
<%= render 'proposals/flag_actions', proposal: @proposal %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<%= render_image(@proposal.image, :large, true) if @proposal.image.present? %>
|
||||
|
||||
<br>
|
||||
<p>
|
||||
<%= t("proposals.show.code") %>
|
||||
<strong><%= @proposal.code %></strong>
|
||||
</p>
|
||||
|
||||
<blockquote><%= @proposal.summary %></blockquote>
|
||||
|
||||
<% if @proposal.video_url.present? %>
|
||||
<div class="small-12 medium-7 small-centered">
|
||||
<div class="flex-video">
|
||||
<div id="js-embedded-video" data-video-code="<%= embedded_video_code %>"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= safe_html_with_links @proposal.description %>
|
||||
|
||||
<% if feature?(:map) && map_location_available?(@proposal.map_location) %>
|
||||
<div class="margin">
|
||||
<%= render_map(@proposal.map_location, "proposal", false, nil) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @proposal.external_url.present? %>
|
||||
<div class="document-link">
|
||||
<p>
|
||||
<span class="icon-document"></span>
|
||||
<strong><%= t('proposals.show.title_external_url') %></strong>
|
||||
</p>
|
||||
<%= text_with_links @proposal.external_url %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @proposal.video_url.present? %>
|
||||
<div class="video-link">
|
||||
<p>
|
||||
<span class="icon-video"></span>
|
||||
<strong><%= t('proposals.show.title_video_url') %></strong>
|
||||
</p>
|
||||
<%= text_with_links @proposal.video_url %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
<h4><%= @proposal.question %></h4>
|
||||
|
||||
<% if @proposal.retired? %>
|
||||
<div id="retired_explanation" class="callout">
|
||||
<h2>
|
||||
<%= t("proposals.show.retired") %>:
|
||||
<%= t("proposals.retire_options.#{@proposal.retired_reason}") unless @proposal.retired_reason == 'other' %>
|
||||
</h2>
|
||||
<%= simple_format text_with_links(@proposal.retired_explanation), {}, sanitize: false %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if feature?(:allow_attached_documents) %>
|
||||
<%= render 'documents/documents',
|
||||
documents: @proposal.documents,
|
||||
max_documents_allowed: Proposal.max_documents_allowed %>
|
||||
<% end %>
|
||||
|
||||
<%= render 'shared/tags', taggable: @proposal %>
|
||||
|
||||
<%= render "proposals/info", proposal: @proposal %>
|
||||
<%= render 'shared/geozone', geozonable: @proposal %>
|
||||
|
||||
<%= render 'relationable/related_content', relationable: @proposal %>
|
||||
|
||||
<div class="js-moderator-proposal-actions margin">
|
||||
@@ -225,4 +140,5 @@
|
||||
</div>
|
||||
|
||||
<%= render "proposals/notifications" %>
|
||||
<%= render "milestones/milestones", milestoneable: @proposal %>
|
||||
</div>
|
||||
|
||||
@@ -536,6 +536,7 @@ en:
|
||||
admin: Admin menu
|
||||
banner: Manage banners
|
||||
poll_questions: Questions
|
||||
proposals: Proposals
|
||||
proposals_topics: Proposals topics
|
||||
budgets: Participatory budgets
|
||||
geozones: Manage geozones
|
||||
@@ -1040,6 +1041,13 @@ en:
|
||||
search:
|
||||
title: Search Organisations
|
||||
no_results: No organizations found.
|
||||
proposals:
|
||||
index:
|
||||
title: Proposals
|
||||
id: ID
|
||||
author: Author
|
||||
milestones: Milestones
|
||||
no_proposals: There are no proposals.
|
||||
hidden_proposals:
|
||||
index:
|
||||
filter: Filter
|
||||
|
||||
@@ -437,6 +437,7 @@ en:
|
||||
flag: This proposal has been flagged as inappropriate by several users.
|
||||
login_to_comment: You must %{signin} or %{signup} to leave a comment.
|
||||
notifications_tab: Notifications
|
||||
milestones_tab: Milestones
|
||||
retired_warning: "The author considers this proposal should not receive more supports."
|
||||
retired_warning_link_to_explanation: Read the explanation before voting for it.
|
||||
retired: Proposal retired by the author
|
||||
|
||||
@@ -532,6 +532,7 @@ es:
|
||||
admin: Menú de administración
|
||||
banner: Gestionar banners
|
||||
poll_questions: Preguntas
|
||||
proposals: Propuestas
|
||||
proposals_topics: Temas de propuestas
|
||||
budgets: Presupuestos participativos
|
||||
geozones: Gestionar distritos
|
||||
@@ -1036,6 +1037,13 @@ es:
|
||||
search:
|
||||
title: Buscar Organizaciones
|
||||
no_results: No se han encontrado organizaciones.
|
||||
proposals:
|
||||
index:
|
||||
title: Propuestas
|
||||
id: ID
|
||||
author: Autor
|
||||
milestones: Hitos
|
||||
no_proposals: No hay propuestas.
|
||||
hidden_proposals:
|
||||
index:
|
||||
filter: Filtro
|
||||
|
||||
@@ -437,6 +437,7 @@ es:
|
||||
flag: Esta propuesta ha sido marcada como inapropiada por varios usuarios.
|
||||
login_to_comment: Necesitas %{signin} o %{signup} para comentar.
|
||||
notifications_tab: Notificaciones
|
||||
milestones_tab: Seguimiento
|
||||
retired_warning: "El autor de esta propuesta considera que ya no debe seguir recogiendo apoyos."
|
||||
retired_warning_link_to_explanation: Revisa su explicación antes de apoyarla.
|
||||
retired: Propuesta retirada por el autor
|
||||
|
||||
@@ -434,6 +434,7 @@ val:
|
||||
flag: Esta proposta ha sigut marcada com inapropiada per diversos usuaris.
|
||||
login_to_comment: Necessites %{signin} o %{signup} per a comentar.
|
||||
notifications_tab: Notificacions
|
||||
milestones_tab: Seguiments
|
||||
retired_warning: "L'autor d'esta proposta considera que ja no ha de seguir recollint avals."
|
||||
retired_warning_link_to_explanation: Revisa la seua explicació abans d'avalar-la.
|
||||
retired: Proposta retirada per l'autor
|
||||
|
||||
@@ -29,6 +29,10 @@ namespace :admin do
|
||||
end
|
||||
end
|
||||
|
||||
resources :proposals, only: [:index, :show] do
|
||||
resources :milestones, controller: "proposal_milestones"
|
||||
end
|
||||
|
||||
resources :hidden_proposals, only: :index do
|
||||
member do
|
||||
put :restore
|
||||
|
||||
@@ -6,17 +6,20 @@ section "Creating default Milestone Statuses" do
|
||||
end
|
||||
|
||||
section "Creating investment milestones" do
|
||||
Budget::Investment.find_each do |investment|
|
||||
rand(1..5).times do
|
||||
milestone = investment.milestones.build(
|
||||
publication_date: rand(Date.tomorrow..(Date.current + 3.weeks)),
|
||||
status_id: Milestone::Status.all.sample
|
||||
)
|
||||
I18n.available_locales.map do |locale|
|
||||
Globalize.with_locale(locale) do
|
||||
milestone.description = "Description for locale #{locale}"
|
||||
milestone.title = I18n.l(Time.current, format: :datetime)
|
||||
milestone.save!
|
||||
[Budget::Investment, Proposal].each do |model|
|
||||
model.find_each do |record|
|
||||
rand(1..5).times do
|
||||
milestone = record.milestones.build(
|
||||
publication_date: Date.tomorrow,
|
||||
status_id: Milestone::Status.all.sample
|
||||
)
|
||||
|
||||
I18n.available_locales.map do |locale|
|
||||
Globalize.with_locale(locale) do
|
||||
milestone.description = "Description for locale #{locale}"
|
||||
milestone.title = I18n.l(Time.current, format: :datetime)
|
||||
milestone.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
39
spec/features/admin/proposals_spec.rb
Normal file
39
spec/features/admin/proposals_spec.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
require "rails_helper"
|
||||
|
||||
feature "Admin proposals" do
|
||||
background do
|
||||
login_as create(:administrator).user
|
||||
end
|
||||
|
||||
it_behaves_like "admin_milestoneable",
|
||||
:proposal,
|
||||
"admin_proposal_path"
|
||||
|
||||
context "Index" do
|
||||
scenario "Search" do
|
||||
create(:proposal, title: "Make Pluto a planet again")
|
||||
create(:proposal, title: "Build a monument to honour CONSUL developers")
|
||||
|
||||
visit admin_root_path
|
||||
within("#side_menu") { click_link "Proposals" }
|
||||
|
||||
expect(page).to have_content "Make Pluto a planet again"
|
||||
expect(page).to have_content "Build a monument"
|
||||
|
||||
fill_in "search", with: "Pluto"
|
||||
click_button "Search"
|
||||
|
||||
expect(page).to have_content "Make Pluto a planet again"
|
||||
expect(page).not_to have_content "Build a monument"
|
||||
end
|
||||
end
|
||||
|
||||
scenario "Show" do
|
||||
create(:proposal, title: "Create a chaotic future", summary: "Chaos isn't controlled")
|
||||
|
||||
visit admin_proposals_path
|
||||
click_link "Create a chaotic future"
|
||||
|
||||
expect(page).to have_content "Chaos isn't controlled"
|
||||
end
|
||||
end
|
||||
@@ -21,7 +21,7 @@ feature "Admin custom information texts" do
|
||||
click_link 'Community'
|
||||
expect(page).to have_content 'Access the community'
|
||||
|
||||
click_link 'Proposals'
|
||||
within("#information-texts-tabs") { click_link "Proposals" }
|
||||
expect(page).to have_content 'Create proposal'
|
||||
|
||||
within "#information-texts-tabs" do
|
||||
@@ -49,7 +49,7 @@ feature "Admin custom information texts" do
|
||||
scenario 'check that tabs are highlight when click it' do
|
||||
visit admin_site_customization_information_texts_path
|
||||
|
||||
click_link 'Proposals'
|
||||
within("#information-texts-tabs") { click_link "Proposals" }
|
||||
expect(find("a[href=\"/admin/site_customization/information_texts?tab=proposals\"].is-active"))
|
||||
.to have_content "Proposals"
|
||||
end
|
||||
|
||||
@@ -3,6 +3,10 @@ require 'rails_helper'
|
||||
|
||||
feature 'Proposals' do
|
||||
|
||||
it_behaves_like "milestoneable",
|
||||
:proposal,
|
||||
"proposal_path"
|
||||
|
||||
scenario 'Disabled with a feature flag' do
|
||||
Setting['feature.proposals'] = nil
|
||||
expect{ visit proposals_path }.to raise_exception(FeatureFlags::FeatureDisabled)
|
||||
|
||||
Reference in New Issue
Block a user