decidim-plans 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +157 -0
- data/Rakefile +48 -0
- data/app/assets/config/admin/decidim_plans_manifest.js +3 -0
- data/app/assets/config/decidim_plans_manifest.css +4 -0
- data/app/assets/config/decidim_plans_manifest.js +2 -0
- data/app/assets/images/decidim/plans/icon.svg +3 -0
- data/app/assets/javascripts/decidim/plans/admin/plans.js.es6 +3 -0
- data/app/assets/javascripts/decidim/plans/admin/sections.js.es6 +78 -0
- data/app/assets/javascripts/decidim/plans/multifield/component.js.es6 +101 -0
- data/app/assets/javascripts/decidim/plans/multifield.js.es6 +5 -0
- data/app/assets/javascripts/decidim/plans/plans_form.js.es6 +9 -0
- data/app/assets/javascripts/decidim/plans/proposal_picker.js.es6 +41 -0
- data/app/assets/javascripts/decidim/plans/remaining_characters.es6 +42 -0
- data/app/assets/javascripts/decidim/plans/social_share.js +2 -0
- data/app/assets/javascripts/decidim/plans/tab_focus.es6 +20 -0
- data/app/assets/stylesheets/decidim/plans/plans_form.scss +26 -0
- data/app/assets/stylesheets/decidim/plans/proposal_picker.scss +3 -0
- data/app/assets/stylesheets/decidim/plans/social_share.css.scss +22 -0
- data/app/cells/decidim/plans/author/flag.erb +5 -0
- data/app/cells/decidim/plans/author/withdraw.erb +6 -0
- data/app/cells/decidim/plans/author_cell.rb +9 -0
- data/app/cells/decidim/plans/coauthorships_cell.rb +23 -0
- data/app/cells/decidim/plans/collapsible_authors/show.erb +17 -0
- data/app/cells/decidim/plans/collapsible_authors_cell.rb +9 -0
- data/app/cells/decidim/plans/plan_activity_cell.rb +19 -0
- data/app/cells/decidim/plans/plan_cell.rb +59 -0
- data/app/cells/decidim/plans/plan_m/footer.erb +8 -0
- data/app/cells/decidim/plans/plan_m_cell.rb +62 -0
- data/app/commands/decidim/plans/accept_access_to_plan.rb +53 -0
- data/app/commands/decidim/plans/admin/answer_plan.rb +82 -0
- data/app/commands/decidim/plans/admin/create_plan.rb +102 -0
- data/app/commands/decidim/plans/admin/update_plan.rb +98 -0
- data/app/commands/decidim/plans/admin/update_sections.rb +54 -0
- data/app/commands/decidim/plans/attachment_methods.rb +78 -0
- data/app/commands/decidim/plans/create_plan.rb +90 -0
- data/app/commands/decidim/plans/destroy_plan.rb +33 -0
- data/app/commands/decidim/plans/nested_updater.rb +26 -0
- data/app/commands/decidim/plans/publish_plan.rb +79 -0
- data/app/commands/decidim/plans/reject_access_to_plan.rb +46 -0
- data/app/commands/decidim/plans/request_access_to_plan.rb +50 -0
- data/app/commands/decidim/plans/respond_to_access_request.rb +76 -0
- data/app/commands/decidim/plans/update_plan.rb +90 -0
- data/app/commands/decidim/plans/withdraw_plan.rb +50 -0
- data/app/controllers/concerns/decidim/plans/orderable.rb +53 -0
- data/app/controllers/decidim/plans/admin/application_controller.rb +16 -0
- data/app/controllers/decidim/plans/admin/plan_answers_controller.rb +40 -0
- data/app/controllers/decidim/plans/admin/plans_controller.rb +80 -0
- data/app/controllers/decidim/plans/admin/sections_controller.rb +75 -0
- data/app/controllers/decidim/plans/application_controller.rb +30 -0
- data/app/controllers/decidim/plans/plan_collaborator_requests_controller.rb +53 -0
- data/app/controllers/decidim/plans/plan_widgets_controller.rb +19 -0
- data/app/controllers/decidim/plans/plans_controller.rb +192 -0
- data/app/controllers/decidim/plans/versions_controller.rb +51 -0
- data/app/events/decidim/plans/accepted_plan_event.rb +9 -0
- data/app/events/decidim/plans/evaluating_plan_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_accepted_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_rejected_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_request_event.rb +27 -0
- data/app/events/decidim/plans/plan_access_requested_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_requester_accepted_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_requester_rejected_event.rb +8 -0
- data/app/events/decidim/plans/publish_plan_event.rb +25 -0
- data/app/events/decidim/plans/rejected_plan_event.rb +9 -0
- data/app/forms/decidim/plans/accept_access_to_plan_form.rb +10 -0
- data/app/forms/decidim/plans/access_to_plan_form.rb +35 -0
- data/app/forms/decidim/plans/admin/plan_answer_form.rb +19 -0
- data/app/forms/decidim/plans/admin/plan_form.rb +88 -0
- data/app/forms/decidim/plans/admin/plan_sections_form.rb +20 -0
- data/app/forms/decidim/plans/admin/section_form.rb +34 -0
- data/app/forms/decidim/plans/attachment_form.rb +20 -0
- data/app/forms/decidim/plans/content_form.rb +58 -0
- data/app/forms/decidim/plans/plan_form.rb +85 -0
- data/app/forms/decidim/plans/reject_access_to_plan_form.rb +9 -0
- data/app/forms/decidim/plans/request_access_to_plan_form.rb +20 -0
- data/app/helpers/decidim/plans/admin/application_helper.rb +19 -0
- data/app/helpers/decidim/plans/application_helper.rb +78 -0
- data/app/helpers/decidim/plans/attached_proposals_helper.rb +44 -0
- data/app/helpers/decidim/plans/attachments_helper.rb +59 -0
- data/app/helpers/decidim/plans/cells_helper.rb +26 -0
- data/app/helpers/decidim/plans/plan_cells_helper.rb +67 -0
- data/app/helpers/decidim/plans/remaining_characters_helper.rb +32 -0
- data/app/helpers/decidim/plans/traceability_helper.rb +79 -0
- data/app/helpers/decidim/plans/user_group_helper.rb +23 -0
- data/app/models/concerns/decidim/plans/traceable.rb +48 -0
- data/app/models/decidim/plans/application_record.rb +10 -0
- data/app/models/decidim/plans/attached_proposal.rb +12 -0
- data/app/models/decidim/plans/content.rb +21 -0
- data/app/models/decidim/plans/paper_trail/version.rb +26 -0
- data/app/models/decidim/plans/paper_trail/version_association.rb +14 -0
- data/app/models/decidim/plans/plan.rb +206 -0
- data/app/models/decidim/plans/plan_collaborator_request.rb +13 -0
- data/app/models/decidim/plans/section.rb +16 -0
- data/app/permissions/decidim/plans/admin/permissions.rb +44 -0
- data/app/permissions/decidim/plans/permissions.rb +67 -0
- data/app/presenters/decidim/plans/admin_log/plan_presenter.rb +49 -0
- data/app/presenters/decidim/plans/admin_log/value_types/plan_state_presenter.rb +16 -0
- data/app/presenters/decidim/plans/content_presenter.rb +25 -0
- data/app/presenters/decidim/plans/log/resource_presenter.rb +18 -0
- data/app/presenters/decidim/plans/official_author_presenter.rb +38 -0
- data/app/presenters/decidim/plans/plan_presenter.rb +48 -0
- data/app/queries/decidim/plans/filtered_plans.rb +37 -0
- data/app/services/decidim/plans/diff_renderer/base.rb +108 -0
- data/app/services/decidim/plans/diff_renderer/categorization.rb +23 -0
- data/app/services/decidim/plans/diff_renderer/content.rb +33 -0
- data/app/services/decidim/plans/diff_renderer/plan.rb +25 -0
- data/app/services/decidim/plans/loggability.rb +19 -0
- data/app/services/decidim/plans/plan_builder.rb +68 -0
- data/app/services/decidim/plans/plan_search.rb +95 -0
- data/app/services/decidim/plans/tracer.rb +29 -0
- data/app/views/decidim/plans/admin/plan_answers/edit.html.erb +22 -0
- data/app/views/decidim/plans/admin/plans/_bulk-actions.html.erb +9 -0
- data/app/views/decidim/plans/admin/plans/_form.html.erb +62 -0
- data/app/views/decidim/plans/admin/plans/_js-callout.html.erb +6 -0
- data/app/views/decidim/plans/admin/plans/_plan-tr.html.erb +53 -0
- data/app/views/decidim/plans/admin/plans/edit.html.erb +7 -0
- data/app/views/decidim/plans/admin/plans/index.html.erb +64 -0
- data/app/views/decidim/plans/admin/plans/new.html.erb +7 -0
- data/app/views/decidim/plans/admin/sections/_bulk-actions.html.erb +7 -0
- data/app/views/decidim/plans/admin/sections/_form.html.erb +27 -0
- data/app/views/decidim/plans/admin/sections/_section.html.erb +66 -0
- data/app/views/decidim/plans/admin/sections/index.html.erb +7 -0
- data/app/views/decidim/plans/admin/shared/_info_plan.html.erb +17 -0
- data/app/views/decidim/plans/attached_proposals/_proposals.html.erb +12 -0
- data/app/views/decidim/plans/plan_widgets/show.html.erb +3 -0
- data/app/views/decidim/plans/plans/_accept_request_access_form.html.erb +8 -0
- data/app/views/decidim/plans/plans/_attached_proposals.html.erb +10 -0
- data/app/views/decidim/plans/plans/_collaborator_requests.html.erb +20 -0
- data/app/views/decidim/plans/plans/_content.html.erb +6 -0
- data/app/views/decidim/plans/plans/_contents.html.erb +8 -0
- data/app/views/decidim/plans/plans/_count.html.erb +1 -0
- data/app/views/decidim/plans/plans/_filters.html.erb +32 -0
- data/app/views/decidim/plans/plans/_filters_small_view.html.erb +18 -0
- data/app/views/decidim/plans/plans/_form.html.erb +57 -0
- data/app/views/decidim/plans/plans/_new_plan_button.html.erb +11 -0
- data/app/views/decidim/plans/plans/_plan.html.erb +1 -0
- data/app/views/decidim/plans/plans/_plan_preview.html.erb +1 -0
- data/app/views/decidim/plans/plans/_plans.html.erb +14 -0
- data/app/views/decidim/plans/plans/_reject_request_access_form.html.erb +10 -0
- data/app/views/decidim/plans/plans/_request_access_form.html.erb +7 -0
- data/app/views/decidim/plans/plans/edit.html.erb +21 -0
- data/app/views/decidim/plans/plans/index.html.erb +28 -0
- data/app/views/decidim/plans/plans/index.js.erb +10 -0
- data/app/views/decidim/plans/plans/new.html.erb +15 -0
- data/app/views/decidim/plans/plans/preview.html.erb +15 -0
- data/app/views/decidim/plans/plans/show.html.erb +133 -0
- data/app/views/decidim/plans/shared/_attachment_fields.html.erb +45 -0
- data/app/views/decidim/plans/shared/_attachments.html.erb +23 -0
- data/app/views/decidim/plans/shared/_remaining_characters_container.html.erb +12 -0
- data/app/views/decidim/plans/shared/_section_content_field.html.erb +39 -0
- data/app/views/decidim/plans/versions/_version.html.erb +20 -0
- data/app/views/decidim/plans/versions/index.html.erb +34 -0
- data/app/views/decidim/plans/versions/show.html.erb +41 -0
- data/config/locales/en.yml +341 -0
- data/config/locales/fi.yml +341 -0
- data/config/locales/sv.yml +341 -0
- data/db/migrate/20181230110225_enable_pg_trgm_extension_for_plans.rb +18 -0
- data/db/migrate/20181230110848_create_decidim_plans.rb +20 -0
- data/db/migrate/20181230111731_create_decidim_plans_sections.rb +13 -0
- data/db/migrate/20181230111931_create_decidim_plans_plan_contents.rb +14 -0
- data/db/migrate/20181230122035_create_decidim_plans_plan_collaborator_requests.rb +12 -0
- data/db/migrate/20190110125855_create_decidim_plans_attached_proposals.rb +12 -0
- data/db/migrate/20190129140441_add_help_to_decidim_plans_sections.rb +7 -0
- data/db/migrate/20190129143607_add_mandatory_to_decidim_plans_sections.rb +7 -0
- data/db/migrate/20190129172040_add_section_type_to_decidim_plans_sections.rb +12 -0
- data/db/migrate/20190129231439_add_answer_length_to_decidim_plans_sections.rb +11 -0
- data/db/migrate/20190202123044_create_version_associations.rb +25 -0
- data/db/migrate/20190202123045_add_transaction_id_column_to_versions.rb +15 -0
- data/db/migrate/20190202200716_add_update_token_to_decidim_plans.rb +7 -0
- data/lib/decidim/plans/admin.rb +10 -0
- data/lib/decidim/plans/admin_engine.rb +29 -0
- data/lib/decidim/plans/component.rb +163 -0
- data/lib/decidim/plans/engine.rb +43 -0
- data/lib/decidim/plans/locale_aware.rb +32 -0
- data/lib/decidim/plans/optionally_translatable_attributes.rb +85 -0
- data/lib/decidim/plans/paper_trail/record_trail.rb +24 -0
- data/lib/decidim/plans/paper_trail.rb +10 -0
- data/lib/decidim/plans/test/factories.rb +130 -0
- data/lib/decidim/plans/version.rb +8 -0
- data/lib/decidim/plans.rb +24 -0
- metadata +323 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Log
|
6
|
+
class ResourcePresenter < Decidim::Log::ResourcePresenter
|
7
|
+
private
|
8
|
+
|
9
|
+
# Private: Presents resource name.
|
10
|
+
#
|
11
|
+
# Returns an HTML-safe String.
|
12
|
+
def present_resource_name
|
13
|
+
Decidim::Plans::PlanPresenter.new(resource).title if resource
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
#
|
6
|
+
# A dummy presenter to abstract out the author of an official plan.
|
7
|
+
#
|
8
|
+
class OfficialAuthorPresenter
|
9
|
+
def name
|
10
|
+
I18n.t("decidim.plans.models.plan.fields.official_plan")
|
11
|
+
end
|
12
|
+
|
13
|
+
def nickname
|
14
|
+
""
|
15
|
+
end
|
16
|
+
|
17
|
+
def badge
|
18
|
+
""
|
19
|
+
end
|
20
|
+
|
21
|
+
def profile_path
|
22
|
+
""
|
23
|
+
end
|
24
|
+
|
25
|
+
def avatar_url
|
26
|
+
ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
|
27
|
+
end
|
28
|
+
|
29
|
+
def deleted?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def can_be_contacted?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
#
|
6
|
+
# Decorator for plans
|
7
|
+
#
|
8
|
+
class PlanPresenter < SimpleDelegator
|
9
|
+
include Rails.application.routes.mounted_helpers
|
10
|
+
include ActionView::Helpers::UrlHelper
|
11
|
+
include TranslatableAttributes
|
12
|
+
|
13
|
+
def author
|
14
|
+
coauthorship = coauthorships.first
|
15
|
+
@author ||= if coauthorship.user_group
|
16
|
+
Decidim::UserGroupPresenter.new(coauthorship.user_group)
|
17
|
+
else
|
18
|
+
Decidim::UserPresenter.new(coauthorship.author)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def plan
|
23
|
+
__getobj__
|
24
|
+
end
|
25
|
+
|
26
|
+
def plan_path
|
27
|
+
Decidim::ResourceLocatorPresenter.new(plan).path
|
28
|
+
end
|
29
|
+
|
30
|
+
def title
|
31
|
+
translated_attribute(plan.title).html_safe
|
32
|
+
end
|
33
|
+
|
34
|
+
def body
|
35
|
+
fields = plan.sections.map do |section|
|
36
|
+
content = plan.contents.find_by(section: section)
|
37
|
+
next if content.nil?
|
38
|
+
|
39
|
+
title = translated_attribute(content.title)
|
40
|
+
body = translated_attribute(content.body)
|
41
|
+
"<dt>#{title}</dt> <dd>#{body}</dd>"
|
42
|
+
end
|
43
|
+
|
44
|
+
"<dl>#{fields.join("\n")}</dl>".html_safe
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A class used to find plans filtered by components and a date range
|
6
|
+
class FilteredPlans < Rectify::Query
|
7
|
+
# Syntactic sugar to initialize the class and return the queried objects.
|
8
|
+
#
|
9
|
+
# components - An array of Decidim::Component
|
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(components, start_at = nil, end_at = nil)
|
13
|
+
new(components, start_at, end_at).query
|
14
|
+
end
|
15
|
+
|
16
|
+
# Initializes the class.
|
17
|
+
#
|
18
|
+
# components - An array of Decidim::Component
|
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(components, start_at = nil, end_at = nil)
|
22
|
+
@components = components
|
23
|
+
@start_at = start_at
|
24
|
+
@end_at = end_at
|
25
|
+
end
|
26
|
+
|
27
|
+
# Finds the Plans scoped to an array of components and filtered
|
28
|
+
# by a range of dates.
|
29
|
+
def query
|
30
|
+
plans = Decidim::Plans::Plan.where(component: @components)
|
31
|
+
plans = plans.where("created_at >= ?", @start_at) if @start_at.present?
|
32
|
+
plans = plans.where("created_at <= ?", @end_at) if @end_at.present?
|
33
|
+
plans
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module DiffRenderer
|
6
|
+
class Base
|
7
|
+
include ::Decidim::ApplicationHelper
|
8
|
+
|
9
|
+
def initialize(version, locale)
|
10
|
+
@version = version
|
11
|
+
@display_locale = locale
|
12
|
+
end
|
13
|
+
|
14
|
+
# Renders the diff of the given changeset. Doesn't take into account
|
15
|
+
# translatable fields.
|
16
|
+
#
|
17
|
+
# Returns a Hash, where keys are the fields that have changed and values
|
18
|
+
# are an array, the first element being the previous value and the last
|
19
|
+
# being the new one.
|
20
|
+
def diff
|
21
|
+
version.changeset.inject({}) do |diff, (attribute, values)|
|
22
|
+
attribute = attribute.to_sym
|
23
|
+
type = attribute_types[attribute]
|
24
|
+
|
25
|
+
if type.blank?
|
26
|
+
diff
|
27
|
+
elsif [:i18n, :i18n_html].include?(type)
|
28
|
+
parse_i18n_changeset(attribute, values, type, diff)
|
29
|
+
else
|
30
|
+
parse_changeset(attribute, values, type, diff)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
attr_reader :version, :display_locale
|
38
|
+
|
39
|
+
def i18n_scope
|
40
|
+
"activemodel.attributes"
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_label(attribute)
|
44
|
+
I18n.t(attribute, scope: i18n_scope)
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate_i18n_label(attribute, locale)
|
48
|
+
label = I18n.t(attribute, scope: i18n_scope)
|
49
|
+
return label if display_locale
|
50
|
+
"#{label} (#{locale_name(locale)})"
|
51
|
+
end
|
52
|
+
|
53
|
+
def locale_name(locale)
|
54
|
+
if I18n.available_locales.include?(locale.to_sym)
|
55
|
+
I18n.t("locale.name", locale: locale)
|
56
|
+
else
|
57
|
+
locale
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def translated_attribute(attribute, locale = nil)
|
62
|
+
return "" if attribute.nil?
|
63
|
+
return attribute unless attribute.is_a?(Hash)
|
64
|
+
|
65
|
+
attribute = attribute.dup.stringify_keys
|
66
|
+
locale ||= I18n.locale.to_s
|
67
|
+
|
68
|
+
attribute[locale].presence ||
|
69
|
+
attribute[I18n.locale.to_s].presence ||
|
70
|
+
attribute[attribute.keys.first].presence ||
|
71
|
+
""
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_changeset(attribute, values, type, diff)
|
75
|
+
diff.update(
|
76
|
+
attribute => {
|
77
|
+
type: type,
|
78
|
+
label: generate_label(attribute),
|
79
|
+
old_value: values[0],
|
80
|
+
new_value: values[1]
|
81
|
+
}
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_i18n_changeset(attribute, values, type, diff)
|
86
|
+
values.last.each_key do |locale, _value|
|
87
|
+
next if display_locale && display_locale != locale
|
88
|
+
|
89
|
+
first_value = values.first.try(:[], locale) if values.first.is_a? Hash
|
90
|
+
last_value = values.last.try(:[], locale) if values.last.is_a? Hash
|
91
|
+
next if first_value == last_value
|
92
|
+
|
93
|
+
attribute_locale = "#{attribute}_#{locale}".to_sym
|
94
|
+
diff.update(
|
95
|
+
attribute_locale => {
|
96
|
+
type: :string,
|
97
|
+
label: generate_i18n_label(attribute, locale),
|
98
|
+
old_value: first_value,
|
99
|
+
new_value: last_value
|
100
|
+
}
|
101
|
+
)
|
102
|
+
end
|
103
|
+
diff
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module DiffRenderer
|
6
|
+
class Categorization < Base
|
7
|
+
protected
|
8
|
+
|
9
|
+
def i18n_scope
|
10
|
+
"activemodel.attributes.plan"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Lists which attributes will be diffable and how
|
14
|
+
# they should be rendered.
|
15
|
+
def attribute_types
|
16
|
+
{
|
17
|
+
decidim_category_id: :category
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module DiffRenderer
|
6
|
+
class Content < Base
|
7
|
+
protected
|
8
|
+
|
9
|
+
def i18n_scope
|
10
|
+
"activemodel.attributes.plan"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Lists which attributes will be diffable and how
|
14
|
+
# they should be rendered.
|
15
|
+
def attribute_types
|
16
|
+
{
|
17
|
+
body: :i18n
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_label(_attribute)
|
22
|
+
translated_attribute(version.item.section.body)
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_i18n_label(_attribute, locale)
|
26
|
+
label = translated_attribute(version.item.section.body, locale)
|
27
|
+
return label if display_locale
|
28
|
+
"#{label} (#{locale_name(locale)})"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module DiffRenderer
|
6
|
+
class Plan < Base
|
7
|
+
protected
|
8
|
+
|
9
|
+
def i18n_scope
|
10
|
+
"activemodel.attributes.plan"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Lists which attributes will be diffable and how
|
14
|
+
# they should be rendered.
|
15
|
+
def attribute_types
|
16
|
+
{
|
17
|
+
title: :i18n,
|
18
|
+
decidim_scope_id: :scope,
|
19
|
+
state: :string
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# The loggability class modifies the core's Traceability class by removing
|
6
|
+
# the PaperTrail tracing from the objects. This is needed because we want to
|
7
|
+
# handle the PaperTrail tracing manually for the plans.
|
8
|
+
class Loggability < ::Decidim::Traceability
|
9
|
+
def perform_action!(action, resource, author, extra_log_info = {})
|
10
|
+
Decidim::ApplicationRecord.transaction do
|
11
|
+
result = block_given? ? yield : nil
|
12
|
+
loggable_resource = resource.is_a?(Class) ? result : resource
|
13
|
+
log(action, author, loggable_resource, extra_log_info)
|
14
|
+
return result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A factory class to ensure we always create Plans the same way since it involves some logic.
|
6
|
+
module PlanBuilder
|
7
|
+
# Public: Creates a new Plan.
|
8
|
+
#
|
9
|
+
# attributes - The Hash of attributes to create the Plan with.
|
10
|
+
# author - An Authorable the will be the first coauthor of the Plan.
|
11
|
+
# user_group_author - A User Group to, optionally, set it as the author too.
|
12
|
+
# action_user - The User to be used as the user who is creating the plan in the traceability logs.
|
13
|
+
#
|
14
|
+
# Returns a Plan.
|
15
|
+
def create(attributes:, author:, action_user:, user_group_author: nil)
|
16
|
+
Decidim.traceability.perform_action!(:create, Plan, action_user, visibility: "all") do
|
17
|
+
plan = Plan.new(attributes)
|
18
|
+
plan.add_coauthor(author, user_group: user_group_author)
|
19
|
+
plan.save!
|
20
|
+
plan
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module_function :create
|
25
|
+
|
26
|
+
# Public: Creates a new Plan by copying the attributes from another one.
|
27
|
+
#
|
28
|
+
# original_plan - The Plan to be used as base to create the new one.
|
29
|
+
# author - An Authorable the will be the first coauthor of the Plan.
|
30
|
+
# user_group_author - A User Group to, optionally, set it as the author too.
|
31
|
+
# action_user - The User to be used as the user who is creating the plan in the traceability logs.
|
32
|
+
# extra_attributes - A Hash of attributes to create the new plan, will overwrite the original ones.
|
33
|
+
# skip_link - Whether to skip linking the two plans or not (default false).
|
34
|
+
#
|
35
|
+
# Returns a Plan
|
36
|
+
#
|
37
|
+
# rubocop:disable Metrics/ParameterLists
|
38
|
+
def copy(original_plan, author:, action_user:, user_group_author: nil, extra_attributes: {}, skip_link: false)
|
39
|
+
origin_attributes = original_plan.attributes.except(
|
40
|
+
"id",
|
41
|
+
"created_at",
|
42
|
+
"updated_at",
|
43
|
+
"state",
|
44
|
+
"answer",
|
45
|
+
"answered_at",
|
46
|
+
"decidim_component_id"
|
47
|
+
).merge(
|
48
|
+
"category" => original_plan.category
|
49
|
+
).merge(
|
50
|
+
extra_attributes
|
51
|
+
)
|
52
|
+
|
53
|
+
plan = create(
|
54
|
+
attributes: origin_attributes,
|
55
|
+
author: author,
|
56
|
+
user_group_author: user_group_author,
|
57
|
+
action_user: action_user
|
58
|
+
)
|
59
|
+
|
60
|
+
plan.link_resources(original_plan, "copied_from_component") unless skip_link
|
61
|
+
plan
|
62
|
+
end
|
63
|
+
# rubocop:enable Metrics/ParameterLists
|
64
|
+
|
65
|
+
module_function :copy
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A service to encapsualte all the logic when searching and filtering
|
6
|
+
# plans in a participatory process.
|
7
|
+
class PlanSearch < ResourceSearch
|
8
|
+
# Public: Initializes the service.
|
9
|
+
# component - A Decidim::Component to get the plans from.
|
10
|
+
# page - The page number to paginate the results.
|
11
|
+
# per_page - The number of plans to return per page.
|
12
|
+
def initialize(options = {})
|
13
|
+
super(Plan.all, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Handle the search_text filter
|
17
|
+
def search_search_text
|
18
|
+
query.where(localized_search_text_in(:title), text: "%#{search_text}%")
|
19
|
+
end
|
20
|
+
|
21
|
+
# Handle the origin filter
|
22
|
+
# The 'official' plans doesn't have an author id
|
23
|
+
def search_origin
|
24
|
+
if origin == "official"
|
25
|
+
query.where.not(coauthorships_count: 0).joins(:coauthorships).where(decidim_coauthorships: { decidim_author_type: "Decidim::Organization" })
|
26
|
+
elsif origin == "citizens"
|
27
|
+
query.where.not(coauthorships_count: 0).joins(:coauthorships).where.not(decidim_coauthorships: { decidim_author_type: "Decidim::Organization" })
|
28
|
+
else # Assume 'all'
|
29
|
+
query
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Handle the activity filter
|
34
|
+
def search_activity
|
35
|
+
query
|
36
|
+
end
|
37
|
+
|
38
|
+
# Handle the state filter
|
39
|
+
def search_state
|
40
|
+
case state
|
41
|
+
when "accepted"
|
42
|
+
query.accepted
|
43
|
+
when "rejected"
|
44
|
+
query.rejected
|
45
|
+
when "evaluating"
|
46
|
+
query.evaluating
|
47
|
+
when "withdrawn"
|
48
|
+
query.withdrawn
|
49
|
+
when "except_rejected"
|
50
|
+
query.except_rejected.except_withdrawn
|
51
|
+
else # Assume 'not_withdrawn'
|
52
|
+
query.except_withdrawn
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Filters Plans by the name of the classes they are linked to. By default,
|
57
|
+
# returns all Plans. When a `related_to` param is given, then it camelcases item
|
58
|
+
# to find the real class name and checks the links for the Plans.
|
59
|
+
#
|
60
|
+
# The `related_to` param is expected to be in this form:
|
61
|
+
#
|
62
|
+
# "decidim/meetings/meeting"
|
63
|
+
#
|
64
|
+
# This can be achieved by performing `klass.name.underscore`.
|
65
|
+
#
|
66
|
+
# Returns only those plans that are linked to the given class name.
|
67
|
+
def search_related_to
|
68
|
+
from = query
|
69
|
+
.joins(:resource_links_from)
|
70
|
+
.where(decidim_resource_links: { to_type: related_to.camelcase })
|
71
|
+
|
72
|
+
to = query
|
73
|
+
.joins(:resource_links_to)
|
74
|
+
.where(decidim_resource_links: { from_type: related_to.camelcase })
|
75
|
+
|
76
|
+
query.where(id: from).or(query.where(id: to))
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Internal: builds the needed query to search for a text in the organization's
|
82
|
+
# available locales. Note that it is intended to be used as follows:
|
83
|
+
#
|
84
|
+
# Example:
|
85
|
+
# Resource.where(localized_search_text_for(:title, text: "my_query"))
|
86
|
+
#
|
87
|
+
# The Hash with the `:text` key is required or it won't work.
|
88
|
+
def localized_search_text_in(field)
|
89
|
+
options[:organization].available_locales.map do |l|
|
90
|
+
"#{field} ->> '#{l}' ILIKE :text"
|
91
|
+
end.join(" OR ")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# The Tracer class allows performing PaperTrail requests where all records
|
6
|
+
# saved during the yield will be stored against the same transaction ID.
|
7
|
+
# This allows saving the main record and its associated records so that the
|
8
|
+
# same changeset can be easily identified afterwards.
|
9
|
+
class Tracer
|
10
|
+
def trace!(author)
|
11
|
+
return unless block_given?
|
12
|
+
|
13
|
+
::PaperTrail.request(whodunnit: gid(author)) do
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Calculates the GlobalID of the version author. If the object does not
|
21
|
+
# respond to `to_gid`, then it returns the object itself.
|
22
|
+
def gid(author)
|
23
|
+
return if author.blank?
|
24
|
+
return author.to_gid if author.respond_to?(:to_gid)
|
25
|
+
author
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%= render "decidim/plans/admin/shared/info_plan" %>
|
2
|
+
<%= decidim_form_for(@form, url: plan_plan_answer_path(plan, @form), html: { class: "form edit_plan_answer" }) do |f| %>
|
3
|
+
<div class="card">
|
4
|
+
<div class="card-divider">
|
5
|
+
<h2 class="card-title"><%= t ".title", title: present(plan).title %></h2>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<div class="card-section">
|
9
|
+
<div class="row column">
|
10
|
+
<%= f.collection_radio_buttons :state, [["accepted", t(".accepted")], ["rejected", t(".rejected")], ["evaluating", t(".evaluating")]], :first, :last, prompt: true %>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class="row column">
|
14
|
+
<%= f.translated :editor, :answer, autofocus: true, rows: 15 %>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="button--double form-general-submit">
|
20
|
+
<%= f.submit t(".answer_plan") %>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<div class="flex--cc flex-gap--1">
|
2
|
+
<div id="js-other-actions-wrapper">
|
3
|
+
<%= link_to t("actions.sections", scope: "decidim.plans"), sections_path, class: "button tiny button--simple" %>
|
4
|
+
|
5
|
+
<% if allowed_to? :create, :plan %>
|
6
|
+
<%= link_to t("actions.new", scope: "decidim.plans"), new_plan_path, class: "button tiny button--simple" %>
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
</div>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<%
|
2
|
+
# Force the form to be multipart for the file uploads.
|
3
|
+
# Calling Decidim form builder's own `upload` builder method should already do
|
4
|
+
# this but it is not working in our case since we have multiple file fields.
|
5
|
+
form.multipart = true
|
6
|
+
%>
|
7
|
+
|
8
|
+
<div class="card">
|
9
|
+
<div class="card-divider">
|
10
|
+
<h2 class="card-title"><%= title %></h2>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class="card-section">
|
14
|
+
<div class="row column">
|
15
|
+
<%= attached_proposals_picker_field form, :proposals %>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<%= javascript_include_tag "decidim/plans/proposal_picker" %>
|
19
|
+
<%= stylesheet_link_tag "decidim/plans/proposal_picker" %>
|
20
|
+
|
21
|
+
<div class="row column">
|
22
|
+
<%= remaining_characters("plans_title", component_settings.plan_title_length) do |opts| %>
|
23
|
+
<% if component_settings.multilingual_answers? %>
|
24
|
+
<%= form.translated :text_field, :title, opts.merge(tabs_id: "title-tabs") %>
|
25
|
+
<% else %>
|
26
|
+
<%= form.text_field(
|
27
|
+
"title_#{current_locale}".to_sym,
|
28
|
+
{
|
29
|
+
label: form.label_for(:title)
|
30
|
+
}.merge(opts)
|
31
|
+
) %>
|
32
|
+
<% end %>
|
33
|
+
<% end %>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<% if @form.categories&.any? %>
|
37
|
+
<div class="row column">
|
38
|
+
<%= form.categories_select :category_id, @form.categories, prompt: t(".select_a_category") %>
|
39
|
+
</div>
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<% if current_participatory_space.has_subscopes? %>
|
43
|
+
<div class="row column">
|
44
|
+
<%= scopes_picker_field form, :scope_id %>
|
45
|
+
</div>
|
46
|
+
<% end %>
|
47
|
+
|
48
|
+
<% @form.contents.each_with_index do |content, idx| %>
|
49
|
+
<%= fields_for "contents[]", content do |content_form| %>
|
50
|
+
<%= render "decidim/plans/shared/section_content_field", form: content_form, id: tabs_id_for_content(idx) %>
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
|
54
|
+
<% if component_settings.attachments_allowed? %>
|
55
|
+
<div class="row column">
|
56
|
+
<%= render partial: "decidim/plans/shared/attachments", object: form, locals: { form: form, blank_attachment: blank_attachment, component_settings: component_settings } %>
|
57
|
+
</div>
|
58
|
+
<% end %>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<%= javascript_include_tag "decidim/plans/plans_form" %>
|