decidim-meetings 0.15.2 → 0.16.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 +4 -4
  2. data/app/cells/decidim/meetings/highlighted_meetings/show.erb +3 -0
  3. data/app/cells/decidim/meetings/highlighted_meetings_cell.rb +25 -0
  4. data/app/cells/decidim/meetings/highlighted_meetings_for_component/show.erb +33 -0
  5. data/app/cells/decidim/meetings/highlighted_meetings_for_component_cell.rb +37 -0
  6. data/app/cells/decidim/meetings/join_meeting_button/show.erb +19 -10
  7. data/app/commands/decidim/meetings/admin/close_meeting.rb +2 -1
  8. data/app/commands/decidim/meetings/admin/copy_meeting.rb +1 -1
  9. data/app/commands/decidim/meetings/admin/create_meeting.rb +3 -2
  10. data/app/commands/decidim/meetings/admin/update_meeting.rb +1 -1
  11. data/app/commands/decidim/meetings/admin/update_registrations.rb +2 -1
  12. data/app/commands/decidim/meetings/admin/validate_registration_code.rb +1 -1
  13. data/app/commands/decidim/meetings/join_meeting.rb +35 -6
  14. data/app/commands/decidim/meetings/leave_meeting.rb +10 -0
  15. data/app/controllers/decidim/meetings/admin/registration_form_controller.rb +30 -0
  16. data/app/controllers/decidim/meetings/calendars_controller.rb +23 -0
  17. data/app/controllers/decidim/meetings/directory/meetings_controller.rb +4 -0
  18. data/app/controllers/decidim/meetings/registrations_controller.rb +43 -0
  19. data/app/events/decidim/meetings/close_meeting_event.rb +7 -0
  20. data/app/events/decidim/meetings/create_meeting_event.rb +3 -0
  21. data/app/events/decidim/meetings/meeting_registration_notification_event.rb +22 -0
  22. data/app/events/decidim/meetings/meeting_registrations_enabled_event.rb +3 -0
  23. data/app/events/decidim/meetings/meeting_registrations_over_percentage_event.rb +4 -0
  24. data/app/events/decidim/meetings/registration_code_validated_event.rb +4 -0
  25. data/app/events/decidim/meetings/upcoming_meeting_event.rb +3 -0
  26. data/app/events/decidim/meetings/update_meeting_event.rb +3 -0
  27. data/app/forms/decidim/meetings/admin/meeting_registrations_form.rb +1 -0
  28. data/app/helpers/decidim/meetings/meetings_helper.rb +18 -0
  29. data/app/jobs/decidim/meetings/upcoming_meeting_notification_job.rb +1 -1
  30. data/app/mailers/decidim/meetings/registration_mailer.rb +1 -9
  31. data/app/models/decidim/meetings/meeting.rb +18 -0
  32. data/app/permissions/decidim/meetings/admin/permissions.rb +14 -0
  33. data/app/presenters/decidim/meetings/admin_log/value_types/meeting_title_description_presenter.rb +1 -1
  34. data/app/presenters/decidim/meetings/meeting_presenter.rb +50 -22
  35. data/app/queries/decidim/meetings/metrics/meeting_followers_metric_measure.rb +31 -0
  36. data/app/serializers/decidim/meetings/registration_serializer.rb +19 -1
  37. data/app/services/decidim/meetings/calendar/base_calendar.rb +61 -0
  38. data/app/services/decidim/meetings/calendar/component_calendar.rb +41 -0
  39. data/app/services/decidim/meetings/calendar/meeting_to_event.rb +70 -0
  40. data/app/services/decidim/meetings/calendar/organization_calendar.rb +33 -0
  41. data/app/services/decidim/meetings/calendar_renderer.rb +16 -0
  42. data/app/views/decidim/meetings/_calendar_modal.html.erb +13 -0
  43. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +2 -2
  44. data/app/views/decidim/meetings/admin/meetings/index.html.erb +4 -1
  45. data/app/views/decidim/meetings/admin/registrations/_form.html.erb +5 -0
  46. data/app/views/decidim/meetings/directory/meetings/_meetings.html.erb +9 -2
  47. data/app/views/decidim/meetings/meetings/_meetings.html.erb +8 -1
  48. data/app/views/decidim/meetings/meetings/show.html.erb +17 -4
  49. data/config/locales/ca.yml +31 -10
  50. data/config/locales/de.yml +30 -10
  51. data/config/locales/en.yml +30 -9
  52. data/config/locales/es-PY.yml +31 -10
  53. data/config/locales/es.yml +30 -9
  54. data/config/locales/eu.yml +31 -10
  55. data/config/locales/fi-pl.yml +31 -10
  56. data/config/locales/fi.yml +31 -10
  57. data/config/locales/fr.yml +30 -9
  58. data/config/locales/gl.yml +30 -9
  59. data/config/locales/hu.yml +31 -10
  60. data/config/locales/id-ID.yml +28 -7
  61. data/config/locales/it.yml +31 -10
  62. data/config/locales/nl.yml +30 -9
  63. data/config/locales/pl.yml +32 -11
  64. data/config/locales/pt-BR.yml +31 -10
  65. data/config/locales/pt.yml +31 -10
  66. data/config/locales/ru.yml +7 -12
  67. data/config/locales/sv.yml +30 -9
  68. data/config/locales/tr-TR.yml +30 -9
  69. data/config/locales/uk.yml +7 -12
  70. data/db/migrate/20180801150031_add_registration_form_enabled_to_decidim_meetings.rb +7 -0
  71. data/db/migrate/20181107175558_add_questionnaire_to_existing_meetings.rb +15 -0
  72. data/lib/decidim/meetings.rb +1 -0
  73. data/lib/decidim/meetings/admin_engine.rb +1 -0
  74. data/lib/decidim/meetings/component.rb +26 -0
  75. data/lib/decidim/meetings/directory_engine.rb +1 -0
  76. data/lib/decidim/meetings/engine.rb +19 -30
  77. data/lib/decidim/meetings/meeting_serializer.rb +77 -0
  78. data/lib/decidim/meetings/test/factories.rb +3 -0
  79. data/lib/decidim/meetings/version.rb +1 -1
  80. metadata +44 -15
  81. data/app/views/decidim/participatory_spaces/_highlighted_meetings.html.erb +0 -19
  82. data/app/views/decidim/participatory_spaces/_meeting.html.erb +0 -1
@@ -3,6 +3,13 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class CloseMeetingEvent < Decidim::Events::SimpleEvent
6
+ def resource_text
7
+ translated_attribute(resource.description)
8
+ end
9
+
10
+ def event_has_roles?
11
+ true
12
+ end
6
13
  end
7
14
  end
8
15
  end
@@ -3,6 +3,9 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class CreateMeetingEvent < Decidim::Events::SimpleEvent
6
+ def resource_text
7
+ translated_attribute(resource.description)
8
+ end
6
9
  end
7
10
  end
8
11
  end
@@ -0,0 +1,22 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ class MeetingRegistrationNotificationEvent < Decidim::Events::BaseEvent
6
+ include Decidim::Events::NotificationEvent
7
+
8
+ def notification_title
9
+ I18n.t("notification_title", i18n_options).html_safe
10
+ end
11
+
12
+ def i18n_options
13
+ {
14
+ resource_title: resource_title,
15
+ resource_url: resource_url,
16
+ scope: event_name,
17
+ registration_code: extra["registration_code"]
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,6 +3,9 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class MeetingRegistrationsEnabledEvent < Decidim::Events::SimpleEvent
6
+ def resource_text
7
+ translated_attribute(resource.description)
8
+ end
6
9
  end
7
10
  end
8
11
  end
@@ -5,6 +5,10 @@ module Decidim
5
5
  class MeetingRegistrationsOverPercentageEvent < Decidim::Events::SimpleEvent
6
6
  i18n_attributes :percentage
7
7
 
8
+ def resource_text
9
+ translated_attribute(resource.description)
10
+ end
11
+
8
12
  def percentage
9
13
  extra["percentage"] * 100
10
14
  end
@@ -5,6 +5,10 @@ module Decidim
5
5
  class RegistrationCodeValidatedEvent < Decidim::Events::SimpleEvent
6
6
  i18n_attributes :registration_code
7
7
 
8
+ def resource_text
9
+ translated_attribute(resource.description)
10
+ end
11
+
8
12
  private
9
13
 
10
14
  def registration_code
@@ -3,6 +3,9 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class UpcomingMeetingEvent < Decidim::Events::SimpleEvent
6
+ def resource_text
7
+ translated_attribute(resource.description)
8
+ end
6
9
  end
7
10
  end
8
11
  end
@@ -3,6 +3,9 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class UpdateMeetingEvent < Decidim::Events::SimpleEvent
6
+ def resource_text
7
+ translated_attribute(resource.description)
8
+ end
6
9
  end
7
10
  end
8
11
  end
@@ -10,6 +10,7 @@ module Decidim
10
10
  mimic :meeting
11
11
 
12
12
  attribute :registrations_enabled, Boolean
13
+ attribute :registration_form_enabled, Boolean
13
14
  attribute :available_slots, Integer
14
15
  attribute :reserved_slots, Integer
15
16
  translatable_attribute :registration_terms, String
@@ -87,6 +87,24 @@ module Decidim
87
87
  end
88
88
  html.html_safe
89
89
  end
90
+
91
+ def registration
92
+ @registration ||= Decidim::Meetings::Registration
93
+ .where(decidim_user_id: current_user)
94
+ .find_by(decidim_meeting_id: @meeting.id)
95
+ end
96
+
97
+ def registration_code_help_text
98
+ t("registration_code_help_text", scope: "decidim.meetings.meetings.show")
99
+ end
100
+
101
+ def registration_validation_state
102
+ if @registration.validated_at
103
+ t("validated", scope: "decidim.meetings.meetings.show.registration_state")
104
+ else
105
+ t("validation_pending", scope: "decidim.meetings.meetings.show.registration_state")
106
+ end
107
+ end
90
108
  end
91
109
  end
92
110
  end
@@ -21,7 +21,7 @@ module Decidim
21
21
  event: "decidim.events.meetings.upcoming_meeting",
22
22
  event_class: Decidim::Meetings::UpcomingMeetingEvent,
23
23
  resource: meeting,
24
- recipient_ids: meeting.followers.pluck(:id)
24
+ followers: meeting.followers
25
25
  )
26
26
  end
27
27
 
@@ -32,15 +32,7 @@ module Decidim
32
32
 
33
33
  def add_calendar_attachment
34
34
  calendar = Icalendar::Calendar.new
35
- calendar.event do |event|
36
- event.dtstart = Icalendar::Values::DateTime.new(@meeting.start_time)
37
- event.dtend = Icalendar::Values::DateTime.new(@meeting.end_time)
38
- event.summary = present(@meeting).title
39
- event.description = strip_tags(present(@meeting).description)
40
- event.location = @meeting.address
41
- event.geo = [@meeting.latitude, @meeting.longitude]
42
- event.url = @locator.url
43
- end
35
+ calendar.add_event(Calendar::MeetingToEvent.new(@meeting).event)
44
36
 
45
37
  attachments["meeting-calendar-info.ics"] = calendar.to_ical
46
38
  end
@@ -18,6 +18,8 @@ module Decidim
18
18
  include Decidim::Traceable
19
19
  include Decidim::Loggable
20
20
  include Decidim::Hashtaggable
21
+ include Decidim::Forms::HasQuestionnaire
22
+ include Decidim::Paddable
21
23
 
22
24
  belongs_to :organizer, foreign_key: "organizer_id", class_name: "Decidim::User", optional: true
23
25
  has_many :registrations, class_name: "Decidim::Meetings::Registration", foreign_key: "decidim_meeting_id", dependent: :destroy
@@ -143,6 +145,22 @@ module Decidim
143
145
  def resource_visible?
144
146
  !private_meeting? || transparent?
145
147
  end
148
+
149
+ # Overwrites method from Paddable to add custom rules in order to know
150
+ # when to display a pad or not.
151
+ def pad_is_visible?
152
+ return false unless pad
153
+
154
+ (start_time - Time.current) <= 24.hours
155
+ end
156
+
157
+ # Overwrites method from Paddable to add custom rules in order to know
158
+ # when a pad is writable or not.
159
+ def pad_is_writable?
160
+ return false unless pad_is_visible?
161
+
162
+ (Time.current - end_time) < 72.hours
163
+ end
146
164
  end
147
165
  end
148
166
  end
@@ -8,6 +8,7 @@ module Decidim
8
8
  return permission_action unless user
9
9
  return permission_action unless permission_action.scope == :admin
10
10
 
11
+ allowed_registration_form_action?
11
12
  allowed_meeting_action?
12
13
  allowed_agenda_action?
13
14
  allowed_minutes_action?
@@ -29,6 +30,10 @@ module Decidim
29
30
  @minutes ||= context.fetch(:minutes, nil)
30
31
  end
31
32
 
33
+ def registration_form
34
+ @registration_form ||= context.fetch(:questionnaire, nil)
35
+ end
36
+
32
37
  def allowed_meeting_action?
33
38
  return unless permission_action.subject == :meeting
34
39
 
@@ -42,6 +47,15 @@ module Decidim
42
47
  end
43
48
  end
44
49
 
50
+ def allowed_registration_form_action?
51
+ return unless permission_action.subject == :questionnaire
52
+
53
+ case permission_action.action
54
+ when :update
55
+ toggle_allow(registration_form.present?)
56
+ end
57
+ end
58
+
45
59
  def allowed_agenda_action?
46
60
  return unless permission_action.subject == :agenda
47
61
 
@@ -11,7 +11,7 @@ module Decidim
11
11
  return unless value
12
12
 
13
13
  renderer = Decidim::ContentRenderers::HashtagRenderer.new(h.translated_attribute(value))
14
- renderer.render_without_link.html_safe
14
+ renderer.render(links: false).html_safe
15
15
  end
16
16
  end
17
17
  end
@@ -7,45 +7,73 @@ module Decidim
7
7
  #
8
8
  class MeetingPresenter < SimpleDelegator
9
9
  include Decidim::TranslationsHelper
10
+ include Decidim::ResourceHelper
10
11
 
11
12
  def meeting
12
13
  __getobj__
13
14
  end
14
15
 
15
- def title
16
+ def title(links: false, all_locales: false)
16
17
  return unless meeting
17
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(translated_attribute(meeting.title))
18
- renderer.render_without_link.html_safe
18
+
19
+ handle_locales(meeting.title, all_locales) do |content|
20
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(content)
21
+ renderer.render(links: links).html_safe
22
+ end
19
23
  end
20
24
 
21
- def html_title
25
+ def description(links: false, all_locales: false)
22
26
  return unless meeting
23
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(translated_attribute(meeting.title))
24
- renderer.render.html_safe
27
+
28
+ handle_locales(meeting.description, all_locales) do |content|
29
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(content)
30
+ renderer.render(links: links).html_safe
31
+ end
25
32
  end
26
33
 
27
- def description
28
- return unless meeting
29
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(translated_attribute(meeting.description))
30
- renderer.render_without_link.html_safe
34
+ # Next methods are used for present a Meeting As Proposal Author
35
+ def name
36
+ title
31
37
  end
32
38
 
33
- def html_description
34
- return unless meeting
35
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(translated_attribute(meeting.description))
36
- renderer.render.html_safe
39
+ def nickname
40
+ ""
37
41
  end
38
42
 
39
- def title_with_locales
40
- return unless meeting
41
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(meeting.title)
42
- renderer.render_without_link
43
+ def badge
44
+ ""
43
45
  end
44
46
 
45
- def description_with_locales
46
- return unless meeting
47
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(meeting.description)
48
- renderer.render_without_link
47
+ def profile_path
48
+ resource_locator(meeting).path
49
+ end
50
+
51
+ def avatar_url
52
+ ActionController::Base.helpers.asset_path("decidim/meetings/icon.svg")
53
+ end
54
+
55
+ def deleted?
56
+ false
57
+ end
58
+
59
+ def can_be_contacted?
60
+ false
61
+ end
62
+
63
+ def has_tooltip?
64
+ false
65
+ end
66
+
67
+ private
68
+
69
+ def handle_locales(content, all_locales)
70
+ if all_locales
71
+ content.each_with_object({}) do |(locale, string), parsed_content|
72
+ parsed_content[locale] = yield(string)
73
+ end
74
+ else
75
+ yield(translated_attribute(content))
76
+ end
49
77
  end
50
78
  end
51
79
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module Metrics
6
+ # Searches for unique Users following the next objects
7
+ # - Meetings
8
+ class MeetingFollowersMetricMeasure < Decidim::MetricMeasure
9
+ def valid?
10
+ super && @resource.is_a?(Decidim::Component)
11
+ end
12
+
13
+ def calculate
14
+ meetings = Decidim::Meetings::Meeting.where(component: @resource).joins(:component)
15
+
16
+ meetings_followers = Decidim::Follow.where(followable: meetings).joins(:user)
17
+ .where("decidim_follows.created_at <= ?", end_time)
18
+ cumulative_users = meetings_followers.pluck(:decidim_user_id)
19
+
20
+ meetings_followers = meetings_followers.where("decidim_follows.created_at >= ?", start_time)
21
+ quantity_users = meetings_followers.pluck(:decidim_user_id)
22
+
23
+ {
24
+ cumulative_users: cumulative_users.uniq,
25
+ quantity_users: quantity_users.uniq
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -3,6 +3,7 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class RegistrationSerializer < Decidim::Exporters::Serializer
6
+ include Decidim::TranslationsHelper
6
7
  # Serializes a registration
7
8
  def serialize
8
9
  {
@@ -11,9 +12,26 @@ module Decidim
11
12
  user: {
12
13
  name: resource.user.name,
13
14
  email: resource.user.email
14
- }
15
+ },
16
+ registration_form_answers: serialize_answers
15
17
  }
16
18
  end
19
+
20
+ private
21
+
22
+ def serialize_answers
23
+ questions = resource.meeting.questionnaire.questions
24
+ answers = resource.meeting.questionnaire.answers.where(user: resource.user)
25
+ questions.each_with_index.inject({}) do |serialized, (question, idx)|
26
+ answer = answers.find_by(question: question)
27
+ serialized.update("#{idx + 1}. #{translated_attribute(question.body)}" => normalize_body(answer))
28
+ end
29
+ end
30
+
31
+ def normalize_body(answer)
32
+ return "" unless answer
33
+ answer.body || answer.choices.pluck(:body)
34
+ end
17
35
  end
18
36
  end
19
37
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module Calendar
6
+ # This class serves as a base class to render calendars. Please, inherit
7
+ # from it and overwrite the `events` with whatever logic you need to do
8
+ # it. After that, modify the `Decidim::Meetings::Calendar.for` method to
9
+ # include your new class.
10
+ class BaseCalendar
11
+ # Convenience method to shorten the calls. Converts the resource
12
+ # meetings to the ICalendar format.
13
+ #
14
+ # resource - a resource that has meetings.
15
+ #
16
+ # Returns a String.
17
+ def self.for(resource)
18
+ new(resource).calendar
19
+ end
20
+
21
+ # Initializes the class.
22
+ #
23
+ # resource - a resource that has meetings.
24
+ def initialize(resource)
25
+ @resource = resource
26
+ end
27
+
28
+ # Converts the resource meetings to the ICalendar format.
29
+ #
30
+ # Returns a String.
31
+ def calendar
32
+ return if events.blank?
33
+ <<~CALENDAR.gsub("\n\n", "\n")
34
+ BEGIN:VCALENDAR\r
35
+ VERSION:2.0\r
36
+ PRODID:icalendar-ruby\r
37
+ CALSCALE:GREGORIAN\r
38
+ #{events}
39
+ END:VCALENDAR\r
40
+ CALENDAR
41
+ end
42
+
43
+ # Internal: this method is supposed to be overwritten by classes
44
+ # inheriting from this one. It should find the relevant meetings that
45
+ # will be exported, and convert them to ICalendar events. Please use the
46
+ # `MeetingToEvent` class to do so. Since this method returns a String,
47
+ # you can cache its contents. Check existing implementations for an
48
+ # example of how to achieve it.
49
+ #
50
+ # Returns a String.
51
+ def events
52
+ raise "Please, overwrite this method. You can use the `MeetingToEvent` class to convert a meeting to the correct ICalendar format."
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :resource
58
+ end
59
+ end
60
+ end
61
+ end