decidim-admin 0.20.1 → 0.21.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -0
  3. data/app/assets/javascripts/decidim/admin/application.js.es6 +1 -0
  4. data/app/assets/javascripts/decidim/admin/bundle.js +5 -5
  5. data/app/assets/javascripts/decidim/admin/bundle.js.map +1 -1
  6. data/app/assets/javascripts/decidim/admin/newsletters.js.es6 +8 -0
  7. data/app/assets/stylesheets/decidim/admin/_variables.scss +1 -1
  8. data/app/assets/stylesheets/decidim/admin/extra/_action-icon.scss +6 -0
  9. data/app/assets/stylesheets/decidim/admin/extra/_cards.scss +11 -0
  10. data/app/assets/stylesheets/decidim/admin/modules/_buttons.scss +5 -0
  11. data/app/assets/stylesheets/decidim/admin/modules/_cards.scss +11 -0
  12. data/app/assets/stylesheets/decidim/admin/modules/_filters.scss +78 -1
  13. data/app/assets/stylesheets/decidim/admin/modules/_layout.scss +1 -1
  14. data/app/assets/stylesheets/decidim/admin/modules/_secondary-nav.scss +5 -1
  15. data/app/assets/stylesheets/decidim/admin/modules/_table-list.scss +24 -3
  16. data/app/cells/decidim/admin/results_per_page/show.erb +16 -0
  17. data/app/cells/decidim/admin/results_per_page_cell.rb +14 -0
  18. data/app/commands/decidim/admin/deliver_newsletter.rb +1 -1
  19. data/app/commands/decidim/admin/update_organization.rb +4 -1
  20. data/app/controllers/concerns/decidim/admin/filterable.rb +152 -0
  21. data/app/controllers/concerns/decidim/admin/officializations/filterable.rb +31 -0
  22. data/app/controllers/concerns/decidim/admin/paginable.rb +20 -0
  23. data/app/controllers/decidim/admin/admin_terms_controller.rb +20 -0
  24. data/app/controllers/decidim/admin/application_controller.rb +2 -0
  25. data/app/controllers/decidim/admin/components/base_controller.rb +5 -1
  26. data/app/controllers/decidim/admin/components_controller.rb +16 -20
  27. data/app/controllers/decidim/admin/concerns/has_private_users.rb +4 -0
  28. data/app/controllers/decidim/admin/newsletters_controller.rb +12 -1
  29. data/app/controllers/decidim/admin/officializations_controller.rb +7 -6
  30. data/app/forms/decidim/admin/organization_form.rb +7 -0
  31. data/app/helpers/decidim/admin/admin_terms_helper.rb +47 -0
  32. data/app/helpers/decidim/admin/application_helper.rb +1 -0
  33. data/app/helpers/decidim/admin/dashboard_helper.rb +25 -0
  34. data/app/helpers/decidim/admin/filterable_helper.rb +121 -0
  35. data/app/helpers/decidim/admin/newsletters_helper.rb +18 -0
  36. data/app/helpers/decidim/admin/paginable/per_page_helper.rb +22 -0
  37. data/app/helpers/decidim/admin/scopes_helper.rb +6 -0
  38. data/app/helpers/decidim/admin/settings_helper.rb +18 -2
  39. data/app/helpers/decidim/admin/user_roles_helper.rb +19 -0
  40. data/app/permissions/decidim/admin/permissions.rb +23 -6
  41. data/app/queries/decidim/admin/newsletter_recipients.rb +11 -4
  42. data/app/views/decidim/admin/admin_terms/show.html.erb +26 -0
  43. data/app/views/decidim/admin/components/_component.html.erb +35 -33
  44. data/app/views/decidim/admin/components/index.html.erb +10 -8
  45. data/app/views/decidim/admin/dashboard/show.html.erb +15 -0
  46. data/app/views/decidim/admin/newsletters/index.html.erb +9 -3
  47. data/app/views/decidim/admin/newsletters/select_recipients_to_deliver.html.erb +4 -4
  48. data/app/views/decidim/admin/officializations/index.html.erb +2 -38
  49. data/app/views/decidim/admin/organization/_form.html.erb +16 -0
  50. data/app/views/decidim/admin/shared/_filters.html.erb +40 -0
  51. data/app/views/layouts/decidim/admin/_application.html.erb +1 -1
  52. data/config/locales/ar.yml +40 -6
  53. data/config/locales/ca.yml +45 -6
  54. data/config/locales/cs.yml +45 -6
  55. data/config/locales/de.yml +9 -6
  56. data/config/locales/el.yml +1 -0
  57. data/config/locales/en.yml +45 -6
  58. data/config/locales/es-MX.yml +45 -6
  59. data/config/locales/es-PY.yml +45 -6
  60. data/config/locales/es.yml +45 -6
  61. data/config/locales/eu.yml +9 -6
  62. data/config/locales/fi-plain.yml +45 -6
  63. data/config/locales/fi.yml +45 -6
  64. data/config/locales/fr.yml +9 -6
  65. data/config/locales/gl.yml +9 -6
  66. data/config/locales/hu.yml +45 -6
  67. data/config/locales/id-ID.yml +9 -6
  68. data/config/locales/is-IS.yml +9 -6
  69. data/config/locales/it.yml +45 -6
  70. data/config/locales/nl.yml +28 -6
  71. data/config/locales/no.yml +45 -6
  72. data/config/locales/pl.yml +9 -6
  73. data/config/locales/pt-BR.yml +9 -6
  74. data/config/locales/pt.yml +9 -6
  75. data/config/locales/ru.yml +9 -6
  76. data/config/locales/sv.yml +9 -6
  77. data/config/locales/tr-TR.yml +9 -6
  78. data/config/locales/uk.yml +9 -6
  79. data/config/routes.rb +6 -0
  80. data/db/migrate/20191118112040_add_accepted_admin_terms_at_field_to_users.rb +7 -0
  81. data/lib/decidim/admin.rb +17 -0
  82. data/lib/decidim/admin/form_builder.rb +5 -0
  83. data/lib/decidim/admin/test.rb +2 -0
  84. data/lib/decidim/admin/test/filterable_examples.rb +129 -0
  85. data/lib/decidim/admin/test/manage_paginated_collection_examples.rb +22 -0
  86. data/lib/decidim/admin/version.rb +1 -1
  87. data/vendor/assets/javascripts/jquery.serializejson.js +344 -0
  88. metadata +26 -8
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Admin
7
+ module Officializations
8
+ module Filterable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Decidim::Admin::Filterable
13
+
14
+ private
15
+
16
+ def base_query
17
+ collection
18
+ end
19
+
20
+ def search_field_predicate
21
+ :name_or_nickname_or_email_cont
22
+ end
23
+
24
+ def filters
25
+ [:officialized_at_null]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Admin
7
+ module Paginable
8
+ # Common logic to paginate admin resources
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Decidim::Paginable
13
+
14
+ def per_page
15
+ params[:per_page].present? ? params[:per_page].to_i : Decidim::Admin.per_page_range.first
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Admin
5
+ # The controller to handle the Admin
6
+ # Terms of use agreement.
7
+ class AdminTermsController < Decidim::Admin::ApplicationController
8
+ def accept
9
+ current_user.admin_terms_accepted_at = Time.current
10
+ if current_user.save!
11
+ flash[:notice] = t("accept.success", scope: "decidim.admin.admin_terms_of_use")
12
+ redirect_to decidim_admin.root_path
13
+ else
14
+ flash[:alert] = t("accept.error", scope: "decidim.admin.admin_terms_of_use")
15
+ redirect_to decidim_admin.admin_terms_show_path
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -8,6 +8,7 @@ module Decidim
8
8
  include NeedsPermission
9
9
  include FormFactory
10
10
  include LocaleSwitcher
11
+ include UseOrganizationTimeZone
11
12
  include PayloadInfo
12
13
  include HttpCachingDisabler
13
14
 
@@ -17,6 +18,7 @@ module Decidim
17
18
  helper Decidim::Admin::IconLinkHelper
18
19
  helper Decidim::Admin::MenuHelper
19
20
  helper Decidim::Admin::ScopesHelper
21
+ helper Decidim::Admin::Paginable::PerPageHelper
20
22
  helper Decidim::DecidimFormHelper
21
23
  helper Decidim::ReplaceButtonsHelper
22
24
  helper Decidim::ScopesHelper
@@ -22,7 +22,7 @@ module Decidim
22
22
  :parent_path
23
23
 
24
24
  before_action except: [:index, :show] do
25
- enforce_permission_to :manage, :component, component: current_component
25
+ enforce_permission_to :manage, :component, component: current_component unless skip_manage_component_permission
26
26
  end
27
27
 
28
28
  before_action on: [:index, :show] do
@@ -59,6 +59,10 @@ module Decidim
59
59
  def parent_path
60
60
  @parent_path ||= ::Decidim::EngineRouter.admin_proxy(current_participatory_space).components_path
61
61
  end
62
+
63
+ def skip_manage_component_permission
64
+ false
65
+ end
62
66
  end
63
67
  end
64
68
  end
@@ -27,7 +27,7 @@ module Decidim
27
27
  end
28
28
 
29
29
  def create
30
- @form = form(ComponentForm).from_params(form_params)
30
+ @form = form(ComponentForm).from_params(component_params)
31
31
  enforce_permission_to :create, :component
32
32
 
33
33
  CreateComponent.call(@form) do
@@ -52,7 +52,7 @@ module Decidim
52
52
 
53
53
  def update
54
54
  @component = query_scope.find(params[:id])
55
- @form = form(ComponentForm).from_params(form_params)
55
+ @form = form(ComponentForm).from_params(component_params)
56
56
  enforce_permission_to :update, :component, component: @component
57
57
 
58
58
  UpdateComponent.call(@form, @component) do
@@ -113,28 +113,24 @@ module Decidim
113
113
 
114
114
  private
115
115
 
116
- # Returns a Class with the attributes sanitized, coerced and filtered
117
- # to the right type. See Decidim::SettingsManifest#schema.
118
- def new_settings_schema(name, data)
119
- manifest.settings(name).schema.new(data, current_organization.default_locale)
120
- end
121
-
122
116
  # Processes the component params so Decidim::Admin::ComponentForm
123
117
  # can assign and validate the attributes when using #from_params.
124
- def form_params
125
- form_params = params[:component].permit!
126
- form_params[:id] = params[:id]
127
- form_params[:manifest] = manifest
128
- form_params[:participatory_space] = current_participatory_space
129
- form_params[:settings] = new_settings_schema(:global, form_params[:settings])
130
- if form_params[:default_step_settings]
131
- form_params[:default_step_settings] = new_settings_schema(:step, form_params[:default_step_settings])
132
- else
133
- form_params[:step_settings].each do |key, value|
134
- form_params[:step_settings][key] = new_settings_schema(:step, value)
118
+ def component_params
119
+ new_settings = proc { |name, data| Component.build_settings(manifest, name, data, current_organization) }
120
+
121
+ params[:component].permit!.tap do |hsh|
122
+ hsh[:id] = params[:id]
123
+ hsh[:manifest] = manifest
124
+ hsh[:participatory_space] = current_participatory_space
125
+ hsh[:settings] = new_settings.call(:global, hsh[:settings])
126
+ if hsh[:default_step_settings]
127
+ hsh[:default_step_settings] = new_settings.call(:step, hsh[:default_step_settings])
128
+ else
129
+ hsh[:step_settings].each do |key, value|
130
+ hsh[:step_settings][key] = new_settings.call(:step, value)
131
+ end
135
132
  end
136
133
  end
137
- form_params
138
134
  end
139
135
 
140
136
  def query_scope
@@ -93,8 +93,12 @@ module Decidim
93
93
  end
94
94
 
95
95
  def collection
96
+ # there's an unidentified corner case where Decidim::User
97
+ # may have been destroyed, but the related ParticipatorySpacePrivateUser
98
+ # remains in the database. That's why filtering by not null users
96
99
  @collection ||= privatable_to
97
100
  .participatory_space_private_users
101
+ .includes(:user).where.not("decidim_users.id" => nil)
98
102
  .page(params[:page])
99
103
  .per(20)
100
104
  end
@@ -7,7 +7,7 @@ module Decidim
7
7
  include Decidim::NewslettersHelper
8
8
  include Decidim::Admin::NewslettersHelper
9
9
  include Paginable
10
- helper_method :newsletter
10
+ helper_method :newsletter, :recipients_count_query
11
11
 
12
12
  def index
13
13
  enforce_permission_to :index, :newsletter
@@ -96,6 +96,12 @@ module Decidim
96
96
  @form.send_to_all_users = current_user.admin?
97
97
  end
98
98
 
99
+ def recipients_count
100
+ data = params.permit(data: {}).to_h[:data]
101
+ @form = form(SelectiveNewsletterForm).from_params(data)
102
+ render plain: recipients_count_query
103
+ end
104
+
99
105
  def deliver
100
106
  enforce_permission_to :update, :newsletter, newsletter: newsletter
101
107
  @form = form(SelectiveNewsletterForm).from_params(params)
@@ -127,6 +133,11 @@ module Decidim
127
133
  def newsletter
128
134
  @newsletter ||= collection.find_by(id: params[:id])
129
135
  end
136
+
137
+ def recipients_count_query
138
+ @form ||= form(SelectiveNewsletterForm).instance
139
+ NewsletterRecipients.for(@form).size
140
+ end
130
141
  end
131
142
  end
132
143
  end
@@ -5,6 +5,8 @@ module Decidim
5
5
  # Controller that allows managing user officializations at the admin panel.
6
6
  #
7
7
  class OfficializationsController < Decidim::Admin::ApplicationController
8
+ include Decidim::Admin::Officializations::Filterable
9
+
8
10
  layout "decidim/admin/users"
9
11
 
10
12
  helper_method :user
@@ -12,12 +14,7 @@ module Decidim
12
14
 
13
15
  def index
14
16
  enforce_permission_to :read, :officialization
15
- @query = params[:q]
16
- @state = params[:state]
17
-
18
- @users = Decidim::Admin::UserFilter.for(current_organization.users.not_deleted, @query, @state)
19
- .page(params[:page])
20
- .per(15)
17
+ @users = filtered_collection
21
18
  end
22
19
 
23
20
  def new
@@ -54,6 +51,10 @@ module Decidim
54
51
 
55
52
  private
56
53
 
54
+ def collection
55
+ @collection ||= current_organization.users.not_deleted
56
+ end
57
+
57
58
  def user
58
59
  @user ||= Decidim::User.find_by(
59
60
  id: params[:user_id],
@@ -12,6 +12,7 @@ module Decidim
12
12
 
13
13
  attribute :name, String
14
14
  attribute :reference_prefix, String
15
+ attribute :time_zone, String
15
16
  attribute :twitter_handler, String
16
17
  attribute :facebook_handler, String
17
18
  attribute :instagram_handler, String
@@ -20,6 +21,7 @@ module Decidim
20
21
  attribute :default_locale, String
21
22
  attribute :badges_enabled, Boolean
22
23
  attribute :user_groups_enabled, Boolean
24
+ attribute :rich_text_editor_in_public_views, Boolean
23
25
 
24
26
  attribute :send_welcome_notification, Boolean
25
27
  attribute :customize_welcome_notification, Boolean
@@ -27,11 +29,16 @@ module Decidim
27
29
  translatable_attribute :welcome_notification_subject, String
28
30
  translatable_attribute :welcome_notification_body, String
29
31
 
32
+ translatable_attribute :admin_terms_of_use_body, String
33
+
30
34
  validates :welcome_notification_subject, :welcome_notification_body, translatable_presence: true, if: proc { |form| form.customize_welcome_notification }
31
35
 
32
36
  validates :name, presence: true
37
+ validates :time_zone, presence: true
38
+ validates :time_zone, time_zone: true
33
39
  validates :default_locale, :reference_prefix, presence: true
34
40
  validates :default_locale, inclusion: { in: :available_locales }
41
+ validates :admin_terms_of_use_body, translatable_presence: true
35
42
 
36
43
  private
37
44
 
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Admin
5
+ # This module includes helpers to show Admin Terms of Use
6
+ module AdminTermsHelper
7
+ def admin_terms_of_use_body
8
+ current_organization.admin_terms_of_use_body.symbolize_keys[I18n.locale].html_safe
9
+ end
10
+
11
+ def admin_terms_announcement_args
12
+ {
13
+ callout_class: current_user.admin_terms_accepted? ? "success" : "warning",
14
+ announcement: announcement_body
15
+ }
16
+ end
17
+
18
+ def announcement_body
19
+ if current_user.admin_terms_accepted?
20
+ t("accept.success", scope: "decidim.admin.admin_terms_of_use")
21
+ else
22
+ t("required_review.callout", scope: "decidim.admin.admin_terms_of_use")
23
+ end
24
+ end
25
+
26
+ def button_to_accept_admin_terms
27
+ button_to(
28
+ t("decidim.admin.admin_terms_of_use.actions.accept"),
29
+ admin_terms_accept_path,
30
+ class: "button success",
31
+ method: :put
32
+ )
33
+ end
34
+
35
+ def button_to_refuse_admin_terms
36
+ link_to(
37
+ t("decidim.admin.admin_terms_of_use.actions.refuse"),
38
+ decidim.root_path,
39
+ class: "button clear",
40
+ data: {
41
+ confirm: t("actions.are_you_sure", scope: "decidim.admin.admin_terms_of_use")
42
+ }
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -12,6 +12,7 @@ module Decidim
12
12
  include Decidim::MetaTagsHelper
13
13
  include Decidim::MapHelper
14
14
  include Decidim::Admin::LogRenderHelper
15
+ include Decidim::Admin::UserRolesHelper
15
16
 
16
17
  def title
17
18
  current_organization.name
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Admin
5
+ # This module includes helpers to be used in the admin dashboard, including helper methods to show the Admin Terms of Use.
6
+ module DashboardHelper
7
+ def admin_terms_announcement_args
8
+ {
9
+ callout_class: "warning",
10
+ announcement: announcement_body
11
+ }
12
+ end
13
+
14
+ def announcement_body
15
+ body = t("required_review.callout", scope: "decidim.admin.admin_terms_of_use")
16
+ body += " "
17
+ body += link_to(
18
+ t("required_review.cta", scope: "decidim.admin.admin_terms_of_use"),
19
+ admin_terms_show_path
20
+ )
21
+ body
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Admin
5
+ # Helper that provides methods related to Decidim::Admin::Filterable concern.
6
+ module FilterableHelper
7
+ # Renders the filters selector with tags in the admin panel.
8
+ def admin_filter_selector
9
+ render partial: "decidim/admin/shared/filters"
10
+ end
11
+
12
+ # Builds a tree of links from Decidim::Admin::Filterable::filters_with_values
13
+ def submenu_options_tree
14
+ filters_with_values.each_with_object({}) do |(filter, values), hash|
15
+ link = filter_link_label(filter)
16
+ hash[link] = if values.is_a?(Array)
17
+ build_submenu_options_tree_from_array(filter, values)
18
+ elsif values.is_a?(Hash)
19
+ build_submenu_options_tree_from_hash(filter, values)
20
+ end
21
+ end
22
+ end
23
+
24
+ # Builds a tree of links from an array. The tree will have only one level.
25
+ def build_submenu_options_tree_from_array(filter, values)
26
+ links = []
27
+ links += extra_dropdown_submenu_options_items(filter)
28
+ links += values.map { |value| filter_link_value(filter, value) }
29
+ links.each_with_object({}) { |link, hash| hash[link] = nil }
30
+ end
31
+
32
+ # To be overriden. Useful for adding links that do not match with the filter.
33
+ # Must return an Array.
34
+ def extra_dropdown_submenu_options_items(_filter)
35
+ []
36
+ end
37
+
38
+ # Builds a tree of links from an Hash. The tree can have many levels.
39
+ def build_submenu_options_tree_from_hash(filter, values)
40
+ values.each_with_object({}) do |(key, value), hash|
41
+ link = filter_link_value(filter, key)
42
+ hash[link] = if value.nil?
43
+ nil
44
+ elsif value.is_a?(Hash)
45
+ build_submenu_options_tree_from_hash(filter, value)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Produces the html for the dropdown submenu from the options tree.
51
+ # Returns a ActiveSupport::SafeBuffer.
52
+ def dropdown_submenu(options)
53
+ content_tag(:ul, class: "vertical menu") do
54
+ options.map do |key, value|
55
+ if value.nil?
56
+ content_tag(:li, key)
57
+ elsif value.is_a?(Hash)
58
+ content_tag(:li, class: "is-dropdown-submenu-parent") do
59
+ key + dropdown_submenu(value)
60
+ end
61
+ end
62
+ end.join.html_safe
63
+ end
64
+ end
65
+
66
+ def filter_link_label(filter)
67
+ link_to(i18n_filter_label(filter), href: "#")
68
+ end
69
+
70
+ def filter_link_value(filter, value)
71
+ link_to(i18n_filter_value(filter, value), query_params_with(filter => value))
72
+ end
73
+
74
+ def i18n_filter_label(filter)
75
+ t("decidim.admin.filters.#{filter}.label")
76
+ end
77
+
78
+ def i18n_filter_value(filter, value)
79
+ if I18n.exists?("decidim.admin.filters.#{filter}.values.#{value}")
80
+ t(value, scope: "decidim.admin.filters.#{filter}.values")
81
+ else
82
+ find_dynamic_translation(filter, value)
83
+ end
84
+ end
85
+
86
+ def applied_filters_hidden_field_tags
87
+ html = []
88
+ html += ransack_params.slice(*filters, *extra_filters).map do |filter, value|
89
+ hidden_field_tag("q[#{filter}]", value)
90
+ end
91
+ html += query_params.slice(*extra_allowed_params).map do |filter, value|
92
+ hidden_field_tag(filter, value)
93
+ end
94
+ html.join.html_safe
95
+ end
96
+
97
+ def applied_filters_tags
98
+ ransack_params.slice(*filters).map do |filter, value|
99
+ applied_filter_tag(filter, value)
100
+ end.join.html_safe
101
+ end
102
+
103
+ def applied_filter_tag(filter, value)
104
+ content_tag(:span, class: "label secondary") do
105
+ concat "#{i18n_filter_label(filter)}: "
106
+ concat i18n_filter_value(filter, value)
107
+ concat remove_filter_icon_link(filter)
108
+ end
109
+ end
110
+
111
+ def remove_filter_icon_link(filter)
112
+ icon_link_to(
113
+ "circle-x",
114
+ url_for(query_params_without(filter)),
115
+ t("decidim.admin.actions.cancel"),
116
+ class: "action-icon--remove"
117
+ )
118
+ end
119
+ end
120
+ end
121
+ end