decidim-proposals 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/app/assets/config/decidim_proposals_manifest.css +3 -1
  4. data/app/assets/javascripts/decidim/proposals/add_proposal.js.es6 +0 -2
  5. data/app/assets/stylesheets/decidim/proposals/social_share.css.scss +8 -5
  6. data/app/commands/decidim/proposals/admin/answer_proposal.rb +32 -0
  7. data/app/commands/decidim/proposals/admin/create_proposal_note.rb +47 -0
  8. data/app/commands/decidim/proposals/create_proposal.rb +12 -0
  9. data/app/commands/decidim/proposals/withdraw_proposal.rb +37 -0
  10. data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +40 -0
  11. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +6 -2
  12. data/app/controllers/decidim/proposals/proposals_controller.rb +20 -5
  13. data/app/events/decidim/proposals/accepted_proposal_event.rb +9 -0
  14. data/app/events/decidim/proposals/create_proposal_event.rb +9 -0
  15. data/app/events/decidim/proposals/evaluating_proposal_event.rb +8 -0
  16. data/app/events/decidim/proposals/rejected_proposal_event.rb +9 -0
  17. data/app/forms/decidim/proposals/admin/proposal_form.rb +12 -12
  18. data/app/forms/decidim/proposals/admin/proposal_note_form.rb +16 -0
  19. data/app/forms/decidim/proposals/proposal_form.rb +11 -11
  20. data/app/helpers/decidim/proposals/application_helper.rb +2 -0
  21. data/app/models/decidim/proposals/abilities/admin_ability.rb +1 -0
  22. data/app/models/decidim/proposals/abilities/current_user_ability.rb +8 -0
  23. data/app/models/decidim/proposals/abilities/participatory_process_admin_ability.rb +1 -0
  24. data/app/models/decidim/proposals/proposal.rb +30 -9
  25. data/app/models/decidim/proposals/proposal_note.rb +13 -0
  26. data/app/presenters/decidim/proposals/official_author_presenter.rb +34 -0
  27. data/app/presenters/decidim/proposals/proposal_presenter.rb +20 -0
  28. data/app/services/decidim/proposals/proposal_search.rb +4 -2
  29. data/app/views/decidim/proposals/admin/proposal_answers/edit.html.erb +1 -0
  30. data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +8 -0
  31. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +45 -0
  32. data/app/views/decidim/proposals/admin/proposal_notes/index.html.erb +3 -0
  33. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +1 -1
  34. data/app/views/decidim/proposals/admin/proposals/index.html.erb +60 -8
  35. data/app/views/decidim/proposals/admin/shared/_info_proposal.html.erb +20 -0
  36. data/app/views/decidim/proposals/proposals/_filters.html.erb +1 -1
  37. data/app/views/decidim/proposals/proposals/_linked_proposals.html.erb +11 -8
  38. data/app/views/decidim/proposals/proposals/_proposal.html.erb +5 -2
  39. data/app/views/decidim/proposals/proposals/_proposal_badge.html.erb +3 -0
  40. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +3 -3
  41. data/app/views/decidim/proposals/proposals/edit.html.erb +2 -2
  42. data/app/views/decidim/proposals/proposals/index.html.erb +6 -2
  43. data/app/views/decidim/proposals/proposals/new.html.erb +2 -2
  44. data/app/views/decidim/proposals/proposals/show.html.erb +8 -4
  45. data/config/locales/ca.yml +46 -7
  46. data/config/locales/en.yml +46 -6
  47. data/config/locales/es.yml +46 -7
  48. data/config/locales/eu.yml +46 -7
  49. data/config/locales/fi.yml +58 -19
  50. data/config/locales/fr.yml +53 -14
  51. data/config/locales/gl.yml +231 -0
  52. data/config/locales/it.yml +46 -7
  53. data/config/locales/nl.yml +46 -7
  54. data/config/locales/pl.yml +46 -7
  55. data/config/locales/pt-BR.yml +231 -0
  56. data/config/locales/pt.yml +48 -9
  57. data/config/locales/ru.yml +7 -7
  58. data/config/locales/sv.yml +231 -0
  59. data/config/locales/uk.yml +7 -7
  60. data/db/migrate/20170215132030_add_reference_to_proposals.rb +5 -1
  61. data/db/migrate/20180111110204_create_decidim_proposal_notes.rb +15 -0
  62. data/db/migrate/20180115155220_add_index_created_at_proposal_notes.rb +7 -0
  63. data/lib/decidim/proposals/admin_engine.rb +1 -0
  64. data/lib/decidim/proposals/engine.rb +4 -0
  65. data/lib/decidim/proposals/feature.rb +21 -6
  66. data/lib/decidim/proposals/test/factories.rb +10 -0
  67. data/lib/decidim/proposals/version.rb +1 -1
  68. metadata +60 -27
  69. data/app/views/decidim/proposals/proposals/_author.html.erb +0 -23
@@ -24,6 +24,7 @@ module Decidim
24
24
  validates :address, presence: true, if: ->(form) { form.has_address? }
25
25
  validates :category, presence: true, if: ->(form) { form.category_id.present? }
26
26
  validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
27
+ validate { errors.add(:scope_id, :invalid) if current_participatory_space&.scope && !current_participatory_space&.scope&.ancestor_of?(scope) }
27
28
 
28
29
  delegate :categories, to: :current_feature
29
30
 
@@ -33,28 +34,27 @@ module Decidim
33
34
  self.category_id = model.categorization.decidim_category_id
34
35
  end
35
36
 
36
- def organization_scopes
37
- current_organization.scopes
38
- end
39
-
40
- def process_scope
41
- current_feature.participatory_space.scope
42
- end
43
-
44
37
  alias feature current_feature
45
38
 
46
39
  # Finds the Category from the category_id.
47
40
  #
48
41
  # Returns a Decidim::Category
49
42
  def category
50
- @category ||= categories.where(id: category_id).first
43
+ @category ||= categories.find_by(id: category_id)
51
44
  end
52
45
 
53
- # Finds the Scope from the scope_id.
46
+ # Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
54
47
  #
55
48
  # Returns a Decidim::Scope
56
49
  def scope
57
- @scope ||= organization_scopes.where(id: scope_id).first || process_scope
50
+ @scope ||= @scope_id ? current_feature.scopes.find_by(id: @scope_id) : current_participatory_space&.scope
51
+ end
52
+
53
+ # Scope identifier
54
+ #
55
+ # Returns the scope identifier related to the proposal
56
+ def scope_id
57
+ @scope_id || scope&.id
58
58
  end
59
59
 
60
60
  def has_address?
@@ -52,6 +52,8 @@ module Decidim
52
52
  "warning"
53
53
  when "evaluating"
54
54
  "secondary"
55
+ when "withdrawn"
56
+ "alert"
55
57
  end
56
58
  end
57
59
 
@@ -14,6 +14,7 @@ module Decidim
14
14
  can :hide, Proposal
15
15
  cannot :create, Proposal unless can_create_proposal?
16
16
  cannot :update, Proposal unless can_update_proposal?
17
+ can :create, ProposalNote
17
18
  end
18
19
 
19
20
  private
@@ -29,6 +29,10 @@ module Decidim
29
29
  proposal.editable_by?(user)
30
30
  end
31
31
 
32
+ can :withdraw, Proposal do |proposal|
33
+ can_withdraw?(proposal)
34
+ end
35
+
32
36
  can :report, Proposal
33
37
  end
34
38
 
@@ -77,6 +81,10 @@ module Decidim
77
81
 
78
82
  feature
79
83
  end
84
+
85
+ def can_withdraw?(proposal)
86
+ proposal.decidim_author_id == @user.id
87
+ end
80
88
  end
81
89
  end
82
90
  end
@@ -15,6 +15,7 @@ module Decidim
15
15
 
16
16
  cannot :create, Proposal unless can_create_proposal?
17
17
  cannot :update, Proposal unless can_update_proposal?
18
+ can :create, ProposalNote
18
19
  end
19
20
 
20
21
  private
@@ -18,6 +18,7 @@ module Decidim
18
18
  feature_manifest_name "proposals"
19
19
 
20
20
  has_many :votes, foreign_key: "decidim_proposal_id", class_name: "ProposalVote", dependent: :destroy, counter_cache: "proposal_votes_count"
21
+ has_many :notes, foreign_key: "decidim_proposal_id", class_name: "ProposalNote", dependent: :destroy, counter_cache: "proposal_notes_count"
21
22
 
22
23
  validates :title, :body, presence: true
23
24
 
@@ -26,6 +27,8 @@ module Decidim
26
27
  scope :accepted, -> { where(state: "accepted") }
27
28
  scope :rejected, -> { where(state: "rejected") }
28
29
  scope :evaluating, -> { where(state: "evaluating") }
30
+ scope :withdrawn, -> { where(state: "withdrawn") }
31
+ scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
29
32
 
30
33
  def self.order_randomly(seed)
31
34
  transaction do
@@ -34,15 +37,6 @@ module Decidim
34
37
  end
35
38
  end
36
39
 
37
- def author_name
38
- return I18n.t("decidim.proposals.models.proposal.fields.official_proposal") if official?
39
- user_group&.name || author.name
40
- end
41
-
42
- def author_avatar_url
43
- author&.avatar&.url || ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
44
- end
45
-
46
40
  # Public: Check if the user has voted the proposal.
47
41
  #
48
42
  # Returns Boolean.
@@ -78,6 +72,13 @@ module Decidim
78
72
  answered? && state == "evaluating"
79
73
  end
80
74
 
75
+ # Public: Checks if the author has withdrawn the proposal.
76
+ #
77
+ # Returns Boolean.
78
+ def withdrawn?
79
+ state == "withdrawn"
80
+ end
81
+
81
82
  # Public: Overrides the `commentable?` Commentable concern method.
82
83
  def commentable?
83
84
  feature.settings.comments_enabled?
@@ -148,6 +149,26 @@ module Decidim
148
149
  authored_by?(user) && !answered? && within_edit_time_limit?
149
150
  end
150
151
 
152
+ # Checks whether the user can withdraw the given proposal.
153
+ #
154
+ # user - the user to check for withdrawability.
155
+ def withdrawable_by?(user)
156
+ user && !withdrawn? && authored_by?(user)
157
+ end
158
+
159
+ # method for sort_link by number of comments
160
+ ransacker :commentable_comments_count do
161
+ query = <<-SQL
162
+ (SELECT COUNT(decidim_comments_comments.id)
163
+ FROM decidim_comments_comments
164
+ WHERE decidim_comments_comments.decidim_commentable_id = decidim_proposals_proposals.id
165
+ AND decidim_comments_comments.decidim_commentable_type = 'Decidim::Proposals::Proposal'
166
+ GROUP BY decidim_comments_comments.decidim_commentable_id
167
+ )
168
+ SQL
169
+ Arel.sql(query)
170
+ end
171
+
151
172
  private
152
173
 
153
174
  # Checks whether the proposal is inside the time window to be editable or not.
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # A proposal can include a notes created by admins.
6
+ class ProposalNote < ApplicationRecord
7
+ belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal", counter_cache: true
8
+ belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
9
+
10
+ default_scope { order(created_at: :asc) }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ #
6
+ # A dummy presenter to abstract out the author of an official proposal.
7
+ #
8
+ class OfficialAuthorPresenter
9
+ def name
10
+ I18n.t("decidim.proposals.models.proposal.fields.official_proposal")
11
+ end
12
+
13
+ def nickname
14
+ ""
15
+ end
16
+
17
+ def badge
18
+ ""
19
+ end
20
+
21
+ def profile_path
22
+ ""
23
+ end
24
+
25
+ def avatar_url
26
+ ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
27
+ end
28
+
29
+ def deleted?
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ #
6
+ # Decorator for proposals
7
+ #
8
+ class ProposalPresenter < SimpleDelegator
9
+ def author
10
+ @author ||= if official?
11
+ Decidim::Proposals::OfficialAuthorPresenter.new
12
+ elsif user_group
13
+ Decidim::UserGroupPresenter.new(user_group)
14
+ else
15
+ Decidim::UserPresenter.new(super)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -54,8 +54,10 @@ module Decidim
54
54
  query.rejected
55
55
  when "evaluating"
56
56
  query.evaluating
57
- else # Assume 'all'
58
- query
57
+ when "withdrawn"
58
+ query.withdrawn
59
+ else # Assume 'not_withdrawn'
60
+ query.except_withdrawn
59
61
  end
60
62
  end
61
63
 
@@ -1,3 +1,4 @@
1
+ <%= render 'decidim/proposals/admin/shared/info_proposal' %>
1
2
  <%= decidim_form_for(@form, url: proposal_proposal_answer_path(proposal, @form), html: { class: "form edit_proposal_answer" }) do |f| %>
2
3
  <div class="card">
3
4
  <div class="card-divider">
@@ -0,0 +1,8 @@
1
+ <%= decidim_form_for(@form, url: proposal_proposal_notes_path(proposal, @form), html: { class: "form new_proposal_note" }) do |f| %>
2
+ <div class="row column">
3
+ <%= f.text_area :body, rows: 10, label: t('.note') %>
4
+ </div>
5
+ <div class="button--double form-general-submit">
6
+ <%= f.submit t(".submit") %>
7
+ </div>
8
+ <% end %>
@@ -0,0 +1,45 @@
1
+ <section class="comments">
2
+ <div class="card">
3
+ <div class="card-divider">
4
+ <h2 class="card-title"><%= t("title", scope: 'decidim.proposals.admin.proposal_notes') %></h2>
5
+ </div>
6
+
7
+ <div class="card-section">
8
+ <div class="comment-thread">
9
+ <% proposal.notes.each do |note| %>
10
+ <div class="card">
11
+ <div class="card-divider">
12
+ <article class="comment">
13
+ <div class="comment__header">
14
+ <div class="author-data">
15
+ <div class="author-data__main">
16
+ <div class="author author--inline">
17
+ <strong><span class="author__name"><%= note.author.name %></span></strong>
18
+ <span><%= l note.created_at, format: :decidim_short %></span>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ <div class="comment__content">
24
+ <%= note.body %>
25
+ </div>
26
+ </article>
27
+ </div>
28
+ </div>
29
+ <% end %>
30
+ </div>
31
+ </div>
32
+ <div class="card-section">
33
+ <div class="add-comment">
34
+ <div class="card">
35
+ <div class="card-divider">
36
+ <h2 class="card-title"><%= t("leave_your_note", scope: 'decidim.proposals.admin.proposal_notes') %></h2>
37
+ </div>
38
+ <div class="card-section">
39
+ <%= render 'form' %>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </section>
@@ -0,0 +1,3 @@
1
+ <%= render 'decidim/proposals/admin/shared/info_proposal' %>
2
+
3
+ <%= render "proposal_notes" %>
@@ -26,7 +26,7 @@
26
26
 
27
27
  <% if current_participatory_space.has_subscopes? %>
28
28
  <div class="row column">
29
- <%= form.scopes_select :scope_id, prompt: I18n.t("decidim.scopes.global"), remote_path: decidim.scopes_search_path(root: current_participatory_space.scope) %>
29
+ <%= scopes_picker_field form, :scope_id %>
30
30
  </div>
31
31
  <% end %>
32
32
 
@@ -19,19 +19,48 @@
19
19
  <table class="table-list">
20
20
  <thead>
21
21
  <tr>
22
- <th><%= t("models.proposal.fields.id", scope: "decidim.proposals") %></th>
23
- <th><%= t("models.proposal.fields.title", scope: "decidim.proposals") %></th>
24
- <th><%= t("models.proposal.fields.category", scope: "decidim.proposals") %></th>
22
+ <th>
23
+ <%= sort_link(query, :id, t("models.proposal.fields.id", scope: "decidim.proposals"), default_order: :desc ) %>
24
+ </th>
25
+ <th>
26
+ <%= sort_link(query, :title, t("models.proposal.fields.title", scope: "decidim.proposals") ) %>
27
+ </th>
28
+ <th>
29
+ <%= sort_link(query, :category_name, t("models.proposal.fields.category", scope: "decidim.proposals") ) %>
30
+ </th>
31
+
25
32
  <% if scopes_enabled?(current_participatory_space) %>
26
- <th><%= t("models.proposal.fields.scope", scope: "decidim.proposals") %></th>
33
+ <th>
34
+ <%= sort_link(query, :scope_name, t("models.proposal.fields.scope", scope: "decidim.proposals") ) %>
35
+ </th>
27
36
  <% end %>
28
37
 
29
- <th><%= t("models.proposal.fields.state", scope: "decidim.proposals") %></th>
38
+ <th>
39
+ <%= sort_link(query, :state, t("models.proposal.fields.state", scope: "decidim.proposals") ) %>
40
+ </th>
30
41
 
31
42
  <% if current_settings.votes_enabled? %>
32
- <th><%= t("models.proposal.fields.votes", scope: "decidim.proposals") %></th>
43
+ <th>
44
+ <%= sort_link(query, :proposal_votes_count, t("models.proposal.fields.votes", scope: "decidim.proposals") ) %>
45
+ </th>
46
+ <% end %>
47
+
48
+ <% if current_feature.settings.comments_enabled? and !current_settings.comments_blocked? %>
49
+ <th>
50
+ <%= sort_link(query, :commentable_comments_count, t("models.proposal.fields.comments", scope: "decidim.proposals") ) %>
51
+ </th>
33
52
  <% end %>
34
53
 
54
+ <% if can? :create, Decidim::Proposals::ProposalNote %>
55
+ <th>
56
+ <%= sort_link(query, :proposal_notes_count, t("models.proposal.fields.notes", scope: "decidim.proposals") ) %>
57
+ </th>
58
+ <% end %>
59
+
60
+ <th>
61
+ <%= sort_link(query, :created_at, t("models.proposal.fields.created_at", scope: "decidim.proposals") ) %>
62
+ </th>
63
+
35
64
  <th class="actions"><%= t("actions.title", scope: "decidim.proposals") %></th>
36
65
  </tr>
37
66
  </thead>
@@ -63,14 +92,37 @@
63
92
  <%= humanize_proposal_state proposal.state %>
64
93
  </strong>
65
94
  </td>
95
+
66
96
  <% if current_settings.votes_enabled? %>
67
97
  <td>
68
- <%= proposal.votes.count %>
98
+ <%= proposal.proposal_votes_count %>
99
+ </td>
100
+ <% end %>
101
+
102
+ <% if current_feature.settings.comments_enabled? and !current_settings.comments_blocked? %>
103
+ <td>
104
+ <%= proposal.comments.count %>
69
105
  </td>
70
106
  <% end %>
107
+
108
+ <% if can? :create, Decidim::Proposals::ProposalNote %>
109
+ <td>
110
+ <%= proposal.proposal_notes_count %>
111
+ </td>
112
+ <% end %>
113
+
114
+ <td>
115
+ <%= l proposal.created_at, format: :decidim_short %>
116
+ </td>
117
+
71
118
  <td class="table-list__actions">
119
+
120
+ <% if can? :create, Decidim::Proposals::ProposalNote %>
121
+ <%= icon_link_to "chat", proposal_proposal_notes_path(proposal_id: proposal.id), t("actions.private_notes", scope: "decidim.proposals"), class: "action-icon--index-notes" %>
122
+ <% end %>
123
+
72
124
  <% if can? :update, proposal %>
73
- <%= icon_link_to "conversation", edit_proposal_proposal_answer_path(proposal_id: proposal.id, id: proposal.id), t("actions.answer", scope: "decidim.proposals"), class: "action-icon--edit-answer" %>
125
+ <%= icon_link_to "comment-square", edit_proposal_proposal_answer_path(proposal_id: proposal.id, id: proposal.id), t("actions.answer", scope: "decidim.proposals"), class: "action-icon--edit-answer" %>
74
126
  <% end %>
75
127
  <%= icon_link_to "eye", resource_locator(proposal).path, t("actions.preview", scope: "decidim.proposals.admin"), class: "action-icon--preview", target: :blank %>
76
128
  </td>
@@ -0,0 +1,20 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title">
4
+ <%= link_to "#{t ".proposals"} > ", proposals_path %>
5
+ <%= proposal.title %>
6
+ </h2>
7
+ </div>
8
+
9
+ <div class="card-section">
10
+ <div class="row column">
11
+ <strong><%= t ".body" %>: </strong><%= proposal.body %>
12
+ </div>
13
+ <div class="row column">
14
+ <strong><%= t ".created_at" %>: </strong> <%= l proposal.created_at, format: :decidim_short %>
15
+ </div>
16
+ <div class="row column">
17
+ <strong><%= t ".proposal_votes_count" %>: </strong> <%= proposal.proposal_votes_count %>
18
+ </div>
19
+ </div>
20
+ </div>