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
@@ -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>