decidim-meetings 0.29.2 → 0.30.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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 +106 -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.rc1"
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