decidim-accountability 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +5 -2
  3. data/app/assets/config/decidim_accountability_manifest.js +1 -0
  4. data/app/assets/javascripts/decidim/accountability/accountability.js.es6 +1 -0
  5. data/app/assets/javascripts/decidim/accountability/version_diff.js.es6 +37 -0
  6. data/app/assets/stylesheets/decidim/accountability/accountability/_versions.scss +8 -0
  7. data/app/commands/decidim/accountability/admin/create_result.rb +3 -1
  8. data/app/commands/decidim/accountability/admin/update_result.rb +3 -1
  9. data/app/controllers/decidim/accountability/results_controller.rb +3 -19
  10. data/app/controllers/decidim/accountability/versions_controller.rb +24 -0
  11. data/app/helpers/decidim/accountability/breadcrumb_helper.rb +27 -0
  12. data/app/models/decidim/accountability/result.rb +1 -0
  13. data/app/models/decidim/accountability/status.rb +1 -1
  14. data/app/services/decidim/accountability/diff_renderer.rb +83 -0
  15. data/app/views/decidim/accountability/results/_show_parent.html.erb +9 -5
  16. data/app/views/decidim/accountability/results/_stats_box.html.erb +46 -27
  17. data/app/views/decidim/accountability/versions/_version.html.erb +20 -0
  18. data/app/views/decidim/accountability/versions/index.html.erb +30 -0
  19. data/app/views/decidim/accountability/versions/show.html.erb +41 -0
  20. data/config/locales/ca.yml +17 -0
  21. data/config/locales/en.yml +17 -0
  22. data/config/locales/es.yml +17 -0
  23. data/config/locales/eu.yml +17 -0
  24. data/config/locales/fi.yml +17 -0
  25. data/config/locales/fr.yml +17 -0
  26. data/config/locales/it.yml +44 -27
  27. data/config/locales/nl.yml +33 -16
  28. data/config/locales/pl.yml +17 -0
  29. data/config/locales/pt.yml +174 -0
  30. data/config/locales/uk.yml +11 -0
  31. data/lib/decidim/accountability/engine.rb +1 -0
  32. data/lib/decidim/accountability/feature.rb +11 -2
  33. data/lib/decidim/accountability/test/factories.rb +7 -4
  34. data/lib/decidim/accountability/version.rb +1 -1
  35. metadata +45 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: d1308279c907cde4a8e1693ea0d9dad5797457b39a1f0e8c0d17d5aebdabaab1
4
- data.tar.gz: 1df0d17479ce96df917b398a000ca39da38d2066ac97e9405fdd0d8ececebf6e
2
+ SHA1:
3
+ metadata.gz: b9e4ccd5f0cb93b7caf07991bf0f69d1de1002dc
4
+ data.tar.gz: fff3cc52314258d1d18d159be8ebf3b41854210b
5
5
  SHA512:
6
- metadata.gz: 5f9a0dde58e5df4a1bcaee18ff6a354581564f59115cbb958877a9bc079f655752fd976b628d1d147abcfa815879a433ea791043c5eba43a3c874669f0cdb334
7
- data.tar.gz: b5df2ad546c34657866b1d23f9619e4c95f8382ea26818ec84225cb7dfac56284c48dcdad29bdde57c6e9d2183c755487f525e267a51781f82b2cf507538ff37
6
+ metadata.gz: 9e615fb44d4e96e40555b689c2885652a7ba0ba6c7f7ad27663ad80019a86c96b6762c2f129a22c454e37c461b2f3eee990a539b0355ba6c705cfbd2ecdbc5b7
7
+ data.tar.gz: 107326f7daeefd26291889a25798a8e21a301c7abe0b6df8a5390b1e6ad421f68bc6ad233eb31345fbd862685ee3d9b68fc2ed01ad44ef7bd0eadead9fda76d6
data/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  The Accountability module adds results to any participatory process. It adds a CRUD engine to the admin and public views scoped inside the participatory process. Accountability will link to related meetings and proposals and will be used to show the progress on the related proposals.
4
4
 
5
-
6
5
  ## Usage
7
6
 
8
7
  Accountability will be available as a Feature for a Participatory Process.
9
8
 
10
9
  ## Installation
10
+
11
11
  Add this line to your application's Gemfile:
12
12
 
13
13
  ```ruby
@@ -15,12 +15,15 @@ gem 'decidim-accountability'
15
15
  ```
16
16
 
17
17
  And then execute:
18
+
18
19
  ```bash
19
- $ bundle
20
+ bundle
20
21
  ```
21
22
 
22
23
  ## Contributing
24
+
23
25
  See [Decidim](https://github.com/AjuntamentdeBarcelona/decidim).
24
26
 
25
27
  ## License
28
+
26
29
  See [Decidim](https://github.com/AjuntamentdeBarcelona/decidim).
@@ -1 +1,2 @@
1
1
  //= link decidim/accountability/accountability.js
2
+ //= link diff.js
@@ -1,3 +1,4 @@
1
+ // = require_tree .
1
2
  // = require_self
2
3
 
3
4
  $(() => {
@@ -0,0 +1,37 @@
1
+ /* global JsDiff */
2
+
3
+ $(() => {
4
+ $(".diff-i18n_html, .diff-i18n").each(function(_index, element) {
5
+ const diffElement = $(element);
6
+ const valueElement = diffElement.find(".diff__value");
7
+ const oldValue = valueElement.data("old-value").
8
+ replace(/</g, "&lt;").
9
+ replace(/>/g, "&gt;");
10
+ const newValue = valueElement.data("new-value").
11
+ replace(/</g, "&lt;").
12
+ replace(/>/g, "&gt;");
13
+
14
+ const diff = JsDiff.diffChars(oldValue, newValue);
15
+ let outputHTML = "";
16
+
17
+ diff.forEach(({added, removed, value}) => {
18
+ let color = "";
19
+
20
+ if (added) {
21
+ color = "#89ffaa";
22
+ } else if (removed) {
23
+ color = "red";
24
+ }
25
+
26
+ if (added || removed) {
27
+ outputHTML += `<span style="background-color: ${color}">${value}</span>`;
28
+ } else {
29
+ outputHTML += value;
30
+ }
31
+ });
32
+
33
+ outputHTML = outputHTML.replace(/\n/g, "<br><br>");
34
+
35
+ valueElement.html(outputHTML);
36
+ });
37
+ })
@@ -0,0 +1,8 @@
1
+ .diff {
2
+ .addition {
3
+ background-color: #e6ffed;
4
+ }
5
+ .removal {
6
+ background-color: #ffeef0
7
+ }
8
+ }
@@ -30,7 +30,9 @@ module Decidim
30
30
  attr_reader :result
31
31
 
32
32
  def create_result
33
- @result = Result.create!(
33
+ @result = Decidim.traceability.create!(
34
+ Result,
35
+ @form.current_user,
34
36
  feature: @form.current_feature,
35
37
  scope: @form.scope,
36
38
  category: @form.category,
@@ -35,7 +35,9 @@ module Decidim
35
35
  attr_reader :result, :form
36
36
 
37
37
  def update_result
38
- result.update_attributes!(
38
+ Decidim.traceability.update!(
39
+ result,
40
+ form.current_user,
39
41
  scope: @form.scope,
40
42
  category: @form.category,
41
43
  parent_id: @form.parent_id,
@@ -6,8 +6,10 @@ module Decidim
6
6
  class ResultsController < Decidim::Accountability::ApplicationController
7
7
  include FilterResource
8
8
  helper Decidim::WidgetUrlsHelper
9
+ helper Decidim::TraceabilityHelper
10
+ helper Decidim::Accountability::BreadcrumbHelper
9
11
 
10
- helper_method :results, :result, :stats_calculator, :first_class_categories, :category, :progress_calculator, :count_calculator, :current_scope
12
+ helper_method :results, :result, :first_class_categories, :count_calculator
11
13
 
12
14
  private
13
15
 
@@ -20,10 +22,6 @@ module Decidim
20
22
  @result ||= Result.includes(:timeline_entries).where(feature: current_feature).find(params[:id])
21
23
  end
22
24
 
23
- def stats_calculator
24
- @stats_calculator ||= ResultStatsCalculator.new(result)
25
- end
26
-
27
25
  def search_klass
28
26
  ResultSearch
29
27
  end
@@ -44,23 +42,9 @@ module Decidim
44
42
  @first_class_categories ||= current_participatory_space.categories.first_class
45
43
  end
46
44
 
47
- def category
48
- if params[:filter] && params[:filter][:category_id].present?
49
- current_participatory_space.categories.find(params[:filter][:category_id])
50
- end
51
- end
52
-
53
- def progress_calculator(scope_id, category_id)
54
- Decidim::Accountability::ResultsCalculator.new(current_feature, scope_id, category_id).progress
55
- end
56
-
57
45
  def count_calculator(scope_id, category_id)
58
46
  Decidim::Accountability::ResultsCalculator.new(current_feature, scope_id, category_id).count
59
47
  end
60
-
61
- def current_scope
62
- params[:filter][:scope_id] if params[:filter]
63
- end
64
48
  end
65
49
  end
66
50
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Accountability
5
+ # Exposes result versions so users can see how a result
6
+ # has been updated through time.
7
+ class VersionsController < Decidim::Accountability::ApplicationController
8
+ helper Decidim::TraceabilityHelper
9
+ helper Decidim::Accountability::BreadcrumbHelper
10
+ helper_method :current_version, :result
11
+
12
+ private
13
+
14
+ def result
15
+ @result ||= Result.includes(:timeline_entries).where(feature: current_feature).find(params[:result_id])
16
+ end
17
+
18
+ def current_version
19
+ return nil if params[:id].to_i < 1
20
+ @current_version ||= result.versions[params[:id].to_i - 1]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Accountability
5
+ # Helpers needed to render the navigation breadcrumbs in results.
6
+ #
7
+ module BreadcrumbHelper
8
+ def stats_calculator
9
+ @stats_calculator ||= ResultStatsCalculator.new(result)
10
+ end
11
+
12
+ def current_scope
13
+ params[:filter][:scope_id] if params[:filter]
14
+ end
15
+
16
+ def progress_calculator(scope_id, category_id)
17
+ Decidim::Accountability::ResultsCalculator.new(current_feature, scope_id, category_id).progress
18
+ end
19
+
20
+ def category
21
+ if params[:filter] && params[:filter][:category_id].present?
22
+ current_participatory_space.categories.find(params[:filter][:category_id])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -11,6 +11,7 @@ module Decidim
11
11
  include Decidim::HasCategory
12
12
  include Decidim::HasReference
13
13
  include Decidim::Comments::Commentable
14
+ include Decidim::Traceable
14
15
 
15
16
  feature_manifest_name "accountability"
16
17
 
@@ -9,7 +9,7 @@ module Decidim
9
9
 
10
10
  feature_manifest_name "accountability"
11
11
 
12
- has_many :results, foreign_key: "decidim_accountability_status_id", class_name: "Decidim::Accountability::Result", inverse_of: :status
12
+ has_many :results, foreign_key: "decidim_accountability_status_id", class_name: "Decidim::Accountability::Result", inverse_of: :status, dependent: :nullify
13
13
 
14
14
  validates :key, presence: true, uniqueness: { scope: :decidim_feature_id }
15
15
  validates :name, presence: true
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Accountability
5
+ class DiffRenderer
6
+ def initialize(version)
7
+ @version = version
8
+ end
9
+
10
+ # Renders the diff of the given changeset. Takes into account translatable fields.
11
+ #
12
+ # Returns a Hash, where keys are the fields that have changed and values are an
13
+ # array, the first element being the previous value and the last being the new one.
14
+ def diff
15
+ version.changeset.inject({}) do |diff, (attribute, values)|
16
+ attribute = attribute.to_sym
17
+ type = attribute_types[attribute]
18
+
19
+ if type.blank?
20
+ diff
21
+ else
22
+ parse_changeset(attribute, values, type, diff)
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :version
30
+
31
+ # Lists which attributes will be diffable and how
32
+ # they should be rendered.
33
+ def attribute_types
34
+ {
35
+ start_date: :date,
36
+ end_date: :date,
37
+ description: :i18n_html,
38
+ title: :i18n,
39
+ decidim_scope_id: :scope,
40
+ progress: :percentage
41
+ }
42
+ end
43
+
44
+ def parse_i18n_changeset(attribute, values, type, diff)
45
+ values.last.each_key do |locale, _value|
46
+ first_value = values.first.try(:[], locale)
47
+ last_value = values.last.try(:[], locale)
48
+ next if first_value == last_value
49
+
50
+ attribute_locale = "#{attribute}_#{locale}".to_sym
51
+ diff.update(
52
+ attribute_locale => {
53
+ type: type,
54
+ label: generate_i18n_label(attribute, locale),
55
+ old_value: first_value,
56
+ new_value: last_value
57
+ }
58
+ )
59
+ end
60
+ diff
61
+ end
62
+
63
+ def parse_changeset(attribute, values, type, diff)
64
+ return parse_i18n_changeset(attribute, values, type, diff) if [:i18n, :i18n_html].include?(type)
65
+
66
+ diff.update(
67
+ attribute => {
68
+ type: type,
69
+ label: I18n.t(attribute, scope: "activemodel.attributes.result"),
70
+ old_value: values[0],
71
+ new_value: values[1]
72
+ }
73
+ )
74
+ end
75
+
76
+ def generate_i18n_label(attribute, locale)
77
+ label = I18n.t(attribute, scope: "activemodel.attributes.result")
78
+ locale_name = I18n.t("locale.name", locale: locale)
79
+ "#{label} (#{locale_name})"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -6,13 +6,17 @@
6
6
  <div class="small-12 columns">
7
7
  <h2 class="heading2"><%= translated_attribute result.title %></h2>
8
8
 
9
- <div class="small-12 medium-8 column row end">
10
- <div class="section">
11
- <div class="description">
12
- <%== translated_attribute result.description %>
9
+ <div class="row">
10
+ <div class="mediumlarge-8 columns">
11
+ <div class="section">
12
+ <div class="description">
13
+ <%== translated_attribute result.description %>
14
+ </div>
15
+ <%= render partial: "decidim/shared/tags", locals: { resource: result, tags_class_extra: "tags--result" } %>
13
16
  </div>
14
- <%= render partial: "decidim/shared/tags", locals: { resource: result, tags_class_extra: "tags--result" } %>
15
17
  </div>
18
+
19
+ <%= render partial: "stats_box" %>
16
20
  </div>
17
21
 
18
22
  <%= render partial: "results_leaf", locals: { results: result.children.page(1).per(result.children_count), total_count: result.children_count } %>
@@ -1,31 +1,50 @@
1
- <div class="columns section view-side mediumlarge-4 large-3 end">
2
- <% if result.linked_resources(:proposals, "included_proposals").any? %>
1
+ <div class="columns section mediumlarge-4 large-3">
2
+ <% if result.versions.any? || result.linked_resources(:proposals, "included_proposals").any? %>
3
3
  <div class="card extra line-stats">
4
4
  <div class="definition-data">
5
- <div class="definition-data__item definition-data__item--double">
6
- <span class="definition-data__title"><%= t("results.show.stats.proposals", scope: "decidim.accountability") %></span>
7
- <span class="definition-data__number"><%= stats_calculator.proposals_count %></span>
8
- </div>
9
- <div class="definition-data__item definition-data__item--double">
10
- <span class="definition-data__title"><%= t("results.show.stats.meetings", scope: "decidim.accountability") %></span>
11
- <span class="definition-data__number"><%= stats_calculator.meetings_count %></span>
12
- </div>
13
- <div class="definition-data__item definition-data__item--double">
14
- <span class="definition-data__title"><%= t("results.show.stats.comments", scope: "decidim.accountability") %></span>
15
- <span class="definition-data__number"><%= stats_calculator.comments_count %></span>
16
- </div>
17
- <div class="definition-data__item definition-data__item--double">
18
- <span class="definition-data__title"><%= t("results.show.stats.attendees", scope: "decidim.accountability") %></span>
19
- <span class="definition-data__number"><%= stats_calculator.attendees_count %></span>
20
- </div>
21
- <div class="definition-data__item definition-data__item--double">
22
- <span class="definition-data__title"><%= t("results.show.stats.votes", scope: "decidim.accountability") %></span>
23
- <span class="definition-data__number"><%= stats_calculator.votes_count %></span>
24
- </div>
25
- <div class="definition-data__item definition-data__item--double">
26
- <span class="definition-data__title"><%= t("results.show.stats.contributions", scope: "decidim.accountability") %></span>
27
- <span class="definition-data__number"><%= stats_calculator.contributions_count %></span>
28
- </div>
5
+ <% if result.versions.any? %>
6
+ <div class="definition-data__item versions_count">
7
+ <span class="definition-data__title"><%= t("results.show.stats.version_number", scope: "decidim.accountability") %></span>
8
+ <%= result.versions.count %>
9
+ <%= link_to t("results.show.stats.show_all_versions", scope: "decidim.accountability"), result_versions_path(result), class: "button button--sc hollow secondary small expanded" %>
10
+ </div>
11
+ <% if result.last_editor.present? %>
12
+ <div class="definition-data__item last_revision_by">
13
+ <span class="definition-data__title"><%= t("results.show.stats.last_edited_by", scope: "decidim.accountability") %></span>
14
+ <%= render_resource_last_editor(result) %>
15
+ </div>
16
+ <% end %>
17
+ <div class="definition-data__item last_updated_at">
18
+ <span class="definition-data__title"><%= t("results.show.stats.last_updated_at", scope: "decidim.accountability") %></span>
19
+ <%= l result.versions.last.created_at, format: :decidim_short %>
20
+ </div>
21
+ <% end %>
22
+ <% if result.linked_resources(:proposals, "included_proposals").any? %>
23
+ <div class="definition-data__item definition-data__item--double">
24
+ <span class="definition-data__title"><%= t("results.show.stats.proposals", scope: "decidim.accountability") %></span>
25
+ <span class="definition-data__number"><%= stats_calculator.proposals_count %></span>
26
+ </div>
27
+ <div class="definition-data__item definition-data__item--double">
28
+ <span class="definition-data__title"><%= t("results.show.stats.meetings", scope: "decidim.accountability") %></span>
29
+ <span class="definition-data__number"><%= stats_calculator.meetings_count %></span>
30
+ </div>
31
+ <div class="definition-data__item definition-data__item--double">
32
+ <span class="definition-data__title"><%= t("results.show.stats.comments", scope: "decidim.accountability") %></span>
33
+ <span class="definition-data__number"><%= stats_calculator.comments_count %></span>
34
+ </div>
35
+ <div class="definition-data__item definition-data__item--double">
36
+ <span class="definition-data__title"><%= t("results.show.stats.attendees", scope: "decidim.accountability") %></span>
37
+ <span class="definition-data__number"><%= stats_calculator.attendees_count %></span>
38
+ </div>
39
+ <div class="definition-data__item definition-data__item--double">
40
+ <span class="definition-data__title"><%= t("results.show.stats.votes", scope: "decidim.accountability") %></span>
41
+ <span class="definition-data__number"><%= stats_calculator.votes_count %></span>
42
+ </div>
43
+ <div class="definition-data__item definition-data__item--double">
44
+ <span class="definition-data__title"><%= t("results.show.stats.contributions", scope: "decidim.accountability") %></span>
45
+ <span class="definition-data__number"><%= stats_calculator.contributions_count %></span>
46
+ </div>
47
+ <% end %>
29
48
  </div>
30
49
  </div>
31
50
  <% end %>
@@ -33,4 +52,4 @@
33
52
  <%= feature_reference(result) %>
34
53
  <%= render partial: "decidim/shared/share_modal" %>
35
54
  <%= embed_modal_for result_result_widget_url(result, format: :js) %>
36
- </div>
55
+ </div>
@@ -0,0 +1,20 @@
1
+ <div class="card--list__item">
2
+ <div class="card--list__text">
3
+ <div>
4
+ <%= link_to result_version_path(result, index + 1) do %>
5
+ <h6 class="card--list__heading heading6">
6
+ <%= t(".version_index", index: index + 1) %>
7
+ </h6>
8
+ <% end %>
9
+ <div class="author-data">
10
+ <%= render_resource_editor(version) %>
11
+ <%= l version.created_at, format: :decidim_short %>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ <div class="card--list__data">
16
+ <%= link_to result_version_path(result, index + 1), class: "card--list__data__icon" do %>
17
+ <%= icon "chevron-right" %>
18
+ <% end %>
19
+ </div>
20
+ </div>