decidim-meetings 0.29.2 → 0.30.0.rc2

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb +1 -1
  3. data/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb +3 -1
  4. data/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +1 -1
  5. data/app/cells/decidim/meetings/dates_and_map/show.erb +1 -1
  6. data/app/cells/decidim/meetings/highlighted_meetings_for_component/show.erb +1 -1
  7. data/app/cells/decidim/meetings/join_meeting_button/show.erb +5 -2
  8. data/app/cells/decidim/meetings/meeting_card_metadata_cell.rb +3 -3
  9. data/app/cells/decidim/meetings/meeting_l_cell.rb +12 -0
  10. data/app/commands/decidim/meetings/admin/copy_meeting.rb +1 -2
  11. data/app/commands/decidim/meetings/admin/create_meeting.rb +8 -2
  12. data/app/commands/decidim/meetings/admin/update_meeting.rb +8 -2
  13. data/app/commands/decidim/meetings/create_meeting.rb +2 -2
  14. data/app/commands/decidim/meetings/update_meeting.rb +2 -3
  15. data/app/controllers/concerns/decidim/meetings/admin/filterable.rb +9 -5
  16. data/app/controllers/concerns/decidim/meetings/component_filterable.rb +1 -2
  17. data/app/controllers/decidim/meetings/admin/meetings_controller.rb +14 -22
  18. data/app/controllers/decidim/meetings/admin/registration_form_controller.rb +8 -0
  19. data/app/controllers/decidim/meetings/directory/meetings_controller.rb +2 -4
  20. data/app/controllers/decidim/meetings/meetings_controller.rb +40 -6
  21. data/app/forms/decidim/meetings/admin/meeting_form.rb +16 -30
  22. data/app/forms/decidim/meetings/base_meeting_form.rb +6 -0
  23. data/app/forms/decidim/meetings/meeting_form.rb +2 -30
  24. data/app/helpers/decidim/meetings/admin/application_helper.rb +11 -1
  25. data/app/helpers/decidim/meetings/application_helper.rb +35 -1
  26. data/app/helpers/decidim/meetings/directory/application_helper.rb +9 -48
  27. data/app/helpers/decidim/meetings/meetings_helper.rb +5 -0
  28. data/app/models/decidim/meetings/invite.rb +10 -0
  29. data/app/models/decidim/meetings/meeting.rb +23 -1
  30. data/app/models/decidim/meetings/meeting_link.rb +25 -0
  31. data/app/packs/entrypoints/decidim_meetings_admin.js +1 -0
  32. data/app/packs/src/decidim/meetings/admin/meetings_components_form.js +77 -0
  33. data/app/packs/src/decidim/meetings/admin/meetings_form.js +8 -0
  34. data/app/permissions/decidim/meetings/admin/permissions.rb +1 -1
  35. data/app/permissions/decidim/meetings/permissions.rb +13 -9
  36. data/app/presenters/decidim/meetings/admin_log/meeting_presenter.rb +1 -1
  37. data/app/presenters/decidim/meetings/meeting_presenter.rb +13 -1
  38. data/app/queries/decidim/meetings/filtered_meetings.rb +2 -2
  39. data/app/queries/decidim/meetings/metrics/meeting_followers_metric_measure.rb +2 -2
  40. data/app/queries/decidim/meetings/metrics/meetings_metric_manage.rb +6 -6
  41. data/app/serializers/decidim/meetings/base_download_your_data_serializer.rb +32 -0
  42. data/app/serializers/decidim/meetings/download_your_data_invite_serializer.rb +6 -26
  43. data/app/serializers/decidim/meetings/download_your_data_meeting_serializer.rb +15 -0
  44. data/app/serializers/decidim/meetings/download_your_data_registration_serializer.rb +6 -24
  45. data/app/views/decidim/meetings/admin/meetings/_component.html.erb +15 -0
  46. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +7 -9
  47. data/app/views/decidim/meetings/admin/meetings/_linked_spaces.html.erb +53 -0
  48. data/app/views/decidim/meetings/admin/meetings/_meeting-tr.html.erb +42 -0
  49. data/app/views/decidim/meetings/admin/meetings/_meeting_actions.html.erb +70 -0
  50. data/app/views/decidim/meetings/admin/meetings/_meetings-thead.html.erb +26 -0
  51. data/app/views/decidim/meetings/admin/meetings/index.html.erb +16 -142
  52. data/app/views/decidim/meetings/admin/meetings/manage_trash.html.erb +23 -0
  53. data/app/views/decidim/meetings/admin/registration_form/edit_questions.html.erb +44 -0
  54. data/app/views/decidim/meetings/admin/registrations/edit.html.erb +1 -0
  55. data/app/views/decidim/meetings/directory/meetings/index.html.erb +1 -2
  56. data/app/views/decidim/meetings/live_events/show.html.erb +5 -5
  57. data/app/views/decidim/meetings/meetings/_form.html.erb +4 -8
  58. data/app/views/decidim/meetings/meetings/_meeting.html.erb +51 -26
  59. data/app/views/decidim/meetings/meetings/_meeting_actions.html.erb +34 -0
  60. data/app/views/decidim/meetings/meetings/_meeting_aside.html.erb +20 -59
  61. data/app/views/decidim/meetings/meetings/_meeting_poll_actions.html.erb +2 -5
  62. data/app/views/decidim/meetings/meetings/_schema_org_event_meeting.html.erb +3 -0
  63. data/app/views/decidim/meetings/meetings/index.html.erb +10 -2
  64. data/app/views/decidim/meetings/meetings/show.html.erb +5 -5
  65. data/app/views/decidim/meetings/polls/answers/index.html.erb +5 -5
  66. data/app/views/decidim/meetings/shared/_filters.html.erb +1 -13
  67. data/app/views/decidim/meetings/shared/_index.html.erb +1 -1
  68. data/app/views/decidim/meetings/shared/_index.js.erb +3 -2
  69. data/app/views/decidim/meetings/shared/_meetings_aside.html.erb +2 -2
  70. data/app/views/decidim/participatory_spaces/_conference_venues.html.erb +1 -1
  71. data/config/locales/ar.yml +17 -17
  72. data/config/locales/bg.yml +5 -25
  73. data/config/locales/ca.yml +137 -25
  74. data/config/locales/cs.yml +138 -28
  75. data/config/locales/de.yml +138 -26
  76. data/config/locales/el.yml +1 -22
  77. data/config/locales/en.yml +133 -21
  78. data/config/locales/es-MX.yml +137 -25
  79. data/config/locales/es-PY.yml +137 -25
  80. data/config/locales/es.yml +137 -25
  81. data/config/locales/eu.yml +138 -26
  82. data/config/locales/fi-plain.yml +138 -26
  83. data/config/locales/fi.yml +138 -26
  84. data/config/locales/fr-CA.yml +79 -26
  85. data/config/locales/fr.yml +79 -26
  86. data/config/locales/ga-IE.yml +0 -13
  87. data/config/locales/gl.yml +1 -12
  88. data/config/locales/hu.yml +1 -19
  89. data/config/locales/id-ID.yml +1 -13
  90. data/config/locales/is-IS.yml +0 -12
  91. data/config/locales/it.yml +1 -17
  92. data/config/locales/ja.yml +95 -25
  93. data/config/locales/lb.yml +0 -11
  94. data/config/locales/lt.yml +1 -23
  95. data/config/locales/lv.yml +1 -13
  96. data/config/locales/nl.yml +1 -17
  97. data/config/locales/no.yml +1 -16
  98. data/config/locales/pl.yml +5 -21
  99. data/config/locales/pt-BR.yml +1 -21
  100. data/config/locales/pt.yml +1 -17
  101. data/config/locales/ro-RO.yml +30 -17
  102. data/config/locales/ru.yml +1 -13
  103. data/config/locales/sk.yml +1 -13
  104. data/config/locales/sv.yml +74 -26
  105. data/config/locales/tr-TR.yml +1 -19
  106. data/config/locales/uk.yml +0 -12
  107. data/config/locales/zh-CN.yml +1 -18
  108. data/config/locales/zh-TW.yml +1 -21
  109. data/db/migrate/20181107175558_add_questionnaire_to_existing_meetings.rb +1 -1
  110. data/db/migrate/20200827153856_add_commentable_counter_cache_to_meetings.rb +1 -1
  111. data/db/migrate/20201016065302_fix_meetings_registration_terms.rb +1 -1
  112. data/db/migrate/20210310120731_add_followable_counter_cache_to_meetings.rb +1 -1
  113. data/db/migrate/20240712104245_create_decidim_meetings_meeting_link.rb +12 -0
  114. data/db/migrate/20240828103603_add_deleted_at_to_decidim_meetings_meetings.rb +8 -0
  115. data/decidim-meetings.gemspec +1 -1
  116. data/lib/decidim/api/agenda_item_type.rb +6 -7
  117. data/lib/decidim/api/agenda_type.rb +3 -4
  118. data/lib/decidim/api/meeting_type.rb +33 -41
  119. data/lib/decidim/api/meetings_type.rb +4 -5
  120. data/lib/decidim/api/service_type.rb +1 -1
  121. data/lib/decidim/meetings/admin_engine.rb +9 -1
  122. data/lib/decidim/meetings/component.rb +14 -4
  123. data/lib/decidim/meetings/download_your_data_user_answers_serializer.rb +13 -7
  124. data/lib/decidim/meetings/meeting_serializer.rb +70 -59
  125. data/lib/decidim/meetings/schema_org_event_meeting_serializer.rb +151 -0
  126. data/lib/decidim/meetings/seeds.rb +2 -4
  127. data/lib/decidim/meetings/test/factories.rb +8 -0
  128. data/lib/decidim/meetings/test/translated_event.rb +3 -3
  129. data/lib/decidim/meetings/version.rb +1 -1
  130. data/lib/decidim/meetings.rb +1 -0
  131. metadata +32 -21
  132. data/app/cells/decidim/meetings/meetings_map/show.erb +0 -16
  133. data/app/cells/decidim/meetings/meetings_map_cell.rb +0 -32
  134. data/app/commands/decidim/meetings/admin/destroy_meeting.rb +0 -21
  135. data/app/helpers/decidim/meetings/map_helper.rb +0 -21
  136. data/app/views/decidim/meetings/meetings/_actions.html.erb +0 -6
@@ -7,32 +7,53 @@ module Decidim
7
7
 
8
8
  implements Decidim::Comments::CommentableInterface
9
9
  implements Decidim::Core::AuthorableInterface
10
- implements Decidim::Core::CategorizableInterface
11
- implements Decidim::Core::ScopableInterface
10
+ implements Decidim::Core::TaxonomizableInterface
12
11
  implements Decidim::Core::AttachableInterface
13
12
  implements Decidim::Core::TimestampsInterface
14
13
  implements Decidim::Meetings::ServicesInterface
15
14
  implements Decidim::Meetings::LinkedResourcesInterface if Decidim::Meetings.enable_proposal_linking
16
15
  implements Decidim::Forms::QuestionnaireEntityInterface
17
16
 
17
+ field :address, GraphQL::Types::String, "The physical address of this meeting (used for geolocation)", null: true
18
+ field :agenda, Decidim::Meetings::AgendaType, "Agenda for this meeting, if available", null: true
19
+ field :attendee_count, GraphQL::Types::Int, "Amount of attendees to this meeting", method: :attendees_count, null: true
20
+ field :attending_organizations, GraphQL::Types::String, "list of attending organizations", null: true
21
+ field :audio_url, GraphQL::Types::String, "URL for the audio of the session, if any", null: true
22
+ field :closed, GraphQL::Types::Boolean, "Whether this meeting is closed or not.", method: :closed?, null: false
23
+ field :closing_report, Decidim::Core::TranslatedFieldType, "The closing report of this meeting.", null: true
24
+ field :contribution_count, GraphQL::Types::Int, "Amount of contributions to this meeting", method: :contributions_count, null: true
25
+ field :coordinates, Decidim::Core::CoordinatesType, "Physical coordinates for this meeting", null: true
26
+ field :description, Decidim::Core::TranslatedFieldType, "The description of this meeting.", null: true
27
+ field :end_time, Decidim::Core::DateTimeType, "The time this meeting ends", null: false
18
28
  field :id, GraphQL::Types::ID, "ID of this meeting", null: false
29
+ field :iframe_embed_type, GraphQL::Types::String, "The type of displaying of the online meeting URL", null: true
30
+ field :is_withdrawn, GraphQL::Types::Boolean, "Whether this meeting is withdrawn or not.", method: :withdrawn?, null: false
31
+ field :location, Decidim::Core::TranslatedFieldType, "The location of this meeting (free format)", null: true
32
+ field :location_hints, Decidim::Core::TranslatedFieldType, "The location of this meeting (free format)", null: true
33
+ field :online_meeting_url, GraphQL::Types::String, "The URL of the meeting (when the type is online)", null: false
34
+ field :private_meeting, GraphQL::Types::Boolean, "Whether the meeting is private or not (it can only be true if transparent)", null: false
19
35
  field :reference, GraphQL::Types::String, "Reference for this meeting", null: false
20
- field :title, Decidim::Core::TranslatedFieldType, "The title of this meeting.", null: false
21
- field :description, Decidim::Core::TranslatedFieldType, "The description of this meeting.", null: true
36
+ field :registration_form, Decidim::Forms::QuestionnaireType, description: "If registration requires to fill a form, this is the questionnaire", null: true
37
+ field :registration_form_enabled, GraphQL::Types::Boolean, "Whether the registrations have a form or not", null: false
38
+ field :registration_terms, Decidim::Core::TranslatedFieldType, "The registration terms", null: true
39
+ field :registrations_enabled, GraphQL::Types::Boolean, "Whether the registrations are enabled or not", null: false
40
+ field :remaining_slots, GraphQL::Types::Int, "Amount of slots available for this meeting", null: true
22
41
  field :start_time, Decidim::Core::DateTimeType, "The time this meeting starts", null: false
23
- field :end_time, Decidim::Core::DateTimeType, "The time this meeting ends", null: false
24
- field :agenda, Decidim::Meetings::AgendaType, "Agenda for this meeting, if available", null: true
42
+ field :title, Decidim::Core::TranslatedFieldType, "The title of this meeting.", null: false
43
+ field :transparent, GraphQL::Types::Boolean, "For private meetings, information is public if transparent", null: false
44
+ field :type_of_meeting, GraphQL::Types::String, "The type of the meeting (online or in-person)", null: false
45
+ field :video_url, GraphQL::Types::String, "URL for the video of the session, if any", null: true
46
+ field :withdrawn, GraphQL::Types::Boolean, "Whether this meeting has been withdrawn or not", method: :withdrawn?, null: true
47
+ field :withdrawn_at, Decidim::Core::DateTimeType, description: "The date and time this meeting was withdrawn", null: true
48
+
49
+ def registration_form
50
+ object.questionnaire if object.registration_form_enabled?
51
+ end
25
52
 
26
53
  def agenda
27
54
  object.agenda if object.agenda&.visible?
28
55
  end
29
56
 
30
- field :closed, GraphQL::Types::Boolean, "Whether this meeting is closed or not.", method: :closed?, null: false
31
- field :isWithdrawn, GraphQL::Types::Boolean, "Whether this meeting is withdrawn or not.", method: :withdrawn?, null: false
32
- field :closing_report, Decidim::Core::TranslatedFieldType, "The closing report of this meeting.", null: true
33
- field :video_url, GraphQL::Types::String, "URL for the video of the session, if any", null: true
34
- field :audio_url, GraphQL::Types::String, "URL for the audio of the session, if any", null: true
35
-
36
57
  def closing_report
37
58
  object.closing_report if object.closing_visible?
38
59
  end
@@ -45,38 +66,9 @@ module Decidim
45
66
  object.audio_url if object.closing_visible?
46
67
  end
47
68
 
48
- field :withdrawn_at, Decidim::Core::DateTimeType, description: "The date and time this meeting was withdrawn", null: true
49
- field :withdrawn, GraphQL::Types::Boolean, "Whether this meeting has been withdrawn or not", method: :withdrawn?, null: true
50
-
51
- field :created_at, Decidim::Core::DateTimeType, description: "The date and time this minutes was created", null: true
52
- field :updated_at, Decidim::Core::DateTimeType, description: "The date and time this minutes was updated", null: true
53
-
54
- field :attending_organizations, GraphQL::Types::String, "list of attending organizations", null: true
55
- field :attendee_count, GraphQL::Types::Int, "Amount of attendees to this meeting", method: :attendees_count, null: true
56
- field :contribution_count, GraphQL::Types::Int, "Amount of contributions to this meeting", method: :contributions_count, null: true
57
-
58
- field :private_meeting, GraphQL::Types::Boolean, "Whether the meeting is private or not (it can only be true if transparent)", null: false
59
- field :transparent, GraphQL::Types::Boolean, "For private meetings, information is public if transparent", null: false
60
- field :registrations_enabled, GraphQL::Types::Boolean, "Whether the registrations are enabled or not", null: false
61
- field :registration_terms, Decidim::Core::TranslatedFieldType, "The registration terms", null: true
62
- field :remaining_slots, GraphQL::Types::Int, "Amount of slots available for this meeting", null: true
63
- field :registration_form_enabled, GraphQL::Types::Boolean, "Whether the registrations have a form or not", null: false
64
- field :registration_form, Decidim::Forms::QuestionnaireType, description: "If registration requires to fill a form, this is the questionnaire", null: true
65
-
66
- def registration_form
67
- object.questionnaire if object.registration_form_enabled?
68
- end
69
- field :location, Decidim::Core::TranslatedFieldType, "The location of this meeting (free format)", null: true
70
- field :location_hints, Decidim::Core::TranslatedFieldType, "The location of this meeting (free format)", null: true
71
- field :address, GraphQL::Types::String, "The physical address of this meeting (used for geolocation)", null: true
72
- field :coordinates, Decidim::Core::CoordinatesType, "Physical coordinates for this meeting", null: true
73
-
74
69
  def coordinates
75
70
  [object.latitude, object.longitude]
76
71
  end
77
- field :type_of_meeting, GraphQL::Types::String, "The type of the meeting (online or in-person)", null: false
78
- field :online_meeting_url, GraphQL::Types::String, "The URL of the meeting (when the type is online)", null: false
79
- field :iframe_embed_type, GraphQL::Types::String, "The type of displaying of the online meeting URL", null: true
80
72
 
81
73
  def self.authorized?(object, context)
82
74
  context[:meeting] = object
@@ -6,16 +6,15 @@ module Decidim
6
6
  graphql_name "Meetings"
7
7
  description "A meetings component of a participatory space."
8
8
 
9
- field :meetings, Decidim::Meetings::MeetingType.connection_type, null: true, connection: true
9
+ field :meeting, Decidim::Meetings::MeetingType, "A single Meeting object", null: true do
10
+ argument :id, GraphQL::Types::ID, "The id of the Meeting requested", required: true
11
+ end
12
+ field :meetings, Decidim::Meetings::MeetingType.connection_type, "A collection of Meetings", null: true, connection: true
10
13
 
11
14
  def meetings
12
15
  Meeting.published.visible.where(component: object).includes(:component)
13
16
  end
14
17
 
15
- field :meeting, Decidim::Meetings::MeetingType, null: true do
16
- argument :id, GraphQL::Types::ID, required: true
17
- end
18
-
19
18
  def meeting(**args)
20
19
  Meeting.published.visible.where(component: object).find_by(id: args[:id])
21
20
  end
@@ -6,8 +6,8 @@ module Decidim
6
6
  graphql_name "MeetingService"
7
7
  description "A meeting service"
8
8
 
9
- field :title, Decidim::Core::TranslatedFieldType, "The title for the service", null: true
10
9
  field :description, Decidim::Core::TranslatedFieldType, "The description for the service", null: true
10
+ field :title, Decidim::Core::TranslatedFieldType, "The title for the service", null: true
11
11
  end
12
12
  end
13
13
  end
@@ -18,13 +18,20 @@ module Decidim
18
18
  member do
19
19
  put :publish
20
20
  put :unpublish
21
+ patch :soft_delete
22
+ patch :restore
21
23
  end
22
24
  resources :meeting_closes, only: [:edit, :update] do
23
25
  get :proposals_picker, on: :collection
24
26
  end
25
27
  resource :registrations, only: [:edit, :update] do
26
28
  resources :invites, only: [:index, :create]
27
- resource :form, only: [:edit, :update], controller: "registration_form"
29
+ resource :form, only: [:edit, :update], controller: "registration_form" do
30
+ member do
31
+ get :edit_questions
32
+ patch :update_questions
33
+ end
34
+ end
28
35
  collection do
29
36
  get :export
30
37
  post :validate_registration_code
@@ -35,6 +42,7 @@ module Decidim
35
42
  resources :attachments, except: [:show]
36
43
  resources :copies, controller: "meeting_copies", only: [:new, :create]
37
44
  resource :poll, only: [:edit, :update], controller: "meetings_poll"
45
+ get :manage_trash, on: :collection
38
46
  end
39
47
  root to: "meetings#index"
40
48
  end
@@ -8,7 +8,11 @@ Decidim.register_component(:meetings) do |component|
8
8
  component.permissions_class_name = "Decidim::Meetings::Permissions"
9
9
 
10
10
  component.query_type = "Decidim::Meetings::MeetingsType"
11
- component.data_portable_entities = ["Decidim::Meetings::Registration"]
11
+ component.data_portable_entities = [
12
+ "Decidim::Meetings::Registration",
13
+ "Decidim::Meetings::Invite",
14
+ "Decidim::Meetings::Meeting"
15
+ ]
12
16
 
13
17
  component.on(:before_destroy) do |instance|
14
18
  raise StandardError, "Cannot remove this component" if Decidim::Meetings::Meeting.where(component: instance).any?
@@ -38,13 +42,20 @@ Decidim.register_component(:meetings) do |component|
38
42
  meetings.sum(:comments_count)
39
43
  end
40
44
 
45
+ component.register_stat :attendees_count, primary: true, priority: Decidim::StatsRegistry::MEDIUM_PRIORITY do |components, start_at, end_at|
46
+ meetings = Decidim::Meetings::Meeting.closed.not_hidden.published.where(component: components, closing_visible: true)
47
+ meetings = meetings.where(closed_at: start_at..) if start_at.present?
48
+ meetings = meetings.where(closed_at: ..end_at) if end_at.present?
49
+ meetings.sum(:attendees_count)
50
+ end
51
+
41
52
  component.exports :meetings do |exports|
42
53
  exports.collection do |component_instance|
43
54
  Decidim::Meetings::Meeting
44
55
  .not_hidden
45
56
  .visible
46
57
  .where(component: component_instance)
47
- .includes(:scope, :category, :attachments, component: { participatory_space: :organization })
58
+ .includes(:taxonomies, :attachments, component: { participatory_space: :organization })
48
59
  end
49
60
 
50
61
  exports.include_in_open_data = true
@@ -77,8 +88,7 @@ Decidim.register_component(:meetings) do |component|
77
88
  component.actions = %w(join comment)
78
89
 
79
90
  component.settings(:global) do |settings|
80
- settings.attribute :scopes_enabled, type: :boolean, default: false
81
- settings.attribute :scope_id, type: :scope
91
+ settings.attribute :taxonomy_filters, type: :taxonomy_filters
82
92
  settings.attribute :announcement, type: :text, translated: true, editor: true
83
93
  settings.attribute :default_registration_terms, type: :text, translated: true, editor: true
84
94
  settings.attribute :comments_enabled, type: :boolean, default: true
@@ -8,16 +8,16 @@ module Decidim
8
8
  def serialize
9
9
  {
10
10
  id: resource.id,
11
- user: {
12
- name: resource.user.name,
13
- email: resource.user.email
14
- },
15
11
  questionnaire: {
16
- id: resource.question.questionnaire.id
12
+ id: resource.question.questionnaire.id,
13
+ title: translated_attribute(resource.question.questionnaire.title),
14
+ description: translated_attribute(resource.question.questionnaire.description),
15
+ tos: translated_attribute(resource.question.questionnaire.tos)
17
16
  },
18
17
  question: {
19
18
  id: resource.question.id,
20
- body: translated_attribute(resource.question.body)
19
+ body: translated_attribute(resource.question.body),
20
+ description: translated_attribute(resource.question.description)
21
21
  },
22
22
  answer: normalize_body(resource)
23
23
  }
@@ -26,7 +26,13 @@ module Decidim
26
26
  private
27
27
 
28
28
  def normalize_body(resource)
29
- resource.choices.pluck(:body)
29
+ attachments_for(resource) || resource.body || resource.choices.pluck(:body)
30
+ end
31
+
32
+ def attachments_for(resource)
33
+ return if resource.attachments.blank?
34
+
35
+ resource.attachments.map(&:url)
30
36
  end
31
37
  end
32
38
  end
@@ -8,63 +8,77 @@ module Decidim
8
8
  include Decidim::ApplicationHelper
9
9
  include Decidim::ResourceHelper
10
10
 
11
- # Public: Initializes the serializer with a meeting.
12
- def initialize(meeting)
13
- @meeting = meeting
14
- end
15
-
16
11
  # Public: Exports a hash with the serialized data for this meeting.
17
12
  def serialize
18
13
  {
19
- id: meeting.id,
20
- category: {
21
- id: meeting.category.try(:id),
22
- name: meeting.category.try(:name)
23
- },
24
- scope: {
25
- id: meeting.scope.try(:id),
26
- name: meeting.scope.try(:name)
27
- },
14
+ id: resource.id,
28
15
  author: {
29
16
  **author_fields
30
17
  },
31
18
  participatory_space: {
32
- id: meeting.participatory_space.id,
33
- url: Decidim::ResourceLocatorPresenter.new(meeting.participatory_space).url
19
+ id: resource.participatory_space.id,
20
+ url: Decidim::ResourceLocatorPresenter.new(resource.participatory_space).url
34
21
  },
22
+ taxonomies:,
35
23
  component: { id: component.id },
36
- title: meeting.title,
37
- description: meeting.description,
38
- start_time: meeting.start_time,
39
- end_time: meeting.end_time,
40
- attendees: meeting.attendees_count.to_i,
41
- contributions: meeting.contributions_count.to_i,
42
- organizations: meeting.attending_organizations,
43
- address: meeting.address,
44
- location: meeting.location,
45
- reference: meeting.reference,
46
- comments: meeting.comments_count,
47
- attachments: meeting.attachments.size,
48
- followers: meeting.follows.size,
24
+ title: resource.title,
25
+ description: resource.description,
26
+ start_time: resource.start_time,
27
+ end_time: resource.end_time,
28
+ attendees: resource.attendees_count.to_i,
29
+ contributions: resource.contributions_count.to_i,
30
+ organizations: resource.attending_organizations,
31
+ address: resource.address,
32
+ location: include_location? ? resource.location : nil,
33
+ reference: resource.reference,
34
+ attachments: resource.attachments.size,
49
35
  url:,
50
36
  related_proposals:,
51
37
  related_results:,
52
- published: meeting.published_at.present?,
53
- withdrawn: meeting.withdrawn?,
54
- withdrawn_at: meeting.withdrawn_at
38
+ published: resource.published_at.present?,
39
+ withdrawn: resource.withdrawn?,
40
+ withdrawn_at: resource.withdrawn_at,
41
+ location_hints: resource.location_hints,
42
+ created_at: resource.created_at,
43
+ updated_at: resource.updated_at,
44
+ latitude: resource.latitude,
45
+ longitude: resource.longitude,
46
+ follows_count: resource.follows_count,
47
+ private_meeting: resource.private_meeting,
48
+ transparent: resource.transparent,
49
+ registration_form_enabled: resource.registration_form_enabled,
50
+ comments: {
51
+ **comment_fields
52
+ },
53
+ online_meeting_url: resource.online_meeting_url,
54
+ closing_visible: resource.closing_visible,
55
+ closing_report: resource.closing_report,
56
+ attending_organizations: resource.attending_organizations,
57
+ registration_url: resource.registration_url,
58
+ decidim_user_group_id: resource.decidim_user_group_id,
59
+ decidim_author_type: resource.decidim_author_type,
60
+ video_url: resource.video_url,
61
+ audio_url: resource.audio_url,
62
+ closed_at: resource.closed_at,
63
+ registration_terms: resource.registration_terms,
64
+ available_slots: resource.available_slots,
65
+ registrations_enabled: resource.registrations_enabled,
66
+ customize_registration_email: resource.customize_registration_email,
67
+ type_of_meeting: resource.type_of_meeting,
68
+ iframe_access_level: resource.iframe_access_level,
69
+ iframe_embed_type: resource.iframe_embed_type,
70
+ reserved_slots: resource.reserved_slots,
71
+ registration_type: resource.registration_type
55
72
  }
56
73
  end
57
74
 
58
75
  private
59
76
 
60
- attr_reader :meeting
61
- alias resource meeting
62
-
63
77
  def author_fields
64
78
  {
65
- id: meeting.author.id,
66
- name: author_name(meeting.author),
67
- url: author_url(meeting.author)
79
+ id: resource.author.id,
80
+ name: author_name(resource.author),
81
+ url: author_url(resource.author)
68
82
  }
69
83
  end
70
84
 
@@ -80,38 +94,35 @@ module Decidim
80
94
  end
81
95
  end
82
96
 
83
- def profile_url(author)
84
- return "" if author.respond_to?(:deleted?) && author.deleted?
85
-
86
- Decidim::Core::Engine.routes.url_helpers.profile_url(author.nickname, host:)
87
- end
88
-
89
- def root_url
90
- Decidim::Core::Engine.routes.url_helpers.root_url(host:)
91
- end
92
-
93
- def host
94
- resource.organization.host
95
- end
96
-
97
- def component
98
- meeting.component
99
- end
100
-
101
97
  def related_proposals
102
- meeting.linked_resources(:proposals, "proposals_from_meeting").map do |proposal|
98
+ resource.linked_resources(:proposals, "proposals_from_meeting").map do |proposal|
103
99
  Decidim::ResourceLocatorPresenter.new(proposal).url
104
100
  end
105
101
  end
106
102
 
107
103
  def related_results
108
- meeting.linked_resources(:results, "meetings_through_proposals").map do |result|
104
+ resource.linked_resources(:results, "meetings_through_proposals").map do |result|
109
105
  Decidim::ResourceLocatorPresenter.new(result).url
110
106
  end
111
107
  end
112
108
 
113
109
  def url
114
- Decidim::ResourceLocatorPresenter.new(meeting).url
110
+ Decidim::ResourceLocatorPresenter.new(resource).url
111
+ end
112
+
113
+ def comment_fields
114
+ return {} unless resource.comments
115
+
116
+ {
117
+ start_time: resource.comments_start_time,
118
+ end_time: resource.comments_end_time,
119
+ enabled: resource.comments_enabled,
120
+ count: resource.comments_count
121
+ }
122
+ end
123
+
124
+ def include_location?
125
+ resource.iframe_access_level == "all"
115
126
  end
116
127
  end
117
128
  end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ class SchemaOrgEventMeetingSerializer < Decidim::Exporters::Serializer
6
+ include Decidim::TranslationsHelper
7
+ include Decidim::SanitizeHelper
8
+ include ActionView::Helpers::UrlHelper
9
+
10
+ # Public: Initializes the serializer with a meeting.
11
+ def initialize(meeting)
12
+ @meeting = meeting
13
+ end
14
+
15
+ # Serializes a meeting for the Schema.org Event type
16
+ #
17
+ # @see https://schema.org/Event
18
+ # @see https://developers.google.com/search/docs/appearance/structured-data/event?hl=en
19
+ def serialize
20
+ attributes = {
21
+ "@context": "https://schema.org",
22
+ "@type": "Event",
23
+ name: decidim_escape_translated(meeting.title),
24
+ description: decidim_escape_translated(meeting.description),
25
+ startDate: meeting.start_time.iso8601,
26
+ endDate: meeting.end_time.iso8601,
27
+ organizer:,
28
+ eventAttendanceMode: event_attendance_mode,
29
+ eventStatus: "https://schema.org/EventScheduled",
30
+ location:
31
+ }
32
+
33
+ attributes = attributes.merge(image:) if meeting.photos.any?
34
+ attributes
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :meeting
40
+ alias resource meeting
41
+
42
+ def organizer
43
+ return organizer_user_group if meeting.decidim_user_group_id?
44
+
45
+ case meeting.author.class.name
46
+ when "Decidim::Organization"
47
+ organizer_organization
48
+ when "Decidim::User"
49
+ organizer_user
50
+ end
51
+ end
52
+
53
+ def organizer_user_group
54
+ {
55
+ "@type": "Organization",
56
+ name: meeting.author.name,
57
+ url: profile_url(meeting.user_group)
58
+ }
59
+ end
60
+
61
+ def organizer_organization
62
+ {
63
+ "@type": "Organization",
64
+ name: decidim_escape_translated(meeting.author.name),
65
+ url: EngineRouter.new("decidim", router_options).root_url
66
+ }
67
+ end
68
+
69
+ def organizer_user
70
+ {
71
+ "@type": "Person",
72
+ name: decidim_escape_translated(meeting.author.name),
73
+ url: profile_url(meeting.author)
74
+ }
75
+ end
76
+
77
+ def router_options = { host: meeting.organization.host }
78
+
79
+ def event_attendance_mode
80
+ case meeting.type_of_meeting
81
+ when "online"
82
+ "https://schema.org/OnlineEventAttendanceMode"
83
+ when "hybrid"
84
+ "https://schema.org/MixedEventAttendanceMode"
85
+ else
86
+ "https://schema.org/OfflineEventAttendanceMode"
87
+ end
88
+ end
89
+
90
+ def location
91
+ case meeting.type_of_meeting
92
+ when "online"
93
+ location_virtual
94
+ when "hybrid"
95
+ [location_postal_address, location_virtual]
96
+ else
97
+ location_postal_address
98
+ end
99
+ end
100
+
101
+ def location_postal_address
102
+ address = {
103
+ "@type": "PostalAddress",
104
+ streetAddress: decidim_escape_translated(meeting.address)
105
+ }
106
+
107
+ address = address.merge({ addressLocality: geocoder_city }) if geocoder_city.present?
108
+ address = address.merge({ addressRegion: geocoder_state }) if geocoder_state.present?
109
+ address = address.merge({ postalCode: geocoder_postal_code }) if geocoder_postal_code.present?
110
+ address = address.merge({ addressCountry: geocoder_country }) if geocoder_country.present?
111
+
112
+ {
113
+ "@type": "Place",
114
+ name: decidim_escape_translated(meeting.location),
115
+ address:
116
+ }
117
+ end
118
+
119
+ def location_virtual
120
+ {
121
+ "@type": "VirtualLocation",
122
+ url: meeting.online_meeting_url
123
+ }
124
+ end
125
+
126
+ def geocoder
127
+ return if meeting.latitude.blank? || meeting.longitude.blank?
128
+
129
+ @geocoder ||= Geocoder.search([meeting.latitude, meeting.longitude]).first
130
+ end
131
+
132
+ def geocoder_city
133
+ @geocoder_city ||= geocoder&.city
134
+ end
135
+
136
+ def geocoder_state
137
+ @geocoder_state ||= geocoder&.state
138
+ end
139
+
140
+ def geocoder_postal_code
141
+ @geocoder_postal_code ||= geocoder&.postal_code
142
+ end
143
+
144
+ def geocoder_country
145
+ @geocoder_country ||= geocoder&.country
146
+ end
147
+
148
+ def image = meeting.photos.map(&:thumbnail_url)
149
+ end
150
+ end
151
+ end
@@ -63,14 +63,12 @@ module Decidim
63
63
 
64
64
  params = {
65
65
  component:,
66
- scope: random_scope(participatory_space:),
67
- category: participatory_space.categories.sample,
68
66
  title: Decidim::Faker::Localized.sentence(word_count: 2),
69
67
  description: Decidim::Faker::Localized.wrapped("<p>", "</p>") do
70
68
  Decidim::Faker::Localized.paragraph(sentence_count: 3)
71
69
  end,
72
- location: Decidim::Faker::Localized.sentence,
73
- location_hints: Decidim::Faker::Localized.sentence,
70
+ location: Decidim::Faker::Localized.sentence(word_count: rand(2..20)),
71
+ location_hints: Decidim::Faker::Localized.sentence(word_count: rand(2..20)),
74
72
  start_time:,
75
73
  end_time:,
76
74
  address: "#{::Faker::Address.street_address} #{::Faker::Address.zip} #{::Faker::Address.city}",
@@ -47,6 +47,7 @@ FactoryBot.define do
47
47
  component { build(:meeting_component) }
48
48
  iframe_access_level { :all }
49
49
  iframe_embed_type { :none }
50
+ deleted_at { nil }
50
51
 
51
52
  author do
52
53
  component.try(:organization)
@@ -73,6 +74,8 @@ FactoryBot.define do
73
74
  trait :online do
74
75
  type_of_meeting { :online }
75
76
  online_meeting_url { "https://decidim.org" }
77
+ latitude { nil }
78
+ longitude { nil }
76
79
  end
77
80
 
78
81
  trait :hybrid do
@@ -183,6 +186,11 @@ FactoryBot.define do
183
186
  end
184
187
  end
185
188
 
189
+ factory :meeting_link, class: "Decidim::Meetings::MeetingLink" do
190
+ meeting
191
+ component
192
+ end
193
+
186
194
  factory :registration, class: "Decidim::Meetings::Registration" do
187
195
  transient do
188
196
  skip_injection { false }
@@ -4,17 +4,17 @@ shared_examples_for "a translated meeting event" do
4
4
  describe "translated notifications" do
5
5
  let(:en_body) { "This is Sparta!" }
6
6
  let(:body) { { en: en_body, machine_translations: { ca: "C'est Sparta!" } } }
7
- let(:participatory_process) { create :participatory_process, organization: }
7
+ let(:participatory_process) { create(:participatory_process, organization:) }
8
8
  let(:meeting_component) { create(:meeting_component, participatory_space: participatory_process) }
9
9
  let(:translatable) { true }
10
10
  let(:en_version) { resource.description["en"] }
11
11
  let(:machine_translated) { resource.description["machine_translations"]["ca"] }
12
12
 
13
13
  let(:resource) do
14
- create :meeting,
14
+ create(:meeting,
15
15
  component: meeting_component,
16
16
  title: { en: "A nice event", machine_translations: { ca: "Une belle event" } },
17
- description: body
17
+ description: body)
18
18
  end
19
19
 
20
20
  it_behaves_like "a translated event"
@@ -4,7 +4,7 @@ module Decidim
4
4
  # This holds the decidim-meetings version.
5
5
  module Meetings
6
6
  def self.version
7
- "0.29.2"
7
+ "0.30.0.rc2"
8
8
  end
9
9
  end
10
10
  end
@@ -16,6 +16,7 @@ module Decidim
16
16
  autoload :MeetingSerializer, "decidim/meetings/meeting_serializer"
17
17
  autoload :UserAnswersSerializer, "decidim/meetings/user_answers_serializer"
18
18
  autoload :DownloadYourDataUserAnswersSerializer, "decidim/meetings/download_your_data_user_answers_serializer"
19
+ autoload :SchemaOrgEventMeetingSerializer, "decidim/meetings/schema_org_event_meeting_serializer"
19
20
 
20
21
  include ActiveSupport::Configurable
21
22