decidim-proposals 0.28.3 → 0.29.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -18
  3. data/app/cells/decidim/proposals/cost_report_cell.rb +0 -3
  4. data/app/cells/decidim/proposals/highlighted_proposals_for_component_cell.rb +1 -1
  5. data/app/cells/decidim/proposals/participatory_text_proposal/buttons.erb +1 -1
  6. data/app/cells/decidim/proposals/participatory_text_proposal_cell.rb +2 -3
  7. data/app/cells/decidim/proposals/proposal_cell.rb +2 -0
  8. data/app/cells/decidim/proposals/proposal_g/show.erb +23 -0
  9. data/app/cells/decidim/proposals/proposal_g_cell.rb +48 -0
  10. data/app/cells/decidim/proposals/proposal_l_cell.rb +1 -3
  11. data/app/cells/decidim/proposals/proposal_metadata_cell.rb +23 -15
  12. data/app/commands/decidim/proposals/admin/answer_proposal.rb +2 -1
  13. data/app/commands/decidim/proposals/admin/assign_proposals_to_valuator.rb +7 -5
  14. data/app/commands/decidim/proposals/admin/create_proposal.rb +2 -2
  15. data/app/commands/decidim/proposals/admin/create_proposal_state.rb +15 -0
  16. data/app/commands/decidim/proposals/admin/destroy_proposal_state.rb +10 -0
  17. data/app/commands/decidim/proposals/admin/import_proposals.rb +10 -2
  18. data/app/commands/decidim/proposals/admin/notify_proposal_answer.rb +4 -21
  19. data/app/commands/decidim/proposals/admin/unassign_proposals_from_valuator.rb +6 -4
  20. data/app/commands/decidim/proposals/admin/update_proposal_state.rb +13 -0
  21. data/app/commands/decidim/proposals/create_proposal.rb +21 -2
  22. data/app/commands/decidim/proposals/update_proposal.rb +2 -2
  23. data/app/commands/decidim/proposals/vote_proposal.rb +1 -1
  24. data/app/commands/decidim/proposals/withdraw_proposal.rb +3 -7
  25. data/app/controllers/concerns/decidim/proposals/admin/filterable.rb +10 -22
  26. data/app/controllers/decidim/proposals/admin/proposal_states_controller.rb +86 -0
  27. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +4 -0
  28. data/app/controllers/decidim/proposals/admin/valuation_assignments_controller.rb +8 -11
  29. data/app/controllers/decidim/proposals/proposals_controller.rb +30 -35
  30. data/app/events/decidim/proposals/proposal_state_changed_event.rb +37 -0
  31. data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +5 -1
  32. data/app/forms/decidim/proposals/admin/proposal_state_form.rb +22 -0
  33. data/app/forms/decidim/proposals/admin/proposals_fork_form.rb +1 -1
  34. data/app/forms/decidim/proposals/admin/proposals_import_form.rb +1 -1
  35. data/app/forms/decidim/proposals/admin/valuation_assignment_form.rb +12 -14
  36. data/app/forms/decidim/proposals/proposal_form.rb +25 -4
  37. data/app/forms/decidim/proposals/reject_access_to_collaborative_draft_form.rb +1 -1
  38. data/app/forms/decidim/proposals/request_access_to_collaborative_draft_form.rb +1 -1
  39. data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +7 -17
  40. data/app/helpers/decidim/proposals/admin/proposals_helper.rb +13 -89
  41. data/app/helpers/decidim/proposals/application_helper.rb +16 -10
  42. data/app/helpers/decidim/proposals/proposal_cells_helper.rb +6 -2
  43. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +3 -3
  44. data/app/helpers/decidim/proposals/proposal_wizard_helper.rb +5 -8
  45. data/app/helpers/decidim/proposals/proposals_helper.rb +18 -24
  46. data/app/models/decidim/proposals/proposal.rb +78 -28
  47. data/app/models/decidim/proposals/proposal_state.rb +58 -0
  48. data/app/packs/documents/decidim/proposals/participatory_texts/participatory_text.md +1 -3
  49. data/app/packs/images/decidim/proposals/proposal-placeholder-card-g.svg +15 -0
  50. data/app/packs/src/decidim/proposals/add_proposal.js +2 -0
  51. data/app/packs/src/decidim/proposals/admin/proposals.js +43 -8
  52. data/app/packs/stylesheets/decidim/proposals/proposals.scss +39 -1
  53. data/app/permissions/decidim/proposals/admin/permissions.rb +16 -4
  54. data/app/presenters/decidim/proposals/admin_log/proposal_state_presenter.rb +21 -0
  55. data/app/presenters/decidim/proposals/proposal_presenter.rb +12 -3
  56. data/app/queries/decidim/proposals/metrics/endorsements_metric_manage.rb +1 -1
  57. data/app/queries/decidim/proposals/metrics/proposal_followers_metric_measure.rb +1 -1
  58. data/app/queries/decidim/proposals/metrics/proposal_participants_metric_measure.rb +4 -4
  59. data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +1 -1
  60. data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +1 -1
  61. data/app/services/decidim/proposals/diff_renderer.rb +1 -1
  62. data/app/views/decidim/proposals/admin/imports/_proposals_fields.html.erb +1 -1
  63. data/app/views/decidim/proposals/admin/participatory_texts/index.html.erb +3 -2
  64. data/app/views/decidim/proposals/admin/proposal_answers/_form.html.erb +2 -2
  65. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +3 -3
  66. data/app/views/decidim/proposals/admin/proposal_states/_form.html.erb +67 -0
  67. data/app/views/decidim/proposals/admin/proposal_states/edit.html.erb +18 -0
  68. data/app/views/decidim/proposals/admin/proposal_states/index.html.erb +50 -0
  69. data/app/views/decidim/proposals/admin/proposal_states/new.html.erb +18 -0
  70. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +6 -12
  71. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +6 -6
  72. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +2 -2
  73. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_assign_to_valuator.html.erb +11 -7
  74. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +7 -5
  75. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_merge.html.erb +2 -2
  76. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_publish_answers.html.erb +2 -2
  77. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +2 -2
  78. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_scope-change.html.erb +2 -2
  79. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_split.html.erb +2 -2
  80. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_unassign_from_valuator.html.erb +11 -7
  81. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_valuators_picker.html.erb +12 -0
  82. data/app/views/decidim/proposals/admin/proposals/index.html.erb +9 -5
  83. data/app/views/decidim/proposals/admin/proposals/publish_answers.js.erb +1 -1
  84. data/app/views/decidim/proposals/admin/proposals/show.html.erb +3 -2
  85. data/app/views/decidim/proposals/admin/proposals/update_attribute.js.erb +3 -3
  86. data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +12 -5
  87. data/app/views/decidim/proposals/proposals/_proposal.html.erb +1 -1
  88. data/app/views/decidim/proposals/proposals/_proposal_aside.html.erb +1 -1
  89. data/app/views/decidim/proposals/proposals/_proposals.html.erb +9 -3
  90. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +1 -1
  91. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +3 -3
  92. data/app/views/decidim/proposals/proposals/_wizard_header.html.erb +0 -1
  93. data/app/views/decidim/proposals/proposals/new.html.erb +2 -7
  94. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +4 -4
  95. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_votes_count.html.erb +8 -8
  96. data/app/views/decidim/proposals/proposals/preview.html.erb +1 -1
  97. data/app/views/decidim/proposals/proposals/show.html.erb +1 -1
  98. data/config/locales/ar.yml +6 -114
  99. data/config/locales/bg.yml +110 -106
  100. data/config/locales/ca.yml +81 -78
  101. data/config/locales/cs.yml +60 -122
  102. data/config/locales/de.yml +122 -119
  103. data/config/locales/el.yml +3 -118
  104. data/config/locales/en.yml +110 -107
  105. data/config/locales/es-MX.yml +82 -79
  106. data/config/locales/es-PY.yml +85 -82
  107. data/config/locales/es.yml +81 -78
  108. data/config/locales/eu.yml +103 -107
  109. data/config/locales/fi-plain.yml +87 -84
  110. data/config/locales/fi.yml +118 -115
  111. data/config/locales/fr-CA.yml +86 -83
  112. data/config/locales/fr.yml +78 -75
  113. data/config/locales/ga-IE.yml +1 -27
  114. data/config/locales/gl.yml +5 -104
  115. data/config/locales/he-IL.yml +0 -13
  116. data/config/locales/hu.yml +15 -88
  117. data/config/locales/id-ID.yml +2 -97
  118. data/config/locales/is-IS.yml +1 -33
  119. data/config/locales/it.yml +6 -96
  120. data/config/locales/ja.yml +110 -107
  121. data/config/locales/lb.yml +1 -0
  122. data/config/locales/lt.yml +3 -122
  123. data/config/locales/lv.yml +2 -96
  124. data/config/locales/nl.yml +6 -95
  125. data/config/locales/no.yml +3 -107
  126. data/config/locales/pl.yml +106 -113
  127. data/config/locales/pt-BR.yml +5 -81
  128. data/config/locales/pt.yml +5 -107
  129. data/config/locales/ro-RO.yml +6 -110
  130. data/config/locales/ru.yml +2 -53
  131. data/config/locales/sk.yml +2 -103
  132. data/config/locales/sv.yml +66 -206
  133. data/config/locales/tr-TR.yml +57 -103
  134. data/config/locales/uk.yml +3 -54
  135. data/config/locales/zh-CN.yml +2 -99
  136. data/config/locales/zh-TW.yml +3 -116
  137. data/db/migrate/20240110203500_add_withdrawn_at_field_to_proposals.rb +27 -0
  138. data/db/migrate/20240110203501_create_decidim_proposals_proposal_state.rb +14 -0
  139. data/db/migrate/20240110203502_add_state_id_to_decidim_proposals_proposals.rb +13 -0
  140. data/db/migrate/20240110203503_remove_state_from_decidim_proposals_proposals.rb +11 -0
  141. data/db/migrate/20240110203504_create_default_proposal_states.rb +31 -0
  142. data/db/migrate/20240209092404_change_color_fields_on_proposals_states.rb +54 -0
  143. data/decidim-proposals.gemspec +2 -2
  144. data/lib/decidim/api/proposal_type.rb +4 -0
  145. data/lib/decidim/proposals/admin_engine.rb +8 -0
  146. data/lib/decidim/proposals/admin_filter.rb +37 -0
  147. data/lib/decidim/proposals/component.rb +8 -5
  148. data/lib/decidim/proposals/engine.rb +1 -15
  149. data/lib/decidim/proposals/import/proposal_answer_creator.rb +6 -6
  150. data/lib/decidim/proposals/import/proposal_creator.rb +1 -1
  151. data/lib/decidim/proposals/markdown_to_proposals.rb +2 -8
  152. data/lib/decidim/proposals/proposal_serializer.rb +5 -3
  153. data/lib/decidim/proposals/seeds.rb +60 -51
  154. data/lib/decidim/proposals/test/factories.rb +64 -8
  155. data/lib/decidim/proposals/version.rb +1 -1
  156. data/lib/decidim/proposals.rb +84 -12
  157. data/lib/tasks/proposals/upgrade/decidim_proposals_upgrade_tasks.rake +32 -0
  158. metadata +50 -36
  159. data/app/events/decidim/proposals/accepted_proposal_event.rb +0 -17
  160. data/app/events/decidim/proposals/evaluating_proposal_event.rb +0 -11
  161. data/app/events/decidim/proposals/rejected_proposal_event.rb +0 -17
  162. data/app/forms/decidim/proposals/proposal_wizard_create_step_form.rb +0 -44
  163. data/app/queries/decidim/proposals/similar_proposals.rb +0 -67
  164. data/app/views/decidim/proposals/proposals/_endorsements_card_row.html.erb +0 -0
  165. data/app/views/decidim/proposals/proposals/_proposal_badge.html.erb +0 -3
  166. data/app/views/decidim/proposals/proposals/compare.html.erb +0 -24
  167. data/app/views/decidim/proposals/proposals/complete.html.erb +0 -31
  168. data/lib/tasks/proposals/upgrade/decdim_proposal_upgrade_tasks.rake +0 -34
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChangeColorFieldsOnProposalsStates < ActiveRecord::Migration[6.1]
4
+ class ProposalState < ApplicationRecord
5
+ self.table_name = :decidim_proposals_proposal_states
6
+
7
+ def self.colors
8
+ {
9
+ gray: {
10
+ background: "#F6F8FA",
11
+ foreground: "#4B5058"
12
+ },
13
+ green: {
14
+ background: "#E3FCE9",
15
+ foreground: "#15602C"
16
+ },
17
+ orange: {
18
+ background: "#FFF1E5",
19
+ foreground: "#BC4C00"
20
+ },
21
+ red: {
22
+ background: "#FFEBE9",
23
+ foreground: "#D1242F"
24
+ }
25
+ }
26
+ end
27
+ end
28
+
29
+ def up
30
+ colors = ProposalState.colors
31
+
32
+ add_column :decidim_proposals_proposal_states, :bg_color, :string, default: colors[:gray][:background], null: false
33
+ add_column :decidim_proposals_proposal_states, :text_color, :string, default: colors[:gray][:foreground], null: false
34
+ remove_column :decidim_proposals_proposal_states, :css_class
35
+
36
+ # rubocop:disable Rails/SkipsModelValidations
37
+ ProposalState.where(token: :accepted).update_all(
38
+ bg_color: colors[:green][:background], text_color: colors[:green][:foreground]
39
+ )
40
+ ProposalState.where(token: :evaluating).update_all(
41
+ bg_color: colors[:orange][:background], text_color: colors[:orange][:foreground]
42
+ )
43
+ ProposalState.where(token: :rejected).update_all(
44
+ bg_color: colors[:red][:background], text_color: colors[:red][:foreground]
45
+ )
46
+ # rubocop:enable Rails/SkipsModelValidations
47
+ end
48
+
49
+ def down
50
+ remove_column :decidim_proposals_proposal_states, :bg_color
51
+ remove_column :decidim_proposals_proposal_states, :text_color
52
+ add_column :decidim_proposals_proposal_states, :css_class, :string
53
+ end
54
+ end
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  "homepage_uri" => "https://decidim.org",
20
20
  "source_code_uri" => "https://github.com/decidim/decidim"
21
21
  }
22
- s.required_ruby_version = "~> 3.1.0"
22
+ s.required_ruby_version = "~> 3.2.0"
23
23
 
24
24
  s.name = "decidim-proposals"
25
25
  s.summary = "Decidim proposals module"
@@ -34,7 +34,7 @@ Gem::Specification.new do |s|
34
34
 
35
35
  s.add_dependency "decidim-comments", Decidim::Proposals.version
36
36
  s.add_dependency "decidim-core", Decidim::Proposals.version
37
- s.add_dependency "doc2text", "~> 0.4.6"
37
+ s.add_dependency "doc2text", "~> 0.4.7"
38
38
  s.add_dependency "redcarpet", "~> 3.5", ">= 3.5.1"
39
39
 
40
40
  s.add_development_dependency "decidim-admin", Decidim::Proposals.version
@@ -26,6 +26,7 @@ module Decidim
26
26
  def coordinates
27
27
  [object.latitude, object.longitude]
28
28
  end
29
+
29
30
  field :reference, GraphQL::Types::String, "This proposal's unique reference", null: true
30
31
  field :state, GraphQL::Types::String, "The answer status in which proposal is in", null: true
31
32
  field :answer, Decidim::Core::TranslatedFieldType, "The answer feedback for the status for this proposal", null: true
@@ -41,6 +42,9 @@ module Decidim
41
42
  field :created_in_meeting, GraphQL::Types::Boolean, "Whether this proposal comes from a meeting or not", method: :official_meeting?, null: true
42
43
  field :meeting, Decidim::Meetings::MeetingType, description: "If the proposal comes from a meeting, the related meeting", null: true
43
44
 
45
+ field :withdrawn_at, Decidim::Core::DateTimeType, description: "The date and time this proposal was withdrawn", null: true
46
+ field :withdrawn, GraphQL::Types::Boolean, "Whether this proposal has been withdrawn or not", method: :withdrawn?, null: true
47
+
44
48
  def meeting
45
49
  object.authors.first if object.official_meeting?
46
50
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "decidim/proposals/admin_filter"
4
+
3
5
  module Decidim
4
6
  module Proposals
5
7
  # This is the engine that runs on the public interface of `decidim-proposals`.
@@ -25,6 +27,8 @@ module Decidim
25
27
  resources :proposal_notes, only: [:create]
26
28
  end
27
29
 
30
+ resources :proposal_states
31
+
28
32
  resources :participatory_texts, only: [:index] do
29
33
  collection do
30
34
  get :new_import
@@ -38,6 +42,10 @@ module Decidim
38
42
  root to: "proposals#index"
39
43
  end
40
44
 
45
+ initializer "decidim_proposals.admin_filters" do
46
+ Decidim::Proposals::AdminFilter.register_filter!
47
+ end
48
+
41
49
  def load_seed
42
50
  nil
43
51
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ class AdminFilter
6
+ def self.register_filter!
7
+ Decidim.admin_filter(:proposals) do |configuration|
8
+ configuration.add_filters(
9
+ :is_emendation_true,
10
+ :state_eq,
11
+ :with_any_state,
12
+ :scope_id_eq,
13
+ :category_id_eq,
14
+ :valuator_role_ids_has
15
+ )
16
+
17
+ configuration.add_filters_with_values(
18
+ is_emendation_true: %w(true false),
19
+ state_eq: state_eq_values,
20
+ with_any_state: %w(state_published state_not_published),
21
+ scope_id_eq: scope_ids_hash(scopes.top_level),
22
+ category_id_eq: category_ids_hash(categories.first_class),
23
+ valuator_role_ids_has: valuator_role_ids
24
+ )
25
+
26
+ configuration.add_dynamically_translated_filters(
27
+ :scope_id_eq,
28
+ :category_id_eq,
29
+ :valuator_role_ids_has,
30
+ :proposal_state_id_eq,
31
+ :state_eq
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -11,6 +11,11 @@ Decidim.register_component(:proposals) do |component|
11
11
  raise "Cannot destroy this component when there are proposals" if Decidim::Proposals::Proposal.where(component: instance).any?
12
12
  end
13
13
 
14
+ component.on(:create) do |instance|
15
+ admin_user = GlobalID::Locator.locate(instance.versions.first.whodunnit)
16
+ Decidim::Proposals.create_default_states!(instance, admin_user)
17
+ end
18
+
14
19
  component.data_portable_entities = ["Decidim::Proposals::Proposal"]
15
20
 
16
21
  component.newsletter_participant_entities = ["Decidim::Proposals::Proposal"]
@@ -33,7 +38,7 @@ Decidim.register_component(:proposals) do |component|
33
38
  settings.attribute :proposal_edit_time, type: :enum, default: "limited", choices: -> { %w(limited infinite) }
34
39
  settings.attribute :proposal_edit_before_minutes, type: :integer, default: 5, required: true
35
40
  settings.attribute :threshold_per_proposal, type: :integer, default: 0, required: true
36
- settings.attribute :can_accumulate_supports_beyond_threshold, type: :boolean, default: false
41
+ settings.attribute :can_accumulate_votes_beyond_threshold, type: :boolean, default: false
37
42
  settings.attribute :proposal_answering_enabled, type: :boolean, default: true
38
43
  settings.attribute :default_sort_order, type: :select, default: "automatic", choices: -> { POSSIBLE_SORT_ORDERS }
39
44
  settings.attribute :official_proposals_enabled, type: :boolean, default: true
@@ -56,8 +61,6 @@ Decidim.register_component(:proposals) do |component|
56
61
  settings.attribute :new_proposal_help_text, type: :text, translated: true, editor: true
57
62
  settings.attribute :proposal_wizard_step_1_help_text, type: :text, translated: true, editor: true
58
63
  settings.attribute :proposal_wizard_step_2_help_text, type: :text, translated: true, editor: true
59
- settings.attribute :proposal_wizard_step_3_help_text, type: :text, translated: true, editor: true
60
- settings.attribute :proposal_wizard_step_4_help_text, type: :text, translated: true, editor: true
61
64
  end
62
65
 
63
66
  component.settings(:step) do |settings|
@@ -99,14 +102,14 @@ Decidim.register_component(:proposals) do |component|
99
102
  end
100
103
 
101
104
  component.register_stat :proposals_count, primary: true, priority: Decidim::StatsRegistry::HIGH_PRIORITY do |components, start_at, end_at|
102
- Decidim::Proposals::FilteredProposals.for(components, start_at, end_at).published.except_withdrawn.not_hidden.count
105
+ Decidim::Proposals::FilteredProposals.for(components, start_at, end_at).published.not_withdrawn.not_hidden.count
103
106
  end
104
107
 
105
108
  component.register_stat :proposals_accepted, primary: true, priority: Decidim::StatsRegistry::HIGH_PRIORITY do |components, start_at, end_at|
106
109
  Decidim::Proposals::FilteredProposals.for(components, start_at, end_at).accepted.not_hidden.count
107
110
  end
108
111
 
109
- component.register_stat :supports_count, priority: Decidim::StatsRegistry::HIGH_PRIORITY do |components, start_at, end_at|
112
+ component.register_stat :votes_count, priority: Decidim::StatsRegistry::HIGH_PRIORITY do |components, start_at, end_at|
110
113
  proposals = Decidim::Proposals::FilteredProposals.for(components, start_at, end_at).published.not_hidden
111
114
  Decidim::Proposals::ProposalVote.where(proposal: proposals).count
112
115
  end
@@ -13,8 +13,6 @@ module Decidim
13
13
  routes do
14
14
  resources :proposals, except: [:destroy] do
15
15
  member do
16
- get :compare
17
- get :complete
18
16
  get :edit_draft
19
17
  patch :update_draft
20
18
  get :preview
@@ -57,6 +55,7 @@ module Decidim
57
55
  Decidim.icons.register(name: "arrow-right-s-fill", icon: "arrow-right-s-fill", category: "system", description: "", engine: :proposals)
58
56
  Decidim.icons.register(name: "bar-chart-2-line", icon: "bar-chart-2-line", category: "system", description: "", engine: :proposals)
59
57
  Decidim.icons.register(name: "scales-line", icon: "scales-line", category: "system", description: "", engine: :proposals)
58
+ Decidim.icons.register(name: "layout-grid-fill", icon: "layout-grid-fill", category: "system", description: "", engine: :proposals)
60
59
  end
61
60
 
62
61
  initializer "decidim_proposals.content_processors" do |_app|
@@ -92,19 +91,6 @@ module Decidim
92
91
  end
93
92
  end
94
93
 
95
- # Subscribes to ActiveSupport::Notifications that may affect a Proposal.
96
- initializer "decidim_proposals.subscribe_to_events" do
97
- # when a proposal is linked from a result
98
- event_name = "decidim.resourceable.included_proposals.created"
99
- ActiveSupport::Notifications.subscribe event_name do |_name, _started, _finished, _unique_id, data|
100
- payload = data[:this]
101
- if payload[:from_type] == Decidim::Accountability::Result.name && payload[:to_type] == Proposal.name
102
- proposal = Proposal.find(payload[:to_id])
103
- proposal.update(state: "accepted", state_published_at: Time.current)
104
- end
105
- end
106
- end
107
-
108
94
  initializer "decidim_proposals.add_cells_view_paths" do
109
95
  Cell::ViewModel.view_paths << File.expand_path("#{Decidim::Proposals::Engine.root}/app/cells")
110
96
  Cell::ViewModel.view_paths << File.expand_path("#{Decidim::Proposals::Engine.root}/app/views") # for proposal partials
@@ -6,9 +6,7 @@ module Decidim
6
6
  # This class is responsible for creating the imported proposal answers
7
7
  # and must be included in proposals component's import manifest.
8
8
  class ProposalAnswerCreator < Decidim::Admin::Import::Creator
9
- POSSIBLE_ANSWER_STATES = %w(evaluating accepted rejected).freeze
10
-
11
- # Retuns the resource class to be created with the provided data.
9
+ # Returns the resource class to be created with the provided data.
12
10
  def self.resource_klass
13
11
  Decidim::Proposals::Proposal
14
12
  end
@@ -58,10 +56,12 @@ module Decidim
58
56
 
59
57
  proposal.answer = answer
60
58
  proposal.answered_at = Time.current
61
- @initial_state = proposal.state
59
+ @initial_state = proposal.proposal_state
60
+
61
+ proposal_state = Decidim::Proposals::ProposalState.where(component:, token: state).first
62
62
 
63
- if POSSIBLE_ANSWER_STATES.include?(state)
64
- proposal.state = state
63
+ if proposal_state.present?
64
+ proposal.proposal_state = proposal_state
65
65
  proposal.state_published_at = Time.current if component.current_settings.publish_answers_immediately?
66
66
  else
67
67
  proposal.errors.add(:state, :invalid)
@@ -6,7 +6,7 @@ module Decidim
6
6
  # This class is responsible for creating the imported proposals
7
7
  # and must be included in proposals component's import manifest.
8
8
  class ProposalCreator < Decidim::Admin::Import::Creator
9
- # Retuns the resource class to be created with the provided data.
9
+ # Returns the resource class to be created with the provided data.
10
10
  def self.resource_klass
11
11
  Decidim::Proposals::Proposal
12
12
  end
@@ -41,14 +41,8 @@ module Decidim
41
41
 
42
42
  # Block-level calls ######################
43
43
 
44
- # Recarpet callback to preprocess the document.
45
- # Removes the HTML comment from the markdown file
46
- def preprocess(document)
47
- document.gsub(/<!--.*-->/, "")
48
- end
49
-
50
44
  # Recarpet callback to process headers.
51
- # Creates Paricipatory Text Proposals at Section and Subsection levels.
45
+ # Creates Participatory Text Proposals at Section and Subsection levels.
52
46
  def header(title, level)
53
47
  participatory_text_level = if level > 1
54
48
  Decidim::Proposals::ParticipatoryTextSection::LEVELS[:sub_section]
@@ -63,7 +57,7 @@ module Decidim
63
57
  end
64
58
 
65
59
  # Recarpet callback to process paragraphs.
66
- # Creates Paricipatory Text Proposals at Article level.
60
+ # Creates Participatory Text Proposals at Article level.
67
61
  def paragraph(text)
68
62
  return if text.blank?
69
63
 
@@ -44,13 +44,13 @@ module Decidim
44
44
  reference: proposal.reference,
45
45
  answer: ensure_translatable(proposal.answer),
46
46
  answered_at: proposal.answered_at,
47
- supports: proposal.proposal_votes_count,
47
+ votes: proposal.proposal_votes_count,
48
48
  endorsements: {
49
49
  total_count: proposal.endorsements.size,
50
50
  user_endorsements:
51
51
  },
52
52
  comments: proposal.comments_count,
53
- attachments: proposal.attachments.count,
53
+ attachments: proposal.attachments.size,
54
54
  followers: proposal.follows.size,
55
55
  published_at: proposal.published_at,
56
56
  url:,
@@ -60,7 +60,9 @@ module Decidim
60
60
  original_proposal: {
61
61
  title: proposal&.amendable&.title,
62
62
  url: original_proposal_url
63
- }
63
+ },
64
+ withdrawn: proposal.withdrawn?,
65
+ withdrawn_at: proposal.withdrawn_at
64
66
  }
65
67
  end
66
68
 
@@ -15,6 +15,8 @@ module Decidim
15
15
  def call
16
16
  component = create_component!
17
17
 
18
+ Decidim::Proposals.create_default_states!(component, admin_user)
19
+
18
20
  5.times do |n|
19
21
  proposal = create_proposal!(component:)
20
22
 
@@ -34,6 +36,9 @@ module Decidim
34
36
  end
35
37
 
36
38
  update_traceability!(component:)
39
+
40
+ create_report!(reportable: Decidim::Proposals::Proposal.take, current_user: Decidim::User.take)
41
+ hide_report!(reportable: Decidim::Proposals::Proposal.take)
37
42
  end
38
43
 
39
44
  def organization
@@ -54,6 +59,7 @@ module Decidim
54
59
  participatory_space:,
55
60
  settings: {
56
61
  vote_limit: 0,
62
+ attachments_allowed: [true, false].sample,
57
63
  collaborative_drafts_enabled: true
58
64
  },
59
65
  step_settings:
@@ -69,20 +75,9 @@ module Decidim
69
75
  end
70
76
  end
71
77
 
72
- # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
73
78
  def create_proposal!(component:)
74
- n = rand(5)
75
- state, answer, state_published_at = if n > 3
76
- ["accepted", Decidim::Faker::Localized.sentence(word_count: 10), Time.current]
77
- elsif n > 2
78
- ["rejected", nil, Time.current]
79
- elsif n > 1
80
- ["evaluating", nil, Time.current]
81
- elsif n.positive?
82
- ["accepted", Decidim::Faker::Localized.sentence(word_count: 10), nil]
83
- else
84
- ["not_answered", nil, nil]
85
- end
79
+ proposal_state, answer, state_published_at = random_state_answer
80
+ proposal_state = Decidim::Proposals::ProposalState.where(component:, token: proposal_state).first
86
81
 
87
82
  params = {
88
83
  component:,
@@ -90,9 +85,9 @@ module Decidim
90
85
  scope: random_scope(participatory_space:),
91
86
  title: { en: ::Faker::Lorem.sentence(word_count: 2) },
92
87
  body: { en: ::Faker::Lorem.paragraphs(number: 2).join("\n") },
93
- state:,
88
+ proposal_state:,
94
89
  answer:,
95
- answered_at: state.present? ? Time.current : nil,
90
+ answered_at: proposal_state.present? ? Time.current : nil,
96
91
  state_published_at:,
97
92
  published_at: Time.current
98
93
  }
@@ -104,26 +99,58 @@ module Decidim
104
99
  visibility: "all"
105
100
  ) do
106
101
  proposal = Decidim::Proposals::Proposal.new(params)
107
- n = 3 if n == 2 && !Decidim.module_installed?(:meetings)
108
-
109
- coauthor = case n
110
- when 0
111
- Decidim::User.where(organization:).sample
112
- when 1
113
- Decidim::UserGroup.where(organization:).sample
114
- when 2
115
- meeting_component = participatory_space.components.find_by(manifest_name: "meetings")
116
-
117
- Decidim::Meetings::Meeting.where(component: meeting_component).sample
118
- else
119
- organization
120
- end
102
+ coauthor = random_coauthor
121
103
  proposal.add_coauthor(coauthor)
122
104
  proposal.save!
105
+
106
+ Decidim::EventsManager.publish(
107
+ event: "decidim.events.proposals.proposal_published_for_space",
108
+ event_class: Decidim::Proposals::PublishProposalEvent,
109
+ resource: proposal,
110
+ followers: proposal.participatory_space.followers
111
+ )
112
+
123
113
  proposal
124
114
  end
115
+
116
+ create_attachment(attached_to: proposal, filename: "city.jpeg") if component.settings.attachments_allowed?
117
+
118
+ proposal
119
+ end
120
+
121
+ def random_state_answer
122
+ n = rand(5)
123
+
124
+ if n > 3
125
+ [:accepted, Decidim::Faker::Localized.sentence(word_count: 10), Time.current]
126
+ elsif n > 2
127
+ [:rejected, nil, Time.current]
128
+ elsif n > 1
129
+ [:evaluating, nil, Time.current]
130
+ elsif n.positive?
131
+ [:accepted, Decidim::Faker::Localized.sentence(word_count: 10), nil]
132
+ else
133
+ [:not_answered, nil, nil]
134
+ end
135
+ end
136
+
137
+ def random_coauthor
138
+ n = rand(5)
139
+ n = 3 if n == 2 && !Decidim.module_installed?(:meetings)
140
+
141
+ case n
142
+ when 0
143
+ Decidim::User.where(organization:).sample
144
+ when 1
145
+ Decidim::UserGroup.where(organization:).sample
146
+ when 2
147
+ meeting_component = participatory_space.components.find_by(manifest_name: "meetings")
148
+
149
+ Decidim::Meetings::Meeting.where(component: meeting_component).sample
150
+ else
151
+ organization
152
+ end
125
153
  end
126
- # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
127
154
 
128
155
  def random_nickname
129
156
  "#{::Faker::Twitter.unique.screen_name}-#{SecureRandom.hex(4)}"[0, 20]
@@ -136,15 +163,7 @@ module Decidim
136
163
  end
137
164
 
138
165
  def create_emendation!(proposal:)
139
- author = Decidim::User.find_or_initialize_by(email: random_email(suffix: "amendment"))
140
- author.update!(
141
- password: "decidim123456789",
142
- name: "#{::Faker::Name.name} #{participatory_space.id}",
143
- nickname: random_nickname,
144
- organization:,
145
- tos_agreement: "1",
146
- confirmed_at: Time.current
147
- )
166
+ author = find_or_initialize_user_by(email: random_email(suffix: "amendment"))
148
167
 
149
168
  group = Decidim::UserGroup.create!(
150
169
  name: ::Faker::Name.name,
@@ -171,7 +190,7 @@ module Decidim
171
190
  scope: random_scope(participatory_space:),
172
191
  title: { en: "#{proposal.title["en"]} #{::Faker::Lorem.sentence(word_count: 1)}" },
173
192
  body: { en: "#{proposal.body["en"]} #{::Faker::Lorem.sentence(word_count: 3)}" },
174
- state: "evaluating",
193
+ proposal_state: Decidim::Proposals::ProposalState.where(component: proposal.component, token: :evaluating).first,
175
194
  answer: nil,
176
195
  answered_at: Time.current,
177
196
  published_at: Time.current
@@ -200,17 +219,7 @@ module Decidim
200
219
  end
201
220
 
202
221
  def create_proposal_votes!(proposal:, emendation: nil)
203
- author = Decidim::User.find_or_initialize_by(email: random_email(suffix: "vote"))
204
- author.update!(
205
- password: "decidim123456789",
206
- name: "#{::Faker::Name.name} #{participatory_space.id}",
207
- nickname: random_nickname,
208
- organization:,
209
- tos_agreement: "1",
210
- confirmed_at: Time.current,
211
- personal_url: ::Faker::Internet.url,
212
- about: ::Faker::Lorem.paragraph(sentence_count: 2)
213
- )
222
+ author = find_or_initialize_user_by(email: random_email(suffix: "vote"))
214
223
 
215
224
  Decidim::Proposals::ProposalVote.create!(proposal:, author:) unless proposal.published_state? && proposal.rejected?
216
225
  Decidim::Proposals::ProposalVote.create!(proposal: emendation, author:) if emendation
@@ -5,6 +5,17 @@ require "decidim/core/test/factories"
5
5
  require "decidim/participatory_processes/test/factories"
6
6
  require "decidim/meetings/test/factories"
7
7
 
8
+ def generate_state_title(token, skip_injection: false)
9
+ value = I18n.t(token, scope: "decidim.proposals.answers")
10
+ Decidim::Faker::Localized.localized do
11
+ if skip_injection
12
+ value
13
+ else
14
+ "<script>alert(\"proposal_state_title\");</script> #{value}"
15
+ end
16
+ end
17
+ end
18
+
8
19
  FactoryBot.define do
9
20
  factory :proposal_component, parent: :component do
10
21
  transient do
@@ -14,6 +25,10 @@ FactoryBot.define do
14
25
  manifest_name { :proposals }
15
26
  participatory_space { create(:participatory_process, :with_steps, organization:, skip_injection:) }
16
27
 
28
+ after :create do |proposal_component|
29
+ Decidim::Proposals.create_default_states!(proposal_component, nil, with_traceability: false)
30
+ end
31
+
17
32
  trait :with_endorsements_enabled do
18
33
  step_settings do
19
34
  {
@@ -148,10 +163,10 @@ FactoryBot.define do
148
163
  end
149
164
  end
150
165
 
151
- trait :with_can_accumulate_supports_beyond_threshold do
166
+ trait :with_can_accumulate_votes_beyond_threshold do
152
167
  settings do
153
168
  {
154
- can_accumulate_supports_beyond_threshold: true
169
+ can_accumulate_votes_beyond_threshold: true
155
170
  }
156
171
  end
157
172
  end
@@ -246,12 +261,45 @@ FactoryBot.define do
246
261
  end
247
262
  end
248
263
 
264
+ factory :proposal_state, class: "Decidim::Proposals::ProposalState" do
265
+ transient do
266
+ skip_injection { false }
267
+ end
268
+ token { :not_answered }
269
+ title { generate_state_title(:not_answered, skip_injection:) }
270
+ announcement_title { generate_localized_title(:announcement_title, skip_injection:) }
271
+ component { build(:proposal_component) }
272
+ bg_color { Faker::Color.hex_color(:light) }
273
+ text_color { Faker::Color.hex_color(:dark) }
274
+
275
+ trait :evaluating do
276
+ title { generate_state_title(:evaluating, skip_injection:) }
277
+ token { :evaluating }
278
+ end
279
+
280
+ trait :accepted do
281
+ title { generate_state_title(:accepted, skip_injection:) }
282
+ token { :accepted }
283
+ end
284
+
285
+ trait :rejected do
286
+ title { generate_state_title(:rejected, skip_injection:) }
287
+ token { :rejected }
288
+ end
289
+
290
+ trait :withdrawn do
291
+ title { generate_state_title(:withdrawn, skip_injection:) }
292
+ token { :withdrawn }
293
+ end
294
+ end
295
+
249
296
  factory :proposal, class: "Decidim::Proposals::Proposal" do
250
297
  transient do
251
298
  users { nil }
252
299
  # user_groups correspondence to users is by sorting order
253
300
  user_groups { [] }
254
301
  skip_injection { false }
302
+ state { :not_answered }
255
303
  end
256
304
 
257
305
  title { generate_localized_title(:proposal_title, skip_injection:) }
@@ -266,6 +314,14 @@ FactoryBot.define do
266
314
  execution_period { generate_localized_title(:proposal_execution_period, skip_injection:) }
267
315
 
268
316
  after(:build) do |proposal, evaluator|
317
+ if proposal.component
318
+ existing_states = Decidim::Proposals::ProposalState.where(component: proposal.component)
319
+
320
+ Decidim::Proposals.create_default_states!(proposal.component, nil, with_traceability: false) unless existing_states.any?
321
+ end
322
+
323
+ proposal.assign_state(evaluator.state)
324
+
269
325
  proposal.title = if evaluator.title.is_a?(String)
270
326
  { proposal.try(:organization).try(:default_locale) || "en" => evaluator.title }
271
327
  else
@@ -330,36 +386,36 @@ FactoryBot.define do
330
386
  end
331
387
 
332
388
  trait :evaluating do
333
- state { "evaluating" }
389
+ state { :evaluating }
334
390
  answered_at { Time.current }
335
391
  state_published_at { Time.current }
336
392
  end
337
393
 
338
394
  trait :accepted do
339
- state { "accepted" }
395
+ state { :accepted }
340
396
  answered_at { Time.current }
341
397
  state_published_at { Time.current }
342
398
  end
343
399
 
344
400
  trait :rejected do
345
- state { "rejected" }
401
+ state { :rejected }
346
402
  answered_at { Time.current }
347
403
  state_published_at { Time.current }
348
404
  end
349
405
 
350
406
  trait :withdrawn do
351
- state { "withdrawn" }
407
+ withdrawn_at { Time.current }
352
408
  end
353
409
 
354
410
  trait :accepted_not_published do
355
- state { "accepted" }
411
+ state { :accepted }
356
412
  answered_at { Time.current }
357
413
  state_published_at { nil }
358
414
  answer { generate_localized_title }
359
415
  end
360
416
 
361
417
  trait :with_answer do
362
- state { "accepted" }
418
+ state { :accepted }
363
419
  answer { generate_localized_title }
364
420
  answered_at { Time.current }
365
421
  state_published_at { Time.current }
@@ -4,7 +4,7 @@ module Decidim
4
4
  # This holds decidim-proposals version.
5
5
  module Proposals
6
6
  def self.version
7
- "0.28.3"
7
+ "0.29.0.rc1"
8
8
  end
9
9
  end
10
10
  end