decidim-sortitions 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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