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
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ class ParticipatorySpaceProposals < Rectify::Query
7
+ # Sugar syntax. Retrieve all proposals for the given sortition.
8
+ def self.for(sortition)
9
+ new(sortition).query
10
+ end
11
+
12
+ # Initializes the class.
13
+ #
14
+ # sortition - a sortition to select proposals
15
+ def initialize(sortition)
16
+ @sortition = sortition
17
+ @category = sortition.category
18
+ @request_timestamp = sortition.request_timestamp
19
+ end
20
+
21
+ # Given a particpiatory process retrieves its proposals
22
+ #
23
+ # Returns an ActiveRecord::Relation.
24
+ def query
25
+ if category.nil?
26
+ return Decidim::Proposals::Proposal
27
+ .except_withdrawn
28
+ .where("created_at < ?", request_timestamp)
29
+ .where(feature: sortition.decidim_proposals_feature)
30
+ .order(id: :asc)
31
+ end
32
+
33
+ # categorization -> category
34
+ Decidim::Proposals::Proposal
35
+ .joins(:categorization)
36
+ .except_withdrawn
37
+ .where(feature: sortition.decidim_proposals_feature)
38
+ .where("decidim_proposals_proposals.created_at < ?", request_timestamp)
39
+ .where("decidim_categorizations.decidim_category_id = ?", category.id)
40
+ .order(id: :asc)
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :sortition, :category, :request_timestamp
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ # A class used to find sortitions filtered by features and a date range
6
+ class FilteredSortitions < Rectify::Query
7
+ # Syntactic sugar to initialize the class and return the queried objects.
8
+ #
9
+ # features - An array of Decidim::Feature
10
+ # start_at - A date to filter resources created after it
11
+ # end_at - A date to filter resources created before it.
12
+ def self.for(features, start_at = nil, end_at = nil)
13
+ new(features, start_at, end_at).query
14
+ end
15
+
16
+ # Initializes the class.
17
+ #
18
+ # features - An array of Decidim::Feature
19
+ # start_at - A date to filter resources created after it
20
+ # end_at - A date to filter resources created before it.
21
+ def initialize(features, start_at = nil, end_at = nil)
22
+ @features = features
23
+ @start_at = start_at
24
+ @end_at = end_at
25
+ end
26
+
27
+ # Finds the Proposals scoped to an array of features and filtered
28
+ # by a range of dates.
29
+ def query
30
+ sortitions = Decidim::Sortitions::Sortition.where(feature: @features)
31
+ sortitions = sortitions.where("created_at >= ?", @start_at) if @start_at.present?
32
+ sortitions = sortitions.where("created_at <= ?", @end_at) if @end_at.present?
33
+ sortitions
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ # This class represents a draw for selecting proposals.
7
+ class Draw
8
+ attr_reader :sortition
9
+
10
+ # Sugar syntax. Retrieves the list of selected proposals.
11
+ def self.for(sortition)
12
+ new(sortition).results
13
+ end
14
+
15
+ # Initializes the draw class. Receives a sortition.
16
+ def initialize(sortition)
17
+ @sortition = sortition
18
+ end
19
+
20
+ # Executes the draw and return the selected proposal ids.
21
+ def results
22
+ proposals.sample(sortition.target_items, random: Random.new(sortition.seed)).pluck(:id)
23
+ end
24
+
25
+ def proposals
26
+ @proposals ||= ParticipatorySpaceProposals.for(sortition).to_a
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ # A service to encapsualte all the logic when searching and filtering
6
+ # sortitions in a participatory process.
7
+ class SortitionSearch < ResourceSearch
8
+ # Public: Initializes the service.
9
+ # feature - A Decidim::Feature to get the proposals from.
10
+ # page - The page number to paginate the results.
11
+ # per_page - The number of proposals to return per page.
12
+ def initialize(options = {})
13
+ super(Sortition.all, options)
14
+ end
15
+
16
+ # Handle the search_text filter
17
+ def search_search_text
18
+ query
19
+ .where("title->>'#{current_locale}' ILIKE ?", "%#{search_text}%")
20
+ .or(
21
+ query.where("additional_info->>'#{current_locale}' ILIKE ?", "%#{search_text}%")
22
+ )
23
+ .or(
24
+ query.where("witnesses->>'#{current_locale}' ILIKE ?", "%#{search_text}%")
25
+ )
26
+ end
27
+
28
+ # Handle the state filter
29
+ def search_state
30
+ case state
31
+ when "active"
32
+ query.active
33
+ when "cancelled"
34
+ query.cancelled
35
+ else # Assume 'all'
36
+ query
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def current_locale
43
+ I18n.locale.to_s
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title"><%= t ".title" %></h2>
4
+ </div>
5
+
6
+ <div class="card-section">
7
+ <div class="row">
8
+ <div class="columns xlarge-12">
9
+ <%= form.translated :text_field, :title, autofocus: true %>
10
+ </div>
11
+
12
+ <div class="columns xlarge-4">
13
+ <%= form.select :decidim_proposals_feature_id,
14
+ features_options(proposal_features),
15
+ prompt: t(".select_proposal_feature") %>
16
+ </div>
17
+
18
+ <div class="columns xlarge-4">
19
+ <%= form.categories_select :decidim_category_id,
20
+ @form.categories,
21
+ prompt: t(".all_categories") %>
22
+ </div>
23
+
24
+ <div class="columns xlarge-4">
25
+ <%= form.number_field :target_items, min: 1, step: 1 %>
26
+ </div>
27
+
28
+ <div class="columns xlarge-12">
29
+ <%= form.translated :editor, :witnesses, toolbar: :full, lines: 10 %>
30
+ </div>
31
+
32
+ <div class="columns xlarge-12">
33
+ <%= form.translated :editor, :additional_info, toolbar: :full, lines: 10 %>
34
+ </div>
35
+
36
+ <div class="columns xlarge-2">
37
+ <%= form.number_field :dice, min: 1, max: 6, step: 1 %>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </div>
@@ -0,0 +1,21 @@
1
+ <h2 class="process-title-summary"><%= t ".title" %></h2>
2
+
3
+ <%= decidim_form_for(@form, html: { class: "form confirm_destroy_sortition" }, method: :delete) do |form| %>
4
+ <div class="card">
5
+ <div class="card-divider">
6
+ <h2 class="card-title"><%= translated_attribute sortition.title %></h2>
7
+ </div>
8
+
9
+ <div class="card-section">
10
+ <div class="row">
11
+ <div class="columns xlarge-12">
12
+ <%= form.translated :editor, :cancel_reason, toolbar: :full, lines: 10 %>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="button--double form-general-submit">
19
+ <%= form.submit t(".destroy"), data: { confirm: t(".confirm_destroy") } %>
20
+ </div>
21
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <h2 class="process-title-summary">
2
+ <%= translated_attribute(sortition.title) %>
3
+ </h2>
4
+
5
+ <%= decidim_form_for(@form, html: { class: "form edit_sortition" }) do |form| %>
6
+ <div class="card">
7
+ <div class="card-divider">
8
+ <h2 class="card-title"><%= t ".title" %></h2>
9
+ </div>
10
+
11
+ <div class="card-section">
12
+ <div class="row">
13
+ <div class="columns xlarge-12">
14
+ <%= form.translated :text_field, :title, autofocus: true %>
15
+ </div>
16
+
17
+ <div class="columns xlarge-12">
18
+ <%= form.translated :editor, :additional_info, toolbar: :full, lines: 10 %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="button--double form-general-submit">
25
+ <%= form.submit t(".update") %>
26
+ </div>
27
+ <% end %>
@@ -0,0 +1,57 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title">
4
+ <%= t(".title") %>
5
+ <div class="button--title">
6
+ <% if can? :create, Decidim::Sortitions::Sortition %>
7
+ <%= link_to t("actions.new", scope: "decidim.sortitions.admin", name: t("models.sortition.name", scope: "decidim.sortitions.admin")), new_sortition_path, class: 'button tiny button--simple' if can? :manage, current_feature %>
8
+ <% end %>
9
+ </div>
10
+ </h2>
11
+ </div>
12
+
13
+ <div class="card-section">
14
+ <div class="table-scroll">
15
+ <table class="table-list">
16
+ <thead>
17
+ <tr>
18
+ <th><%= t("models.sortition.fields.reference", scope: "decidim.sortitions.admin") %></th>
19
+ <th><%= t("models.sortition.fields.title", scope: "decidim.sortitions.admin") %></th>
20
+ <th><%= t("models.sortition.fields.created_at", scope: "decidim.sortitions.admin") %></th>
21
+ <th class="actions"></th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <% sortitions.each do |sortition| %>
26
+ <tr>
27
+ <td><%= sortition.reference %></td>
28
+ <td><%= translated_attribute sortition.title %></td>
29
+ <td><%=l sortition.created_at, format: :short %></td>
30
+ <td class="table-list__actions">
31
+ <%= icon_link_to "eye",
32
+ sortition_path(sortition),
33
+ t("actions.show", scope: "decidim.sortitions.admin"),
34
+ class: "action-icon--preview" %>
35
+
36
+ <% if can? :destroy, sortition %>
37
+ <%= icon_link_to "pencil",
38
+ edit_sortition_path(sortition),
39
+ t("actions.edit", scope: "decidim.sortitions.admin"),
40
+ class: "action-icon--edit" %>
41
+ <% end %>
42
+
43
+ <% if can? :destroy, sortition %>
44
+ <%= icon_link_to "circle-x",
45
+ confirm_destroy_sortition_path(sortition),
46
+ t("actions.destroy", scope: "decidim.sortitions.admin"),
47
+ class: "action-icon--remove" %>
48
+ <% end %>
49
+ </td>
50
+ </tr>
51
+ <% end %>
52
+ </tbody>
53
+ </table>
54
+ <%= paginate sortitions, theme: "decidim" %>
55
+ </div>
56
+ </div>
57
+ </div>
@@ -0,0 +1,11 @@
1
+ <h2 class="process-title-summary">
2
+ <%=t ".title" %>
3
+ </h2>
4
+
5
+ <%= decidim_form_for(@form, html: { class: "form new_sortition" }) do |f| %>
6
+ <%= render partial: "form", object: f %>
7
+
8
+ <div class="button--double form-general-submit">
9
+ <%= f.submit t(".create"), data: { confirm: t(".confirm") } %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,55 @@
1
+ <div class="card" id="sortition">
2
+ <div class="card-divider">
3
+ <h2 class="card-title">
4
+ <%= translated_attribute(sortition.title) %>
5
+ </h2>
6
+ </div>
7
+ <div class="card-section">
8
+ <div class="table-scroll">
9
+ <table class="table-list sortition">
10
+ <thead>
11
+ <tr>
12
+ <th><%= t("models.sortition.fields.request_timestamp", scope: "decidim.sortitions.admin") %></th>
13
+ <th><%= t("models.sortition.fields.dice", scope: "decidim.sortitions.admin") %></th>
14
+ <th><%= t("models.sortition.fields.target_items", scope: "decidim.sortitions.admin") %></th>
15
+ <th><%= t("models.sortition.fields.category", scope: "decidim.sortitions.admin") %></th>
16
+ <th><%= t("models.sortition.fields.decidim_proposals_feature", scope: "decidim.sortitions.admin") %></th>
17
+ <th><%= t("models.sortition.fields.seed", scope: "decidim.sortitions.admin") %></th>
18
+ <th></th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <tr>
23
+ <td><%=l sortition.request_timestamp, format: :long %></td>
24
+ <td><%= sortition.dice %></td>
25
+ <td><%= sortition.target_items %></td>
26
+ <td><%= sortition_category sortition %></td>
27
+ <td><%= translated_attribute sortition.decidim_proposals_feature.name %></td>
28
+ <td><%= sortition.seed %></td>
29
+ <td></td>
30
+ </tr>
31
+ </tbody>
32
+ </table>
33
+ </div>
34
+ </div>
35
+ <div class="card-section">
36
+ <div class="table-scroll">
37
+ <table class="table-list proposals">
38
+ <thead>
39
+ <tr>
40
+ <th><%=t ".selected_proposals" %></th>
41
+ <th></th>
42
+ </tr>
43
+ </thead>
44
+ <tbody>
45
+ <% sortition.proposals.each do |proposal| %>
46
+ <tr>
47
+ <td><%= proposal.title %></td>
48
+ <td></td>
49
+ </tr>
50
+ <% end %>
51
+ </tbody>
52
+ </table>
53
+ </div>
54
+ </div>
55
+ </div>
@@ -0,0 +1,4 @@
1
+ <% content_for(:title, translated_attribute(model.title)) %>
2
+ <p>
3
+ <%= decidim_sanitize html_truncate(translated_attribute(model.additional_info), length: 100, separator: '...') %>
4
+ </p>
@@ -0,0 +1 @@
1
+ <%= t(".proposals_count", count: proposals.count) %>
@@ -0,0 +1,31 @@
1
+ <%= filter_form_for filter do |form| %>
2
+ <div class="filters__section">
3
+ <div class="filters__search">
4
+ <div class="input-group">
5
+ <%= form.search_field :search_text, label: false, class: "input-group-field", placeholder: t('.search') %>
6
+ <div class="input-group-button">
7
+ <button type="submit" class="button button--muted">
8
+ <%= icon "magnifying-glass", aria_label: t('.search') %>
9
+ </button>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ </div>
14
+
15
+ <% if current_participatory_space.categories.any? %>
16
+ <%= form.categories_select :category_id,
17
+ current_participatory_space.categories,
18
+ legend_title: t('.category'),
19
+ disable_parents: false,
20
+ label: false,
21
+ prompt: t('.category_prompt') %>
22
+ <% end %>
23
+
24
+ <%= form.collection_radio_buttons :state,
25
+ [["all", t('.all')], ["active", t('.active')], ["cancelled", t(".cancelled")]],
26
+ :first,
27
+ :last,
28
+ legend_title: t('.state') %>
29
+
30
+ <%= hidden_field_tag :order, order, id: nil, class: "order_filter" %>
31
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <div class="filters-controls hide-for-mediumlarge">
2
+ <button data-open="filter-box" class="filters-controls__trigger">
3
+ <%= t ".filter" %>
4
+ <%= icon "caret-bottom", class: "icon--small float-right", aria_label: t('.unfold'), role: "img" %>
5
+ </button>
6
+ </div>
7
+
8
+ <div class="reveal" id="filter-box" data-reveal>
9
+ <div class="reveal__header">
10
+ <h3 class="reveal__title"><%= t ".filter_by" %>:</h3>
11
+ <button class="close-button" data-close aria-label="<%= t(".close_modal") %>" type="button">
12
+ <span aria-hidden="true">&times;</span>
13
+ </button>
14
+ </div>
15
+ <div class="filters">
16
+ <%= render partial: "filters" %>
17
+ </div>
18
+ </div>