decidim-proposals 0.8.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>