decidim-sortitions 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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +31 -0
  4. data/Rakefile +3 -0
  5. data/app/assets/config/decidim_sortitions_manifest.css +1 -0
  6. data/app/assets/config/decidim_sortitions_manifest.js +1 -0
  7. data/app/assets/images/decidim/sortitions/icon.svg +4 -0
  8. data/app/assets/javascripts/decidim/sortitions/admin/sortitions.js +2 -0
  9. data/app/assets/javascripts/decidim/sortitions/social_share.js +2 -0
  10. data/app/assets/stylesheets/decidim/sortitions/_sortitions.scss +45 -0
  11. data/app/assets/stylesheets/decidim/sortitions/social_share.css.scss +18 -0
  12. data/app/commands/decidim/sortitions/admin/create_sortition.rb +81 -0
  13. data/app/commands/decidim/sortitions/admin/destroy_sortition.rb +45 -0
  14. data/app/commands/decidim/sortitions/admin/update_sortition.rb +45 -0
  15. data/app/controllers/concerns/decidim/sortitions/orderable.rb +50 -0
  16. data/app/controllers/decidim/sortitions/admin/application_controller.rb +28 -0
  17. data/app/controllers/decidim/sortitions/admin/sortitions_controller.rb +104 -0
  18. data/app/controllers/decidim/sortitions/application_controller.rb +13 -0
  19. data/app/controllers/decidim/sortitions/sortition_widgets_controller.rb +20 -0
  20. data/app/controllers/decidim/sortitions/sortitions_controller.rb +45 -0
  21. data/app/events/decidim/sortitions/create_sortition_event.rb +8 -0
  22. data/app/forms/decidim/sortitions/admin/destroy_sortition_form.rb +20 -0
  23. data/app/forms/decidim/sortitions/admin/edit_sortition_form.rb +22 -0
  24. data/app/forms/decidim/sortitions/admin/sortition_form.rb +44 -0
  25. data/app/helpers/decidim/sortitions/admin/sortitions_helper.rb +24 -0
  26. data/app/helpers/decidim/sortitions/sortitions_helper.rb +38 -0
  27. data/app/models/decidim/sortitions/abilities/admin/admin_ability.rb +23 -0
  28. data/app/models/decidim/sortitions/abilities/admin/process_admin_ability.rb +46 -0
  29. data/app/models/decidim/sortitions/application_record.rb +9 -0
  30. data/app/models/decidim/sortitions/sortition.rb +81 -0
  31. data/app/queries/decidim/sortitions/admin/participatory_space_proposal_features.rb +29 -0
  32. data/app/queries/decidim/sortitions/admin/participatory_space_proposals.rb +49 -0
  33. data/app/queries/decidim/sortitions/filtered_sortitions.rb +37 -0
  34. data/app/services/decidim/sortitions/admin/draw.rb +31 -0
  35. data/app/services/decidim/sortitions/sortition_search.rb +47 -0
  36. data/app/views/decidim/sortitions/admin/sortitions/_form.html.erb +41 -0
  37. data/app/views/decidim/sortitions/admin/sortitions/confirm_destroy.html.erb +21 -0
  38. data/app/views/decidim/sortitions/admin/sortitions/edit.html.erb +27 -0
  39. data/app/views/decidim/sortitions/admin/sortitions/index.html.erb +57 -0
  40. data/app/views/decidim/sortitions/admin/sortitions/new.html.erb +11 -0
  41. data/app/views/decidim/sortitions/admin/sortitions/show.html.erb +55 -0
  42. data/app/views/decidim/sortitions/sortition_widgets/show.html.erb +4 -0
  43. data/app/views/decidim/sortitions/sortitions/_count.html.erb +1 -0
  44. data/app/views/decidim/sortitions/sortitions/_filters.html.erb +31 -0
  45. data/app/views/decidim/sortitions/sortitions/_filters_small_view.html.erb +18 -0
  46. data/app/views/decidim/sortitions/sortitions/_filters_small_view0.html.erb +18 -0
  47. data/app/views/decidim/sortitions/sortitions/_linked_sortitions.html.erb +29 -0
  48. data/app/views/decidim/sortitions/sortitions/_proposal.html.erb +24 -0
  49. data/app/views/decidim/sortitions/sortitions/_proposal_badge.html.erb +3 -0
  50. data/app/views/decidim/sortitions/sortitions/_results_count.html.erb +8 -0
  51. data/app/views/decidim/sortitions/sortitions/_sortition.html.erb +25 -0
  52. data/app/views/decidim/sortitions/sortitions/_sortition_author.html.erb +17 -0
  53. data/app/views/decidim/sortitions/sortitions/_sortition_cancel_author.html.erb +17 -0
  54. data/app/views/decidim/sortitions/sortitions/_sortitions.html.erb +14 -0
  55. data/app/views/decidim/sortitions/sortitions/_sortitions_count.html.erb +1 -0
  56. data/app/views/decidim/sortitions/sortitions/_tags.html.erb +16 -0
  57. data/app/views/decidim/sortitions/sortitions/index.html.erb +21 -0
  58. data/app/views/decidim/sortitions/sortitions/index.js.erb +10 -0
  59. data/app/views/decidim/sortitions/sortitions/show.html.erb +134 -0
  60. data/config/initializers/social_share_button.rb +5 -0
  61. data/config/locales/ca.yml +155 -0
  62. data/config/locales/en.yml +154 -0
  63. data/config/locales/es.yml +155 -0
  64. data/db/migrate/20171215161358_create_decidim_module_sortitions_sortitions.rb +23 -0
  65. data/db/migrate/20171220164658_add_witnesses_to_sortitions.rb +7 -0
  66. data/db/migrate/20171220164744_add_additional_info_to_sortitions.rb +7 -0
  67. data/db/migrate/20180102100101_add_author_to_sortitions.rb +7 -0
  68. data/db/migrate/20180102101128_add_reference_to_sortitions.rb +8 -0
  69. data/db/migrate/20180103082645_add_title_to_sortition.rb +7 -0
  70. data/db/migrate/20180103123055_drop_decidim_category_id_from_sortitions.rb +13 -0
  71. data/db/migrate/20180103160301_add_cancel_data_to_sortition.rb +9 -0
  72. data/db/migrate/20180104143054_make_sortition_reference_nullable.rb +7 -0
  73. data/db/migrate/20180104145344_add_candidate_proposals_to_sortitions.rb +7 -0
  74. data/db/migrate/20180108132729_rename_sortitions_table.rb +7 -0
  75. data/lib/decidim/sortitions.rb +24 -0
  76. data/lib/decidim/sortitions/admin.rb +10 -0
  77. data/lib/decidim/sortitions/admin_engine.rb +35 -0
  78. data/lib/decidim/sortitions/engine.rb +26 -0
  79. data/lib/decidim/sortitions/feature.rb +43 -0
  80. data/lib/decidim/sortitions/test/factories.rb +37 -0
  81. data/lib/decidim/sortitions/version.rb +7 -0
  82. metadata +208 -0
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Decidim::Sortitions
2
+ This module makes possible to select amont a set of proposal by sortition.
3
+
4
+ ## Usage
5
+ Simply include it in your Decidim instance.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'decidim-sortitions'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install decidim-sortitions
22
+ ```
23
+
24
+ ## Import migrations
25
+
26
+ After installing the gem you must import and execute the migrations bundled with the gem:
27
+
28
+ ```bash
29
+ $ bundle exec rails decidim_sortitions:install:migrations
30
+ $ bundle exec rails db:migrate
31
+ ```
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/dev/common_rake"
@@ -0,0 +1 @@
1
+ /*= link decidim/sortitions/social_share.css.scss */
@@ -0,0 +1 @@
1
+ //= link decidim/sortitions/social_share.js
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="8" height="8" data-icon="aperture" viewBox="0 0 8 8">
2
+ <path d="M4 0c-.69 0-1.336.19-1.906.5l3.219 2.344.719-2.25c-.59-.36-1.281-.594-2.031-.594zm-2.75 1.125c-.76.73-1.25 1.735-1.25 2.875 0 .25.022.489.063.719l3.094-2.219-1.906-1.375zm5.625.125l-1.219 3.75h2.219c.08-.32.125-.65.125-1 0-1.07-.435-2.03-1.125-2.75zm-4.719 3.188l-1.75 1.281c.55 1.13 1.595 1.989 2.875 2.219l-1.125-3.5zm1.563 1.563l.625 1.969c1.33-.11 2.454-.879 3.094-1.969h-3.719z"
3
+ />
4
+ </svg>
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,2 @@
1
+ // = require social-share-button
2
+ // = require_self
@@ -0,0 +1,45 @@
1
+ #sortition-details {
2
+ margin-bottom: 1.5rem;
3
+ }
4
+
5
+ .sortition-details {
6
+ padding: 0;
7
+ display: flex;
8
+ flex-direction: row;
9
+ flex-wrap: wrap;
10
+ justify-content: flex-start;
11
+ align-items: stretch;
12
+ align-content: stretch;
13
+
14
+ .sortition-details-item {
15
+ width: 33.3%;
16
+ @include breakpoint(large down){
17
+ width: 33.3%;
18
+ }
19
+ @include breakpoint(medium down){
20
+ width: 49.9%;
21
+ }
22
+ @include breakpoint(smallmedium down){
23
+ width: 100%;
24
+ }
25
+ display: inline-block;
26
+ padding: 1em;
27
+ border: $border;
28
+ background: $white;
29
+ .sortition-details-title {
30
+ margin-right: 0.5rem;
31
+ font-weight: bolder;
32
+ }
33
+
34
+ .sortition-details-text{
35
+ text-transform: uppercase;
36
+ letter-spacing: 0.03em;
37
+ font-size: 90%;
38
+ color: #3D393C;
39
+ font-weight: 600;
40
+ line-height: 1;
41
+ vertical-align: middle;
42
+ height: 1.5em;
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,18 @@
1
+ /*= require social-share-button */
2
+ $size: 45px;
3
+
4
+ .share-link:hover {
5
+ text-decoration: underline;
6
+ cursor: pointer;
7
+ }
8
+
9
+ .social-share-button {
10
+ display: inline-block;
11
+ vertical-align: top;
12
+ .ssb-icon {
13
+ margin-right: 5px;
14
+ background-size: $size $size;
15
+ height: $size;
16
+ width: $size;
17
+ }
18
+ }
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ # Command that creates a sortition that selects proposals
7
+ class CreateSortition < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # form - A form object with the params.
11
+ def initialize(form)
12
+ @form = form
13
+ end
14
+
15
+ # Executes the command. Broadcasts these events:
16
+ #
17
+ # - :ok when everything is valid.
18
+ # - :invalid if the form wasn't valid and we couldn't proceed.
19
+ #
20
+ # Returns nothing.
21
+ def call
22
+ return broadcast(:invalid) if form.invalid?
23
+
24
+ ActiveRecord::Base.transaction do
25
+ sortition = create_sortition
26
+ categorize(sortition) if form.decidim_category_id.present?
27
+ select_proposals_for(sortition)
28
+ send_notification(sortition)
29
+
30
+ broadcast(:ok, sortition)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :form
37
+
38
+ def create_sortition
39
+ Sortition.create!(
40
+ feature: form.current_feature,
41
+ title: form.title,
42
+ decidim_proposals_feature_id: form.decidim_proposals_feature_id,
43
+ request_timestamp: Time.now.utc,
44
+ author: form.current_user,
45
+ dice: form.dice,
46
+ target_items: form.target_items,
47
+ witnesses: form.witnesses,
48
+ additional_info: form.additional_info,
49
+ selected_proposals: [],
50
+ candidate_proposals: []
51
+ )
52
+ end
53
+
54
+ def categorize(sortition)
55
+ Decidim::Categorization.create!(
56
+ decidim_category_id: form.decidim_category_id,
57
+ categorizable: sortition
58
+ )
59
+ end
60
+
61
+ def select_proposals_for(sortition)
62
+ draw = Draw.new(sortition)
63
+
64
+ sortition.update(
65
+ selected_proposals: draw.results,
66
+ candidate_proposals: draw.proposals.pluck(:id)
67
+ )
68
+ end
69
+
70
+ def send_notification(sortition)
71
+ Decidim::EventsManager.publish(
72
+ event: "decidim.events.sortitions.sortition_created",
73
+ event_class: Decidim::Sortitions::CreateSortitionEvent,
74
+ resource: sortition,
75
+ recipient_ids: sortition.participatory_space.followers.pluck(:id)
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ # Command that cancels a sortition
7
+ class DestroySortition < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # form - A form object with the params.
11
+ def initialize(form)
12
+ @form = form
13
+ end
14
+
15
+ # Executes the command. Broadcasts these events:
16
+ #
17
+ # - :ok when everything is valid.
18
+ # - :invalid if the form wasn't valid and we couldn't proceed.
19
+ #
20
+ # Returns nothing.
21
+ def call
22
+ return broadcast(:invalid) if form.invalid?
23
+ destroy_sortition
24
+ broadcast(:ok, sortition)
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :form
30
+
31
+ def destroy_sortition
32
+ sortition.update(
33
+ cancel_reason: form.cancel_reason,
34
+ cancelled_on: Time.now.utc,
35
+ cancelled_by_user: form.current_user
36
+ )
37
+ end
38
+
39
+ def sortition
40
+ @sortition ||= Sortition.find(form.id)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ # Command that creates a sortition that selects proposals
7
+ class UpdateSortition < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # form - A form object with the params.
11
+ def initialize(form)
12
+ @form = form
13
+ end
14
+
15
+ # Executes the command. Broadcasts these events:
16
+ #
17
+ # - :ok when everything is valid.
18
+ # - :invalid if the form wasn't valid and we couldn't proceed.
19
+ #
20
+ # Returns nothing.
21
+ def call
22
+ return broadcast(:invalid) if form.invalid?
23
+
24
+ update_sortition
25
+ broadcast(:ok, sortition)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :form
31
+
32
+ def update_sortition
33
+ sortition.update(
34
+ title: form.title,
35
+ additional_info: form.additional_info
36
+ )
37
+ end
38
+
39
+ def sortition
40
+ @sortition ||= Sortition.find(form.id)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Sortitions
7
+ # Common logic to ordering resources
8
+ module Orderable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ helper_method :order, :available_orders, :random_seed
13
+
14
+ private
15
+
16
+ # Gets how the proposals should be ordered based on the choice made by the user.
17
+ def order
18
+ @order ||= detect_order(params[:order]) || default_order
19
+ end
20
+
21
+ # Available orders based on enabled settings
22
+ def available_orders
23
+ %w(random recent)
24
+ end
25
+
26
+ def default_order
27
+ "recent"
28
+ end
29
+
30
+ # Returns: A random float number between -1 and 1 to be used as a random seed at the database.
31
+ def random_seed
32
+ @random_seed ||= (params[:random_seed] ? params[:random_seed].to_f : (rand * 2 - 1))
33
+ end
34
+
35
+ def detect_order(candidate)
36
+ available_orders.detect { |order| order == candidate }
37
+ end
38
+
39
+ def reorder(sortitions)
40
+ case order
41
+ when "random"
42
+ sortitions.order_randomly(random_seed)
43
+ when "recent"
44
+ sortitions.order(created_at: :desc)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ # This controller is the abstract class from which all other controllers of
7
+ # this engine inherit.
8
+ #
9
+ # Note that it inherits from `Decidim::Features::BaseController`, which
10
+ # override its layout and provide all kinds of useful methods.
11
+ class ApplicationController < Decidim::Admin::Features::BaseController
12
+ helper_method :sortitions, :sortition
13
+
14
+ def sortitions
15
+ @sortitions ||= Decidim::Sortitions::FilteredSortitions
16
+ .for(current_feature)
17
+ .order(created_at: :desc)
18
+ .page(params[:page])
19
+ .per(Decidim::Sortitions.items_per_page)
20
+ end
21
+
22
+ def sortition
23
+ @sortition ||= sortitions.find(params[:id])
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ # Controller responsible of the sortition that selects proposals from
7
+ # a participatory space.
8
+ class SortitionsController < Admin::ApplicationController
9
+ helper_method :proposal_features
10
+
11
+ def index; end
12
+
13
+ def show
14
+ authorize! :show, sortition
15
+ end
16
+
17
+ def edit
18
+ authorize! :update, sortition
19
+ @form = edit_sortition_form.from_model(sortition, current_participatory_space: current_participatory_space)
20
+ end
21
+
22
+ def update
23
+ authorize! :update, sortition
24
+ @form = edit_sortition_form.from_params(params, current_participatory_space: current_participatory_space)
25
+ UpdateSortition.call(@form) do
26
+ on(:ok) do |_sortition|
27
+ flash[:notice] = I18n.t("sortitions.update.success", scope: "decidim.sortitions.admin")
28
+ redirect_to action: :index
29
+ end
30
+
31
+ on(:invalid) do
32
+ flash.now[:alert] = I18n.t("sortitions.update.error", scope: "decidim.sortitions.admin")
33
+ render :edit
34
+ end
35
+ end
36
+ end
37
+
38
+ def new
39
+ authorize! :create, Sortition
40
+ @form = sortition_form.instance(current_participatory_space: current_participatory_space)
41
+ end
42
+
43
+ def create
44
+ authorize! :create, Sortition
45
+ @form = sortition_form.from_params(params, current_participatory_space: current_participatory_space)
46
+ CreateSortition.call(@form) do
47
+ on(:ok) do |sortition|
48
+ flash[:notice] = I18n.t("sortitions.create.success", scope: "decidim.sortitions.admin")
49
+ redirect_to action: :show, id: sortition.id
50
+ end
51
+
52
+ on(:invalid) do
53
+ flash.now[:alert] = I18n.t("sortitions.create.error", scope: "decidim.sortitions.admin")
54
+ render :new
55
+ end
56
+ end
57
+ end
58
+
59
+ def confirm_destroy
60
+ authorize! :destroy, sortition
61
+ @form = destroy_sortition_form.from_model(sortition, current_participatory_space: current_participatory_space)
62
+ end
63
+
64
+ def destroy
65
+ authorize! :destroy, sortition
66
+ @form = destroy_sortition_form.from_params(params, current_participatory_space: current_participatory_space)
67
+ DestroySortition.call(@form) do
68
+ on(:ok) do |_sortition|
69
+ flash[:notice] = I18n.t("sortitions.destroy.success", scope: "decidim.sortitions.admin")
70
+ redirect_to action: :index
71
+ end
72
+
73
+ on(:invalid) do
74
+ flash.now[:alert] = I18n.t("sortitions.destroy.error", scope: "decidim.sortitions.admin")
75
+ render :confirm_destroy
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def sortition_form
83
+ form(SortitionForm)
84
+ end
85
+
86
+ def edit_sortition_form
87
+ form(EditSortitionForm)
88
+ end
89
+
90
+ def destroy_sortition_form
91
+ form(DestroySortitionForm)
92
+ end
93
+
94
+ def proposal_features
95
+ ParticipatorySpaceProposalFeatures.for(current_participatory_space)
96
+ end
97
+
98
+ def ability_context
99
+ super.merge(current_participatory_space: current_participatory_space)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end