decidim-debates 0.19.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/decidim/gamification/badges/commented_debates.svg +1 -78
  3. data/app/cells/decidim/debates/debate_activity_cell.rb +4 -0
  4. data/app/cells/decidim/debates/debate_m/data.erb +10 -8
  5. data/app/cells/decidim/debates/debate_m/footer.erb +6 -1
  6. data/app/cells/decidim/debates/debate_m/multiple_dates.erb +1 -1
  7. data/app/cells/decidim/debates/debate_m_cell.rb +27 -1
  8. data/app/commands/decidim/debates/admin/close_debate.rb +54 -0
  9. data/app/commands/decidim/debates/admin/create_debate.rb +4 -2
  10. data/app/commands/decidim/debates/admin/update_debate.rb +5 -2
  11. data/app/commands/decidim/debates/close_debate.rb +58 -0
  12. data/app/commands/decidim/debates/create_debate.rb +17 -12
  13. data/app/commands/decidim/debates/update_debate.rb +56 -0
  14. data/app/controllers/decidim/debates/admin/debate_closes_controller.rb +43 -0
  15. data/app/controllers/decidim/debates/admin/debates_controller.rb +4 -2
  16. data/app/controllers/decidim/debates/debates_controller.rb +70 -9
  17. data/app/controllers/decidim/debates/orderable.rb +41 -0
  18. data/app/controllers/decidim/debates/versions_controller.rb +16 -0
  19. data/app/controllers/decidim/debates/widgets_controller.rb +19 -0
  20. data/app/events/decidim/debates/close_debate_event.rb +15 -0
  21. data/app/events/decidim/debates/create_debate_event.rb +0 -2
  22. data/app/forms/decidim/debates/admin/close_debate_form.rb +34 -0
  23. data/app/forms/decidim/debates/admin/debate_form.rb +5 -1
  24. data/app/forms/decidim/debates/close_debate_form.rb +39 -0
  25. data/app/forms/decidim/debates/debate_form.rb +27 -1
  26. data/app/helpers/decidim/debates/admin/application_helper.rb +13 -0
  27. data/app/helpers/decidim/debates/application_helper.rb +55 -0
  28. data/app/models/decidim/debates/debate.rb +70 -8
  29. data/app/permissions/decidim/debates/admin/permissions.rb +3 -1
  30. data/app/permissions/decidim/debates/permissions.rb +28 -0
  31. data/app/presenters/decidim/debates/admin_log/debate_presenter.rb +6 -2
  32. data/app/presenters/decidim/debates/admin_log/value_types/debate_title_description_presenter.rb +20 -0
  33. data/app/presenters/decidim/debates/debate_presenter.rb +64 -3
  34. data/app/presenters/decidim/debates/log/resource_presenter.rb +18 -0
  35. data/app/presenters/decidim/debates/official_author_presenter.rb +1 -29
  36. data/app/queries/decidim/debates/metrics/debates_metric_manage.rb +2 -8
  37. data/app/services/decidim/debates/debate_search.rb +13 -23
  38. data/app/services/decidim/debates/diff_renderer.rb +27 -0
  39. data/app/types/decidim/debates/debate_type.rb +28 -0
  40. data/app/types/decidim/debates/debates_type.rb +32 -0
  41. data/app/views/decidim/debates/admin/debate_closes/edit.html.erb +17 -0
  42. data/app/views/decidim/debates/admin/debates/_form.html.erb +5 -5
  43. data/app/views/decidim/debates/admin/debates/index.html.erb +7 -1
  44. data/app/views/decidim/debates/debates/_close_debate_modal.html.erb +13 -0
  45. data/app/views/decidim/debates/debates/_debates.html.erb +7 -0
  46. data/app/views/decidim/debates/debates/_filters.html.erb +20 -6
  47. data/app/views/decidim/debates/debates/_form.html.erb +19 -0
  48. data/app/views/decidim/debates/debates/edit.html.erb +22 -0
  49. data/app/views/decidim/debates/debates/index.html.erb +5 -6
  50. data/app/views/decidim/debates/debates/new.html.erb +2 -23
  51. data/app/views/decidim/debates/debates/show.html.erb +86 -10
  52. data/app/views/decidim/debates/versions/index.html.erb +8 -0
  53. data/app/views/decidim/debates/versions/show.html.erb +10 -0
  54. data/config/locales/am-ET.yml +1 -0
  55. data/config/locales/ar.yml +2 -1
  56. data/config/locales/bg-BG.yml +7 -0
  57. data/config/locales/bg.yml +15 -0
  58. data/config/locales/ca.yml +73 -1
  59. data/config/locales/cs.yml +112 -40
  60. data/config/locales/da-DK.yml +1 -0
  61. data/config/locales/da.yml +1 -0
  62. data/config/locales/de.yml +51 -1
  63. data/config/locales/el-GR.yml +1 -0
  64. data/config/locales/el.yml +155 -0
  65. data/config/locales/en.yml +73 -1
  66. data/config/locales/eo.yml +1 -0
  67. data/config/locales/es-MX.yml +73 -1
  68. data/config/locales/es-PY.yml +73 -1
  69. data/config/locales/es.yml +73 -1
  70. data/config/locales/et-EE.yml +1 -0
  71. data/config/locales/et.yml +1 -0
  72. data/config/locales/eu.yml +2 -1
  73. data/config/locales/fi-plain.yml +73 -1
  74. data/config/locales/fi.yml +78 -6
  75. data/config/locales/fr-CA.yml +223 -0
  76. data/config/locales/fr.yml +73 -1
  77. data/config/locales/ga-IE.yml +1 -0
  78. data/config/locales/gl.yml +2 -1
  79. data/config/locales/hr-HR.yml +1 -0
  80. data/config/locales/hr.yml +1 -0
  81. data/config/locales/hu.yml +31 -6
  82. data/config/locales/id-ID.yml +2 -1
  83. data/config/locales/is-IS.yml +102 -0
  84. data/config/locales/is.yml +102 -0
  85. data/config/locales/it.yml +45 -10
  86. data/config/locales/ja-JP.yml +195 -0
  87. data/config/locales/ja.yml +221 -0
  88. data/config/locales/ko-KR.yml +1 -0
  89. data/config/locales/ko.yml +1 -0
  90. data/config/locales/lt-LT.yml +1 -0
  91. data/config/locales/lt.yml +1 -0
  92. data/config/locales/lv.yml +135 -0
  93. data/config/locales/mt-MT.yml +1 -0
  94. data/config/locales/mt.yml +1 -0
  95. data/config/locales/nl.yml +51 -3
  96. data/config/locales/no.yml +159 -9
  97. data/config/locales/om-ET.yml +1 -0
  98. data/config/locales/pl.yml +100 -45
  99. data/config/locales/pt-BR.yml +3 -4
  100. data/config/locales/pt.yml +56 -36
  101. data/config/locales/ro-RO.yml +173 -0
  102. data/config/locales/ru.yml +2 -1
  103. data/config/locales/sk-SK.yml +156 -0
  104. data/config/locales/sk.yml +157 -0
  105. data/config/locales/sl.yml +22 -0
  106. data/config/locales/so-SO.yml +1 -0
  107. data/config/locales/sr-CS.yml +6 -0
  108. data/config/locales/sv.yml +54 -1
  109. data/config/locales/ti-ER.yml +1 -0
  110. data/config/locales/tr-TR.yml +2 -1
  111. data/config/locales/uk.yml +2 -1
  112. data/config/locales/vi-VN.yml +1 -0
  113. data/config/locales/vi.yml +1 -0
  114. data/config/locales/zh-CN.yml +218 -0
  115. data/config/locales/zh-TW.yml +1 -0
  116. data/db/migrate/20200320105918_index_foreign_keys_in_decidim_debates_debates.rb +7 -0
  117. data/db/migrate/20200703134657_close_debates.rb +9 -0
  118. data/db/migrate/20200708072042_fix_debates_i18n_fields.rb +48 -0
  119. data/db/migrate/20200716143929_add_endorsable_to_debates.rb +8 -0
  120. data/db/migrate/20200827154116_add_commentable_counter_cache_to_debates.rb +14 -0
  121. data/db/migrate/20200902133452_add_cached_comment_metadata_to_debates.rb +23 -0
  122. data/lib/decidim/debates/admin_engine.rb +3 -1
  123. data/lib/decidim/debates/component.rb +51 -1
  124. data/lib/decidim/debates/engine.rb +7 -1
  125. data/lib/decidim/debates/test/factories.rb +62 -6
  126. data/lib/decidim/debates/version.rb +1 -1
  127. metadata +84 -13
@@ -11,7 +11,7 @@ module Decidim
11
11
  include Decidim::Resourceable
12
12
  include Decidim::Followable
13
13
  include Decidim::Comments::Commentable
14
- include Decidim::ScopableComponent
14
+ include Decidim::ScopableResource
15
15
  include Decidim::Authorable
16
16
  include Decidim::Reportable
17
17
  include Decidim::HasReference
@@ -19,22 +19,44 @@ module Decidim
19
19
  include Decidim::Loggable
20
20
  include Decidim::DataPortability
21
21
  include Decidim::NewsletterParticipant
22
+ include Decidim::Searchable
23
+ include Decidim::TranslatableResource
24
+ include Decidim::TranslatableAttributes
25
+ include Decidim::Endorsable
26
+ include Decidim::Randomable
22
27
 
28
+ belongs_to :last_comment_by, polymorphic: true, foreign_key: "last_comment_by_id", foreign_type: "last_comment_by_type", optional: true
23
29
  component_manifest_name "debates"
24
30
 
25
31
  validates :title, presence: true
26
32
 
33
+ translatable_fields :title, :description, :instructions, :information_updates
34
+ searchable_fields({
35
+ participatory_space: { component: :participatory_space },
36
+ A: :title,
37
+ D: :description,
38
+ datetime: :start_time
39
+ },
40
+ index_on_create: ->(debate) { debate.visible? },
41
+ index_on_update: ->(debate) { debate.visible? })
42
+
43
+ scope :open, -> { where(closed_at: nil) }
44
+ scope :closed, -> { where.not(closed_at: nil) }
45
+ scope :authored_by, ->(author) { where(author: author) }
46
+ scope :commented_by, lambda { |author|
47
+ joins(:comments).where(
48
+ decidim_comments_comments:
49
+ {
50
+ decidim_author_id: author.id,
51
+ decidim_author_type: author.class.base_class.name
52
+ }
53
+ )
54
+ }
55
+
27
56
  def self.log_presenter_class_for(_log)
28
57
  Decidim::Debates::AdminLog::DebatePresenter
29
58
  end
30
59
 
31
- # Public: Checks whether the debate is official or not.
32
- #
33
- # Returns a boolean.
34
- def official?
35
- author.is_a?(Decidim::Organization)
36
- end
37
-
38
60
  # Public: Overrides the `reported_content_url` Reportable concern method.
39
61
  def reported_content_url
40
62
  ResourceLocatorPresenter.new(self).url
@@ -72,6 +94,7 @@ module Decidim
72
94
  # Public: Overrides the `accepts_new_comments?` Commentable concern method.
73
95
  def accepts_new_comments?
74
96
  return false unless open?
97
+ return false if closed?
75
98
 
76
99
  commentable? && !comments_blocked?
77
100
  end
@@ -114,6 +137,45 @@ module Decidim
114
137
  .pluck(:decidim_author_id).flatten.compact.uniq
115
138
  end
116
139
 
140
+ # Checks whether the user can edit the debate.
141
+ #
142
+ # user - the user to check for authorship
143
+ def editable_by?(user)
144
+ !closed? && authored_by?(user)
145
+ end
146
+
147
+ # Checks whether the debate is closed or not.
148
+ #
149
+ def closed?
150
+ closed_at.present? && conclusions.present?
151
+ end
152
+
153
+ # Checks whether the user can edit the debate.
154
+ #
155
+ # user - the user to check for authorship
156
+ def closeable_by?(user)
157
+ authored_by?(user)
158
+ end
159
+
160
+ # Public: Updates the comments counter cache. We have to do it these
161
+ # way in order to properly calculate the counter with hidden
162
+ # comments.
163
+ #
164
+ # rubocop:disable Rails/SkipsModelValidations
165
+ def update_comments_count
166
+ comments_count = comments.not_hidden.count
167
+ last_comment = comments.not_hidden.order("created_at DESC").first
168
+
169
+ update_columns(
170
+ last_comment_at: last_comment&.created_at,
171
+ last_comment_by_id: last_comment&.decidim_author_id,
172
+ last_comment_by_type: last_comment&.decidim_author_type,
173
+ comments_count: comments_count,
174
+ updated_at: Time.current
175
+ )
176
+ end
177
+ # rubocop:enable Rails/SkipsModelValidations
178
+
117
179
  private
118
180
 
119
181
  def comments_blocked?
@@ -12,7 +12,9 @@ module Decidim
12
12
  case permission_action.action
13
13
  when :create, :read
14
14
  allow!
15
- when :update, :delete
15
+ when :update
16
+ toggle_allow(debate && !debate.closed? && debate.official?)
17
+ when :delete, :close
16
18
  toggle_allow(debate && debate.official?)
17
19
  end
18
20
 
@@ -15,6 +15,12 @@ module Decidim
15
15
  toggle_allow(can_create_debate?)
16
16
  when :report
17
17
  allow!
18
+ when :edit
19
+ can_edit_debate?
20
+ when :endorse
21
+ can_endorse_debate?
22
+ when :close
23
+ can_close_debate?
18
24
  end
19
25
 
20
26
  permission_action
@@ -26,6 +32,28 @@ module Decidim
26
32
  authorized?(:create) &&
27
33
  current_settings&.creation_enabled? && component.participatory_space.can_participate?(user)
28
34
  end
35
+
36
+ def can_edit_debate?
37
+ return allow! if debate&.editable_by?(user)
38
+
39
+ disallow!
40
+ end
41
+
42
+ def can_close_debate?
43
+ return allow! if debate&.closeable_by?(user)
44
+
45
+ disallow!
46
+ end
47
+
48
+ def can_endorse_debate?
49
+ return disallow! if debate.closed?
50
+
51
+ allow!
52
+ end
53
+
54
+ def debate
55
+ @debate ||= context.fetch(:debate, nil) || context.fetch(:resource, nil)
56
+ end
29
57
  end
30
58
  end
31
59
  end
@@ -15,14 +15,18 @@ module Decidim
15
15
  class DebatePresenter < Decidim::Log::BasePresenter
16
16
  private
17
17
 
18
+ def resource_presenter
19
+ @resource_presenter ||= Decidim::Debates::Log::ResourcePresenter.new(action_log.resource, h, action_log.extra["resource"])
20
+ end
21
+
18
22
  def diff_fields_mapping
19
23
  {
20
- description: :i18n,
24
+ description: "Decidim::Debates::AdminLog::ValueTypes::DebateTitleDescriptionPresenter",
21
25
  end_date: :date,
22
26
  information_updates: :i18n,
23
27
  instructions: :i18n,
24
28
  start_date: :date,
25
- title: :i18n
29
+ title: "Decidim::Debates::AdminLog::ValueTypes::DebateTitleDescriptionPresenter"
26
30
  }
27
31
  end
28
32
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module AdminLog
6
+ module ValueTypes
7
+ # This class presents the given value as a Decidim::Debates::DebateTitleBody. Check
8
+ # the `DefaultPresenter` for more info on how value presenters work.
9
+ class DebateTitleDescriptionPresenter < Decidim::Log::ValueTypes::DefaultPresenter
10
+ def present
11
+ return unless value
12
+
13
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(h.translated_attribute(value))
14
+ renderer.render(links: false).html_safe
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -6,8 +6,11 @@ module Decidim
6
6
  # Decorator for debates
7
7
  #
8
8
  class DebatePresenter < SimpleDelegator
9
+ include Decidim::TranslationsHelper
10
+ include Decidim::ResourceHelper
9
11
  include Decidim::SanitizeHelper
10
12
  include Decidim::TranslatableAttributes
13
+ include ActionView::Helpers::DateHelper
11
14
 
12
15
  def debate
13
16
  __getobj__
@@ -23,9 +26,67 @@ module Decidim
23
26
  end
24
27
  end
25
28
 
26
- def title
27
- content = translated_attribute(debate.title)
28
- decidim_html_escape(content)
29
+ def title(links: false, all_locales: false)
30
+ return unless debate
31
+
32
+ handle_locales(debate.title, all_locales) do |content|
33
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(decidim_html_escape(content))
34
+ renderer.render(links: links).html_safe
35
+ end
36
+ end
37
+
38
+ def description(strip_tags: false, links: false, all_locales: false)
39
+ return unless debate
40
+
41
+ handle_locales(debate.description, all_locales) do |content|
42
+ content = strip_tags(content) if strip_tags
43
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(content)
44
+ content = renderer.render(links: links).html_safe
45
+ content = Decidim::ContentRenderers::LinkRenderer.new(content).render if links
46
+ content
47
+ end
48
+ end
49
+
50
+ def handle_locales(content, all_locales, &block)
51
+ if all_locales
52
+ content.each_with_object({}) do |(key, value), parsed_content|
53
+ parsed_content[key] = if key == "machine_translations"
54
+ handle_locales(value, all_locales, &block)
55
+ else
56
+ block.call(value)
57
+ end
58
+ end
59
+ else
60
+ yield(translated_attribute(content))
61
+ end
62
+ end
63
+
64
+ def last_comment_at
65
+ return unless debate.last_comment_at
66
+
67
+ time_ago_in_words(debate.last_comment_at)
68
+ end
69
+
70
+ def last_comment_by
71
+ debate.last_comment_by&.presenter
72
+ end
73
+
74
+ def participants_count
75
+ comments_authors.count do |author|
76
+ author.is_a?(Decidim::User)
77
+ end
78
+ end
79
+
80
+ def groups_count
81
+ comments_authors.count do |author|
82
+ author.is_a?(Decidim::UserGroup)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def comments_authors
89
+ @comments_authors ||= debate.comments.includes(:author, :user_group).map(&:normalized_author).uniq
29
90
  end
30
91
  end
31
92
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Log
6
+ class ResourcePresenter < Decidim::Log::ResourcePresenter
7
+ private
8
+
9
+ # Private: Presents resource name.
10
+ #
11
+ # Returns an HTML-safe String.
12
+ def present_resource_name
13
+ Decidim::Debates::DebatePresenter.new(resource).title
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -5,38 +5,10 @@ module Decidim
5
5
  #
6
6
  # A dummy presenter to abstract out the author of an official debate.
7
7
  #
8
- class OfficialAuthorPresenter
8
+ class OfficialAuthorPresenter < Decidim::OfficialAuthorPresenter
9
9
  def name
10
10
  I18n.t("decidim.debates.models.debate.fields.official_debate")
11
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
-
33
- def can_be_contacted?
34
- false
35
- end
36
-
37
- def has_tooltip?
38
- false
39
- end
40
12
  end
41
13
  end
42
14
  end
@@ -9,9 +9,6 @@ module Decidim
9
9
  end
10
10
 
11
11
  def save
12
- return @registry if @registry
13
-
14
- @registry = []
15
12
  cumulative.each do |key, cumulative_value|
16
13
  next if cumulative_value.zero?
17
14
 
@@ -21,10 +18,8 @@ module Decidim
21
18
  organization: @organization, decidim_category_id: category_id,
22
19
  participatory_space_type: space_type, participatory_space_id: space_id)
23
20
  record.assign_attributes(cumulative: cumulative_value, quantity: quantity_value)
24
- @registry << record
21
+ record.save!
25
22
  end
26
- @registry.each(&:save!)
27
- @registry
28
23
  end
29
24
 
30
25
  private
@@ -32,8 +27,7 @@ module Decidim
32
27
  def query
33
28
  return @query if @query
34
29
 
35
- components = Decidim::Component.where(participatory_space: retrieve_participatory_spaces).published
36
- @query = Decidim::Debates::Debate.where(component: components).joins(:component)
30
+ @query = Decidim::Debates::Debate.where(component: visible_component_ids_from_spaces(retrieve_participatory_spaces)).joins(:component)
37
31
  .left_outer_joins(:category)
38
32
  @query = @query.where("decidim_debates_debates.start_time <= ?", end_time)
39
33
  @query = @query.group("decidim_categorizations.decidim_category_id",
@@ -6,41 +6,31 @@ module Decidim
6
6
  # `current_component` param with a `Decidim::Component` in order to
7
7
  # find the debates.
8
8
  class DebateSearch < ResourceSearch
9
+ text_search_fields :title, :description
10
+
9
11
  # Public: Initializes the service.
10
12
  # component - A Decidim::Component to get the debates from.
11
13
  # page - The page number to paginate the results.
12
- # per_page - The number of proposals to return per page.
14
+ # per_page - The number of debates to return per page.
13
15
  def initialize(options = {})
14
16
  super(Debate.not_hidden, options)
15
17
  end
16
18
 
17
- # Handle the search_text filter. We have to cast the JSONB columns
18
- # into a `text` type so that we can search.
19
- def search_search_text
20
- query
21
- .where("decidim_debates_debates.title::text ILIKE ?", "%#{search_text}%")
22
- .or(query.where("decidim_debates_debates.description::text ILIKE ?", "%#{search_text}%"))
23
- end
24
-
25
- # Handle the origin filter
26
- def search_origin
27
- if origin == "official"
28
- query.where(author: component.organization)
29
- elsif origin == "citizens"
30
- query.where.not(decidim_author_type: "Decidim::Organization")
19
+ # Handle the activity filter
20
+ def search_activity
21
+ case activity
22
+ when "commented"
23
+ query.commented_by(user)
24
+ when "my_debates"
25
+ query.authored_by(user)
31
26
  else # Assume 'all'
32
27
  query
33
28
  end
34
29
  end
35
30
 
36
- def search_order_start_time
37
- if order_start_time == "asc"
38
- query.order("start_time ASC")
39
- elsif order_start_time == "desc"
40
- query.order("start_time DESC")
41
- else
42
- query.order("start_time ASC")
43
- end
31
+ # Handle the state filter
32
+ def search_state
33
+ apply_scopes(%w(open closed), state)
44
34
  end
45
35
  end
46
36
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ class DiffRenderer < BaseDiffRenderer
6
+ private
7
+
8
+ # Lists which attributes will be diffable and how they should be rendered.
9
+ def attribute_types
10
+ {
11
+ title: :i18n,
12
+ description: :i18n,
13
+ information_updates: :i18n,
14
+ instructions: :i18n,
15
+ start_time: :string,
16
+ end_time: :string,
17
+ conclusions: :i18n,
18
+ closed_at: :string
19
+ }
20
+ end
21
+
22
+ def debate
23
+ @debate ||= Debate.find_by(id: version.item_id)
24
+ end
25
+ end
26
+ end
27
+ end