From fab405a70851ae40c8aba361f9309fa69c62f81f Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Fri, 14 Feb 2025 12:23:48 +0100 Subject: [PATCH 01/14] add Waitlist enabled option --- .../app/cells/decidim/meetings/join_meeting_button_cell.rb | 6 ++++++ .../decidim/meetings/admin/update_registrations.rb | 1 + .../decidim/meetings/admin/meeting_registrations_form.rb | 2 ++ .../decidim/meetings/admin/registrations/_form.html.erb | 4 ++++ decidim-meetings/config/locales/en.yml | 3 +++ ...50214110525_add_waitlist_enabled_to_decidim_meetings.rb | 7 +++++++ 6 files changed, 23 insertions(+) create mode 100644 decidim-meetings/db/migrate/20250214110525_add_waitlist_enabled_to_decidim_meetings.rb diff --git a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb index afc05a17d6da1..06758018c0264 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb @@ -34,6 +34,12 @@ def i18n_join_text I18n.t("no_slots_available", scope: "decidim.meetings.meetings.show") end + def i18n_waiting_list_text + return if model.has_available_slots? + + I18n.t("waiting_list", scope: "decidim.meetings.meetings.show") + end + def i18n_confirm_text I18n.t("confirm", scope: "decidim.meetings.meetings.registration_confirm") end diff --git a/decidim-meetings/app/commands/decidim/meetings/admin/update_registrations.rb b/decidim-meetings/app/commands/decidim/meetings/admin/update_registrations.rb index ddd899a55fcc6..fd634340b894b 100644 --- a/decidim-meetings/app/commands/decidim/meetings/admin/update_registrations.rb +++ b/decidim-meetings/app/commands/decidim/meetings/admin/update_registrations.rb @@ -27,6 +27,7 @@ def attributes available_slots: form.available_slots, reserved_slots: form.reserved_slots, registration_terms: form.registration_terms, + waitlist_enabled: form.waitlist_enabled, customize_registration_email: form.customize_registration_email } extra_params.merge!(registration_email_custom_content: form.registration_email_custom_content) if form.customize_registration_email diff --git a/decidim-meetings/app/forms/decidim/meetings/admin/meeting_registrations_form.rb b/decidim-meetings/app/forms/decidim/meetings/admin/meeting_registrations_form.rb index d3c1acf2bf7c5..8281cba0fb505 100644 --- a/decidim-meetings/app/forms/decidim/meetings/admin/meeting_registrations_form.rb +++ b/decidim-meetings/app/forms/decidim/meetings/admin/meeting_registrations_form.rb @@ -14,6 +14,7 @@ class MeetingRegistrationsForm < Decidim::Form attribute :customize_registration_email, Boolean attribute :available_slots, Integer attribute :reserved_slots, Integer + attribute :waitlist_enabled, :boolean, default: false translatable_attribute :registration_terms, Decidim::Attributes::RichText translatable_attribute :registration_email_custom_content, Decidim::Attributes::RichText @@ -22,6 +23,7 @@ class MeetingRegistrationsForm < Decidim::Form validates :available_slots, :reserved_slots, presence: true, if: ->(form) { form.registrations_enabled? } validates :available_slots, numericality: { greater_than_or_equal_to: 0 }, if: ->(form) { form.registrations_enabled? && form.available_slots.present? } validates :reserved_slots, numericality: { greater_than_or_equal_to: 0 }, if: ->(form) { form.registrations_enabled? } + validates :waitlist_enabled, inclusion: { in: [true, false] } validates :reserved_slots, numericality: { less_than_or_equal_to: :available_slots }, if: lambda { |form| form.registrations_enabled? && form.reserved_slots.present? && diff --git a/decidim-meetings/app/views/decidim/meetings/admin/registrations/_form.html.erb b/decidim-meetings/app/views/decidim/meetings/admin/registrations/_form.html.erb index 85ea38a092432..482ac563885fe 100644 --- a/decidim-meetings/app/views/decidim/meetings/admin/registrations/_form.html.erb +++ b/decidim-meetings/app/views/decidim/meetings/admin/registrations/_form.html.erb @@ -18,6 +18,10 @@ <%= form.number_field :reserved_slots, help_text: t(".reserved_slots_help") %> +
+ <%= form.check_box :waitlist_enabled, help_text: t(".waitlist_enabled_help") %> +
+
<%= form.check_box :customize_registration_email %>
diff --git a/decidim-meetings/config/locales/en.yml b/decidim-meetings/config/locales/en.yml index 1b6e07e1f396f..93ac0a0d4b8a2 100644 --- a/decidim-meetings/config/locales/en.yml +++ b/decidim-meetings/config/locales/en.yml @@ -431,6 +431,8 @@ en: reserved_slots_help: Leave it to 0 if you do not have reserved slots. reserved_slots_less_than: Must be less than or equal to %{count} title: Registrations + waitlist_enabled: Waitlist enabled + waitlist_enabled_help: If enabled, the user will be able to join the waitlist when the available slots are full. update: invalid: There was a problem saving the registration settings. success: Meeting registrations settings successfully saved. @@ -592,6 +594,7 @@ en: cancel: Cancel confirm: Confirm show: + add_to_waitlist: Add to waitlist attendees: Attendees count contributions: Contributions count join: Register diff --git a/decidim-meetings/db/migrate/20250214110525_add_waitlist_enabled_to_decidim_meetings.rb b/decidim-meetings/db/migrate/20250214110525_add_waitlist_enabled_to_decidim_meetings.rb new file mode 100644 index 0000000000000..c0707d4366d61 --- /dev/null +++ b/decidim-meetings/db/migrate/20250214110525_add_waitlist_enabled_to_decidim_meetings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddWaitlistEnabledToDecidimMeetings < ActiveRecord::Migration[7.0] + def change + add_column :decidim_meetings_meetings, :waitlist_enabled, :boolean, default: false, null: false + end +end From 988a91a712207819808fa49daff8d8990a42ad4b Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Tue, 18 Feb 2025 10:58:08 +0100 Subject: [PATCH 02/14] add cell, migrations --- .../meetings/join_meeting_button_cell.rb | 6 -- .../meetings/join_waitlist_button/show.erb | 22 +++++++ .../meetings/join_waitlist_button_cell.rb | 41 +++++++++++++ .../decidim/meetings/join_waitlist.rb | 57 +++++++++++++++++++ .../meetings/registrations_controller.rb | 18 ++++++ .../models/decidim/meetings/registration.rb | 4 ++ .../meetings/meetings/_meeting_aside.html.erb | 6 ++ ...tions_to_decidim_meetings_registrations.rb | 8 +++ .../lib/decidim/meetings/engine.rb | 1 + 9 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb create mode 100644 decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb create mode 100644 decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb create mode 100644 decidim-meetings/db/migrate/20250214113208_add_status_to_registrations_to_decidim_meetings_registrations.rb diff --git a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb index 06758018c0264..afc05a17d6da1 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb @@ -34,12 +34,6 @@ def i18n_join_text I18n.t("no_slots_available", scope: "decidim.meetings.meetings.show") end - def i18n_waiting_list_text - return if model.has_available_slots? - - I18n.t("waiting_list", scope: "decidim.meetings.meetings.show") - end - def i18n_confirm_text I18n.t("confirm", scope: "decidim.meetings.meetings.registration_confirm") end diff --git a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb new file mode 100644 index 0000000000000..945ef8c5181d5 --- /dev/null +++ b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb @@ -0,0 +1,22 @@ +<% if model.registration_form_enabled? %> + <%= action_authorized_link_to( + :join_waitlist, + join_waitlist_meeting_registration_path(model), + class: button_classes + ) do %> + <%= icon(icon_name) %> + <%= i18n_join_waitlist_text %> + <% end %> +<% else %> + <% unless options[:hide_modal] %> + <%= render :registration_modal %> + <% end %> + <%= action_authorized_button_to( + :join_waitlist, + content_tag(:span, "#{icon(icon_name)} #{i18n_join_waitlist_text}".html_safe), + "#", + class: button_classes, + data: { "dialog-open": current_user.present? ? "meeting-registration-confirm-#{model.id}" : "loginModal" }, + resource: model + ) %> +<% end %> diff --git a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb new file mode 100644 index 0000000000000..1174b04b77e50 --- /dev/null +++ b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Decidim + module Meetings + # This cell renders the button to join a waitlist. + class JoinWaitlistButtonCell < Decidim::ViewModel + include MeetingCellsHelper + + def show + return unless model.waitlist_enabled? && !model.has_available_slots? && model.can_be_joined_by?(current_user) + return if model.has_registration_for?(current_user) + + render + end + + private + + def current_component + model.component + end + + def button_classes + "button button__sm button__transparent-secondary w-full" + end + + def i18n_join_waitlist_text + return if !model.waitlist_enabled? && model.has_available_slots? + + I18n.t("add_to_waitlist", scope: "decidim.meetings.meetings.show") + end + + def icon_name + "list-ordered" + end + + def registration_form + @registration_form ||= Decidim::Meetings::JoinMeetingForm.new + end + end + end +end diff --git a/decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb b/decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb new file mode 100644 index 0000000000000..ea1a18673bef5 --- /dev/null +++ b/decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Decidim + module Meetings + # This command is executed when the user joins a meeting waitlist. + class JoinWaitlist < Decidim::Command + delegate :current_user, to: :form + + def initialize(meeting, form) + @meeting = meeting + @user_group = Decidim::UserGroup.find_by(id: form.user_group_id) + @form = form + end + + def call + return broadcast(:invalid) unless can_join_waitlist? + return broadcast(:invalid_form) unless form.valid? + + meeting.with_lock do + create_waitlist_entry + send_waitlist_notification + end + + broadcast(:ok) + end + + private + + attr_reader :meeting, :user_group, :form + + def can_join_waitlist? + meeting.waitlist_enabled? && + !meeting.registrations.exists?(user: current_user) && + !meeting.has_available_slots? + end + + def create_waitlist_entry + @registration = Decidim::Meetings::Registration.create!( + meeting:, + user: current_user, + user_group:, + public_participation: form.public_participation, + status: :waiting_list + ) + end + + def send_waitlist_notification + Decidim::EventsManager.publish( + event: "decidim.events.meetings.meeting_waitlist_added", + event_class: Decidim::Meetings::MeetingWaitlistNotificationEvent, + resource: meeting, + affected_users: [current_user] + ) + end + end + end +end diff --git a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb index b9582ebf49c64..5334d775f9fa5 100644 --- a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb +++ b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb @@ -47,6 +47,24 @@ def create end end + def join_waitlist + enforce_permission_to(:join_waitlist, :meeting, meeting:) + + @form = JoinMeetingForm.from_params(params).with_context(current_user:) + + JoinWaitlist.call(meeting, @form) do + on(:ok) do + flash[:notice] = I18n.t("registrations.waitlist.success", scope: "decidim.meetings") + redirect_after_path + end + + on(:invalid) do + flash.now[:alert] = I18n.t("registrations.waitlist.invalid", scope: "decidim.meetings") + redirect_after_path + end + end + end + def destroy enforce_permission_to(:leave, :meeting, meeting:) diff --git a/decidim-meetings/app/models/decidim/meetings/registration.rb b/decidim-meetings/app/models/decidim/meetings/registration.rb index 090c7b8c2dd5c..5464cb39ef886 100644 --- a/decidim-meetings/app/models/decidim/meetings/registration.rb +++ b/decidim-meetings/app/models/decidim/meetings/registration.rb @@ -14,9 +14,13 @@ class Registration < Meetings::ApplicationRecord validates :code, uniqueness: { allow_blank: true, scope: :meeting } validates :code, presence: true, on: :create + enum status: { registered: "registered", waitlisted: "waitlisted" }, _prefix: true + before_validation :generate_code, on: :create scope :public_participant, -> { where(decidim_user_group_id: nil, public_participation: true) } + scope :registered, -> { where(status: :registered) } + scope :waiting_list, -> { where(status: :waiting_list).order(:created_at) } def self.user_collection(user) where(decidim_user_id: user.id) diff --git a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb index f34a63c227c75..23e8426faea1e 100644 --- a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb +++ b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb @@ -6,6 +6,12 @@ <% end %> +<% if meeting.waitlist_enabled? && !meeting.has_available_slots? && meeting.can_be_joined_by?(current_user) %> +
+ <%= cell "decidim/meetings/join_waitlist_button", meeting, hide_modal: local_assigns[:hide_modal] %> +
+<% end %> + <% if (meeting.closed? && meeting.closing_visible?) || (registration.present? && registration.meeting.component.settings.registration_code_enabled) || (meeting.services.any?) %>
<% if meeting.closed? && meeting.closing_visible? %> diff --git a/decidim-meetings/db/migrate/20250214113208_add_status_to_registrations_to_decidim_meetings_registrations.rb b/decidim-meetings/db/migrate/20250214113208_add_status_to_registrations_to_decidim_meetings_registrations.rb new file mode 100644 index 0000000000000..4bcce525e5e8c --- /dev/null +++ b/decidim-meetings/db/migrate/20250214113208_add_status_to_registrations_to_decidim_meetings_registrations.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddStatusToRegistrationsToDecidimMeetingsRegistrations < ActiveRecord::Migration[7.0] + def change + add_column :decidim_meetings_registrations, :status, :string, default: "registered", null: false + add_index :decidim_meetings_registrations, :status + end +end diff --git a/decidim-meetings/lib/decidim/meetings/engine.rb b/decidim-meetings/lib/decidim/meetings/engine.rb index 3531cc8d9c56d..7412dd3841f51 100644 --- a/decidim-meetings/lib/decidim/meetings/engine.rb +++ b/decidim-meetings/lib/decidim/meetings/engine.rb @@ -26,6 +26,7 @@ class Engine < ::Rails::Engine get :decline_invitation get :join, action: :show post :answer + post :join_waitlist end end resources :versions, only: [:show] From 89e69e63cdf28ae637af6c028ec9a95660f9b70c Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Tue, 18 Feb 2025 14:24:57 +0100 Subject: [PATCH 03/14] leave waiting list button, move_from_waitlist! --- .../cancel_registration_meeting_button/show.erb | 2 +- .../cancel_registration_meeting_button_cell.rb | 9 +++++++++ .../meetings/join_waitlist_button_cell.rb | 2 +- .../commands/decidim/meetings/join_waitlist.rb | 5 +++-- .../commands/decidim/meetings/leave_meeting.rb | 17 +++++++++++++++++ .../meetings/registrations_controller.rb | 11 ++++++----- .../app/models/decidim/meetings/meeting.rb | 4 ++-- .../app/models/decidim/meetings/registration.rb | 6 ++++-- .../meetings/meetings/_meeting_aside.html.erb | 2 +- decidim-meetings/config/locales/en.yml | 6 +++++- decidim-meetings/lib/decidim/meetings/engine.rb | 3 ++- 11 files changed, 51 insertions(+), 16 deletions(-) diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb index d4b8ee70387db..d0bc2bc1482ad 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb @@ -7,6 +7,6 @@ class: button_classes, data: { "dialog-open": "meeting-cancelation-confirm-#{model.id}" } ) do %> - <%= t("leave", scope: "decidim.meetings.meetings.show") %> + <%= cancel_button_text %> <%= icon icon_name %> <% end %> diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb index c4a568be0c679..d17264549591c 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb @@ -19,6 +19,15 @@ def current_component model.component end + def registration_status + model.registrations.find_by(user: current_user)&.status + end + + def cancel_button_text + scope = "decidim.meetings.meetings.show" + registration_status == "on_waiting_list" ? I18n.t("cancel_waitlist", scope:) : I18n.t("leave", scope:) + end + def button_classes "button button__sm button__transparent-secondary w-full" end diff --git a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb index 1174b04b77e50..57986bfb72302 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb @@ -30,7 +30,7 @@ def i18n_join_waitlist_text end def icon_name - "list-ordered" + "clockwise-line" end def registration_form diff --git a/decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb b/decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb index ea1a18673bef5..84746211e4229 100644 --- a/decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb +++ b/decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb @@ -40,14 +40,15 @@ def create_waitlist_entry user: current_user, user_group:, public_participation: form.public_participation, - status: :waiting_list + status: :on_waiting_list ) end + # TODO: Implement this method def send_waitlist_notification Decidim::EventsManager.publish( event: "decidim.events.meetings.meeting_waitlist_added", - event_class: Decidim::Meetings::MeetingWaitlistNotificationEvent, + event_class: Decidim::Meetings::MeetingRegistrationNotificationEvent, resource: meeting, affected_users: [current_user] ) diff --git a/decidim-meetings/app/commands/decidim/meetings/leave_meeting.rb b/decidim-meetings/app/commands/decidim/meetings/leave_meeting.rb index c592030d8e164..67c963ae51079 100644 --- a/decidim-meetings/app/commands/decidim/meetings/leave_meeting.rb +++ b/decidim-meetings/app/commands/decidim/meetings/leave_meeting.rb @@ -24,6 +24,7 @@ def call destroy_registration destroy_questionnaire_answers decrement_score + move_from_waitlist! end broadcast(:ok) end @@ -50,6 +51,22 @@ def destroy_questionnaire_answers def decrement_score Decidim::Gamification.decrement_score(@user, :attended_meetings) end + + def move_from_waitlist! + return unless @meeting.remaining_slots.positive? + + on_waiting_list_user = @meeting.registrations.on_waiting_list.order(:created_at).first + return unless on_waiting_list_user + + on_waiting_list_user.update!(status: :registered) + + Decidim::EventsManager.publish( + event: "decidim.events.meetings.meeting_registration_confirmed", + event_class: Decidim::Meetings::MeetingRegistrationNotificationEvent, + resource: @meeting, + affected_users: [on_waiting_list_user.user] + ) + end end end end diff --git a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb index 5334d775f9fa5..8e97a7ddecd76 100644 --- a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb +++ b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb @@ -10,15 +10,16 @@ def answer enforce_permission_to(:join, :meeting, meeting:) @form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token:) + command = params[:waitlist] ? JoinWaitlist : JoinMeeting - JoinMeeting.call(meeting, @form) do + command.call(meeting, @form) do on(:ok) do - flash[:notice] = I18n.t("registrations.create.success", scope: "decidim.meetings") + flash[:notice] = I18n.t("registrations.#{params[:waitlist] ? "waitlist" : "create"}.success", scope: "decidim.meetings") redirect_to after_answer_path end on(:invalid) do - flash.now[:alert] = I18n.t("registrations.create.invalid", scope: "decidim.meetings") + flash.now[:alert] = I18n.t("registrations.#{params[:waitlist] ? "waitlist" : "create"}.invalid", scope: "decidim.meetings") render template: "decidim/forms/questionnaires/show" end @@ -98,7 +99,7 @@ def decline_invitation end def allow_answers? - meeting.registrations_enabled? && meeting.registration_form_enabled? && meeting.has_available_slots? + meeting.registrations_enabled? && meeting.registration_form_enabled? && (meeting.has_available_slots? || meeting.waitlist_enabled?) end def after_answer_path @@ -108,7 +109,7 @@ def after_answer_path # You can implement this method in your controller to change the URL # where the questionnaire will be submitted. def update_url - answer_meeting_registration_path(meeting_id: meeting.id) + answer_meeting_registration_path(meeting_id: meeting.id, waitlist: request.path.include?("join_waitlist")) end def questionnaire_for diff --git a/decidim-meetings/app/models/decidim/meetings/meeting.rb b/decidim-meetings/app/models/decidim/meetings/meeting.rb index 3a7e12d7400e8..62b844187c10a 100644 --- a/decidim-meetings/app/models/decidim/meetings/meeting.rb +++ b/decidim-meetings/app/models/decidim/meetings/meeting.rb @@ -208,11 +208,11 @@ def emendation? def has_available_slots? return true if available_slots.zero? - (available_slots - reserved_slots) > registrations.count + (available_slots - reserved_slots) > registrations.registered.count end def remaining_slots - available_slots - reserved_slots - registrations.count + available_slots - reserved_slots - registrations.registered.count end def has_registration_for?(user) diff --git a/decidim-meetings/app/models/decidim/meetings/registration.rb b/decidim-meetings/app/models/decidim/meetings/registration.rb index 5464cb39ef886..f19d08a56badf 100644 --- a/decidim-meetings/app/models/decidim/meetings/registration.rb +++ b/decidim-meetings/app/models/decidim/meetings/registration.rb @@ -12,9 +12,9 @@ class Registration < Meetings::ApplicationRecord validates :user, uniqueness: { scope: :meeting } validates :code, uniqueness: { allow_blank: true, scope: :meeting } - validates :code, presence: true, on: :create + validates :code, presence: true, on: :create, if: -> { status == "registered" } - enum status: { registered: "registered", waitlisted: "waitlisted" }, _prefix: true + enum status: { registered: "registered", on_waiting_list: "on_waiting_list" } before_validation :generate_code, on: :create @@ -45,6 +45,8 @@ def validated? private def generate_code + return if on_waiting_list? + self[:code] ||= calculate_registration_code end diff --git a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb index 23e8426faea1e..466cf77f651b2 100644 --- a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb +++ b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb @@ -41,7 +41,7 @@ <% end %> <% end %> - <% if registration.present? && registration.meeting.component.settings.registration_code_enabled %> + <% if registration.present? && registration.meeting.component.settings.registration_code_enabled && registration.registered? %> <%= render layout: "decidim/meetings/layouts/aside_block", locals: { emoji: "coupon-line" } do %>

<%= registration_code_help_text %>

<%= registration.code %>
diff --git a/decidim-meetings/config/locales/en.yml b/decidim-meetings/config/locales/en.yml index 93ac0a0d4b8a2..e948f9b6be128 100644 --- a/decidim-meetings/config/locales/en.yml +++ b/decidim-meetings/config/locales/en.yml @@ -594,11 +594,12 @@ en: cancel: Cancel confirm: Confirm show: - add_to_waitlist: Add to waitlist + add_to_waitlist: Join the waiting list attendees: Attendees count contributions: Contributions count join: Register leave: Cancel your registration + cancel_waitlist: Leave the waiting list leave_confirmation: Are you sure you want to cancel your registration for this meeting? link_available_soon: Link available soon link_closed: The link to join the meeting will be available a few minutes before it starts @@ -702,6 +703,9 @@ en: destroy: invalid: There was a problem leaving this meeting. success: You have left the meeting successfully. + waitlist: + invalid: There was a problem joining the waiting list. + success: You have joined the waiting list successfully. type_of_meeting: hybrid: Hybrid in_person: In person diff --git a/decidim-meetings/lib/decidim/meetings/engine.rb b/decidim-meetings/lib/decidim/meetings/engine.rb index 7412dd3841f51..36665f08145e7 100644 --- a/decidim-meetings/lib/decidim/meetings/engine.rb +++ b/decidim-meetings/lib/decidim/meetings/engine.rb @@ -26,7 +26,7 @@ class Engine < ::Rails::Engine get :decline_invitation get :join, action: :show post :answer - post :join_waitlist + get :join_waitlist, action: :show end end resources :versions, only: [:show] @@ -72,6 +72,7 @@ class Engine < ::Rails::Engine Decidim.icons.register(name: "bill-line", icon: "bill-line", category: "system", description: "", engine: :meetings) Decidim.icons.register(name: "add-box-line", icon: "add-box-line", category: "system", description: "", engine: :meetings) Decidim.icons.register(name: "calendar-close-line", icon: "calendar-close-line", category: "system", description: "", engine: :meetings) + Decidim.icons.register(name: "clockwise-line", icon: "clockwise-line", category: "system", description: "", engine: :meetings) end initializer "decidim_meetings.content_processors" do |_app| From e4889d2f0aded833d0817b76e890848686a9ee95 Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Tue, 18 Feb 2025 17:56:23 +0100 Subject: [PATCH 04/14] registration_modal --- .../cancel_registration_meeting_button_cell.rb | 3 +-- .../join_meeting_button/registration_modal.erb | 2 +- .../cells/decidim/meetings/join_meeting_button_cell.rb | 4 ++++ .../decidim/meetings/join_waitlist_button/show.erb | 10 ++++++---- .../decidim/meetings/join_waitlist_button_cell.rb | 4 ++++ .../app/permissions/decidim/meetings/permissions.rb | 9 +++++++++ decidim-meetings/lib/decidim/meetings/engine.rb | 1 + 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb index d17264549591c..c328ac8e6cbe8 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb @@ -24,8 +24,7 @@ def registration_status end def cancel_button_text - scope = "decidim.meetings.meetings.show" - registration_status == "on_waiting_list" ? I18n.t("cancel_waitlist", scope:) : I18n.t("leave", scope:) + I18n.t(registration_status == "on_waiting_list" ? "cancel_waitlist" : "leave", scope: "decidim.meetings.meetings.show") end def button_classes diff --git a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb index 9c2536cf18068..da05b241e4e8c 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb +++ b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb @@ -1,5 +1,5 @@ <%= decidim_modal id: "meeting-registration-confirm-#{model.id}", class: "meeting__registration-modal" do %> - <%= decidim_form_for(registration_form, url: meeting_registration_path(model), method: :post) do |form| %> + <%= decidim_form_for(registration_form, url: model.has_available_slots? ? meeting_registration_path(model) : join_waitlist_meeting_registration_path(model), method: :post) do |form| %>
<%= icon "login-circle-line" %>

<%= t("join", scope: "decidim.meetings.meetings.show") %>

diff --git a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb index afc05a17d6da1..75de873e82281 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button_cell.rb @@ -12,6 +12,10 @@ def show render end + def registration_modal + render + end + private delegate :current_user, to: :controller, prefix: false diff --git a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb index 945ef8c5181d5..14d6a1ec9182a 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb +++ b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button/show.erb @@ -4,19 +4,21 @@ join_waitlist_meeting_registration_path(model), class: button_classes ) do %> - <%= icon(icon_name) %> <%= i18n_join_waitlist_text %> + <%= icon(icon_name) %> <% end %> <% else %> <% unless options[:hide_modal] %> - <%= render :registration_modal %> + <%= render_registration_modal %> <% end %> <%= action_authorized_button_to( :join_waitlist, - content_tag(:span, "#{icon(icon_name)} #{i18n_join_waitlist_text}".html_safe), "#", class: button_classes, data: { "dialog-open": current_user.present? ? "meeting-registration-confirm-#{model.id}" : "loginModal" }, resource: model - ) %> + ) do %> + <%= i18n_join_waitlist_text %> + <%= icon(icon_name) %> + <% end %> <% end %> diff --git a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb index 57986bfb72302..b5325c8ed5458 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/join_waitlist_button_cell.rb @@ -13,6 +13,10 @@ def show render end + def render_registration_modal + cell(JoinMeetingButtonCell, model, current_user: current_user).call(:registration_modal) + end + private def current_component diff --git a/decidim-meetings/app/permissions/decidim/meetings/permissions.rb b/decidim-meetings/app/permissions/decidim/meetings/permissions.rb index e8b48a7a131ca..6487fe643017c 100644 --- a/decidim-meetings/app/permissions/decidim/meetings/permissions.rb +++ b/decidim-meetings/app/permissions/decidim/meetings/permissions.rb @@ -30,6 +30,8 @@ def permissions case permission_action.action when :join toggle_allow(can_join_meeting?) + when :join_waitlist + toggle_allow(can_join_waitlist?) when :leave toggle_allow(can_leave_meeting?) when :decline_invitation @@ -74,6 +76,13 @@ def can_join_meeting? authorized?(:join, resource: meeting) end + def can_join_waitlist? + meeting.waitlist_enabled? && + !meeting.has_available_slots? && + !meeting.has_registration_for?(user) && + authorized?(:join_waitlist, resource: meeting) + end + def can_leave_meeting? meeting.registrations_enabled? end diff --git a/decidim-meetings/lib/decidim/meetings/engine.rb b/decidim-meetings/lib/decidim/meetings/engine.rb index 36665f08145e7..0e70e0940c9d5 100644 --- a/decidim-meetings/lib/decidim/meetings/engine.rb +++ b/decidim-meetings/lib/decidim/meetings/engine.rb @@ -27,6 +27,7 @@ class Engine < ::Rails::Engine get :join, action: :show post :answer get :join_waitlist, action: :show + post :join_waitlist end end resources :versions, only: [:show] From 7fd36203cd05ef2e8c66852fca1b175b015255c3 Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Wed, 19 Feb 2025 10:34:37 +0100 Subject: [PATCH 05/14] cancel waitlist modal --- .../cancelation_modal.erb | 11 +++++--- ...cancel_registration_meeting_button_cell.rb | 26 ++++++++++++++++++- .../meetings/registrations_controller.rb | 8 ++++-- decidim-meetings/config/locales/en.yml | 15 ++++++++--- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb index 2d49bb2bd3892..4f14fd1dc6f25 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb @@ -2,10 +2,13 @@ <%= decidim_form_for(registration_form, url: meeting_registration_path(model), method: :delete) do |form| %>
<%= icon "door-open-line" %> -

<%= t("leave", scope: "decidim.meetings.meetings.show") %>

+

+ <%= i18n_modal_title %> +

- -
<%= t("leave_confirmation", scope: "decidim.meetings.meetings.show") %>
+
+ <%= i18n_modal_confirmation_text %> +
@@ -13,7 +16,7 @@ <%= t("close", scope: "decidim.shared.flag_modal") %>
diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb index c328ac8e6cbe8..a0e89a4f32635 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb @@ -23,8 +23,32 @@ def registration_status model.registrations.find_by(user: current_user)&.status end + def action_keys + if registration_status == "on_waiting_list" + { + button: "cancel_waitlist", + modal_title: "waitlist_title", + modal_confirmation: "waitlist_confirmation" + } + else + { + button: "leave", + modal_title: "registration_title", + modal_confirmation: "registration_confirmation" + } + end + end + def cancel_button_text - I18n.t(registration_status == "on_waiting_list" ? "cancel_waitlist" : "leave", scope: "decidim.meetings.meetings.show") + I18n.t(action_keys[:button], scope: "decidim.meetings.meetings.show") + end + + def i18n_modal_title + I18n.t(action_keys[:modal_title], scope: "decidim.meetings.meetings.show") + end + + def i18n_modal_confirmation_text + I18n.t(action_keys[:modal_confirmation], scope: "decidim.meetings.meetings.show") end def button_classes diff --git a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb index 8e97a7ddecd76..d4a108e49431b 100644 --- a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb +++ b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb @@ -71,12 +71,12 @@ def destroy LeaveMeeting.call(meeting, current_user) do on(:ok) do - flash[:notice] = I18n.t("registrations.destroy.success", scope: "decidim.meetings") + flash[:notice] = I18n.t("registrations.destroy.#{registration.status}.success", scope: "decidim.meetings") redirect_after_path end on(:invalid) do - flash.now[:alert] = I18n.t("registrations.destroy.invalid", scope: "decidim.meetings") + flash.now[:alert] = I18n.t("registrations.destroy.#{registration.status}.invalid", scope: "decidim.meetings") redirect_after_path end end @@ -122,6 +122,10 @@ def meeting @meeting ||= Meeting.where(component: current_component).find(params[:meeting_id]) end + def registration + @registration ||= meeting.registrations.find_by(user: current_user) + end + def redirect_after_path redirect_to meeting_path(meeting) end diff --git a/decidim-meetings/config/locales/en.yml b/decidim-meetings/config/locales/en.yml index e948f9b6be128..39cccd8388e60 100644 --- a/decidim-meetings/config/locales/en.yml +++ b/decidim-meetings/config/locales/en.yml @@ -596,11 +596,10 @@ en: show: add_to_waitlist: Join the waiting list attendees: Attendees count + cancel_waitlist: Leave the waiting list contributions: Contributions count join: Register leave: Cancel your registration - cancel_waitlist: Leave the waiting list - leave_confirmation: Are you sure you want to cancel your registration for this meeting? link_available_soon: Link available soon link_closed: The link to join the meeting will be available a few minutes before it starts live_event: This meeting is happening right now @@ -610,13 +609,17 @@ en: organizations: Attending organizations redirect_notice: This meeting is part of another space, so you have been moved to %{current_space_name}.
If you prefer, you can go back to %{previous_space_name}. registration_code_help_text: Your registration code + registration_confirmation: Are you sure you want to cancel your registration for this meeting? registration_state: validated: VALIDATED validation_pending: VALIDATION PENDING + registration_title: Cancel your registration remaining_slots: one: "%{count} slot remaining" other: "%{count} slots remaining" visit_finished: View past meeting + waitlist_confirmation: Are you sure you want to cancel your waitlist registration for this meeting? + waitlist_title: Cancel your waitlist registration withdraw_btn_hint: You can withdraw your meeting if you change your mind. The meeting is not deleted, it will appear in the list of withdrawn meetings. withdraw_confirmation_html: Are you sure you want to withdraw this meeting?

This action cannot be cancelled! withdraw_meeting: Withdraw @@ -701,8 +704,12 @@ en: invalid: There was a problem declining the invitation. success: You have declined the invitation successfully. destroy: - invalid: There was a problem leaving this meeting. - success: You have left the meeting successfully. + on_waiting_list: + invalid: There was a problem leaving the waiting list. + success: You have left the waiting list successfully. + registered: + invalid: There was a problem leaving this meeting. + success: You have left the meeting successfully. waitlist: invalid: There was a problem joining the waiting list. success: You have joined the waiting list successfully. From 1672e51ea06d5a048c014b481d84b69bc0ba1ce9 Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Tue, 25 Feb 2025 15:22:43 +0100 Subject: [PATCH 06/14] add tests --- .../meetings/registrations_controller.rb | 14 +- .../app/models/decidim/meetings/meeting.rb | 2 +- .../lib/decidim/meetings/test/factories.rb | 1 + .../admin/update_registrations_spec.rb | 4 +- .../spec/commands/join_waitlist_spec.rb | 100 ++++++++++ .../meetings/registrations_controller_spec.rb | 181 ++++++++++++++++++ 6 files changed, 294 insertions(+), 8 deletions(-) create mode 100644 decidim-meetings/spec/commands/join_waitlist_spec.rb create mode 100644 decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb diff --git a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb index d4a108e49431b..2756d11b78da7 100644 --- a/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb +++ b/decidim-meetings/app/controllers/decidim/meetings/registrations_controller.rb @@ -10,22 +10,24 @@ def answer enforce_permission_to(:join, :meeting, meeting:) @form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token:) - command = params[:waitlist] ? JoinWaitlist : JoinMeeting + + waitlist = ActiveModel::Type::Boolean.new.cast(params[:waitlist]) + command = waitlist ? JoinWaitlist : JoinMeeting command.call(meeting, @form) do on(:ok) do - flash[:notice] = I18n.t("registrations.#{params[:waitlist] ? "waitlist" : "create"}.success", scope: "decidim.meetings") + flash[:notice] = I18n.t("registrations.#{waitlist ? "waitlist" : "create"}.success", scope: "decidim.meetings") redirect_to after_answer_path end on(:invalid) do - flash.now[:alert] = I18n.t("registrations.#{params[:waitlist] ? "waitlist" : "create"}.invalid", scope: "decidim.meetings") - render template: "decidim/forms/questionnaires/show" + flash.now[:alert] = I18n.t("registrations.#{waitlist ? "waitlist" : "create"}.invalid", scope: "decidim.meetings") + render template: "decidim/forms/questionnaires/show", status: :unprocessable_entity end on(:invalid_form) do flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope) - render template: "decidim/forms/questionnaires/show" + render template: "decidim/forms/questionnaires/show", status: :unprocessable_entity end end end @@ -109,7 +111,7 @@ def after_answer_path # You can implement this method in your controller to change the URL # where the questionnaire will be submitted. def update_url - answer_meeting_registration_path(meeting_id: meeting.id, waitlist: request.path.include?("join_waitlist")) + answer_meeting_registration_path(meeting_id: meeting.id, waitlist: params[:waitlist] || request.path.include?("join_waitlist")) end def questionnaire_for diff --git a/decidim-meetings/app/models/decidim/meetings/meeting.rb b/decidim-meetings/app/models/decidim/meetings/meeting.rb index 62b844187c10a..9f4071506b7cf 100644 --- a/decidim-meetings/app/models/decidim/meetings/meeting.rb +++ b/decidim-meetings/app/models/decidim/meetings/meeting.rb @@ -206,7 +206,7 @@ def emendation? end def has_available_slots? - return true if available_slots.zero? + return false if available_slots.zero? (available_slots - reserved_slots) > registrations.registered.count end diff --git a/decidim-meetings/lib/decidim/meetings/test/factories.rb b/decidim-meetings/lib/decidim/meetings/test/factories.rb index 7ad7fbfbde41b..1ee2b74b9e113 100644 --- a/decidim-meetings/lib/decidim/meetings/test/factories.rb +++ b/decidim-meetings/lib/decidim/meetings/test/factories.rb @@ -44,6 +44,7 @@ registration_terms { generate_localized_description(:meeting_registration_terms, skip_injection:) } registration_type { :on_this_platform } type_of_meeting { :in_person } + waitlist_enabled { false } component { build(:meeting_component) } iframe_access_level { :all } iframe_embed_type { :none } diff --git a/decidim-meetings/spec/commands/admin/update_registrations_spec.rb b/decidim-meetings/spec/commands/admin/update_registrations_spec.rb index 5858b5145a096..e841c8373a65f 100644 --- a/decidim-meetings/spec/commands/admin/update_registrations_spec.rb +++ b/decidim-meetings/spec/commands/admin/update_registrations_spec.rb @@ -28,6 +28,7 @@ module Decidim::Meetings registration_form_enabled:, available_slots:, reserved_slots:, + waitlist_enabled: true, customize_registration_email:, registration_email_custom_content:, registration_terms:, @@ -57,12 +58,13 @@ module Decidim::Meetings expect(meeting.customize_registration_email).to be true expect(meeting.registration_email_custom_content).to eq(registration_email_custom_content) expect(translated(meeting.registration_terms)).to eq "A legal text" + expect(meeting.waitlist_enabled).to be true end end describe "events" do let(:user) { create(:user, :confirmed, organization: meeting.organization) } - let!(:follow) { create(:follow, followable: meeting, user:) } + let!(:follow) { create(:follow, followable: meeting, user: user) } context "when registrations are enabled" do it "notifies the change" do diff --git a/decidim-meetings/spec/commands/join_waitlist_spec.rb b/decidim-meetings/spec/commands/join_waitlist_spec.rb new file mode 100644 index 0000000000000..b9c7d8fa008f1 --- /dev/null +++ b/decidim-meetings/spec/commands/join_waitlist_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Meetings + describe JoinWaitlist do + subject { described_class.new(meeting, form) } + + let(:organization) { create(:organization) } + let(:participatory_process) { create(:participatory_process, organization: organization) } + let(:component) { create(:component, manifest_name: :meetings, participatory_space: participatory_process) } + let(:available_slots) { 2 } + let(:waitlist_enabled) { true } + let(:meeting) do + create(:meeting, + component: component, + waitlist_enabled: waitlist_enabled, + registrations_enabled: true, + available_slots: available_slots) + end + + let(:user) { create(:user, :confirmed, organization: organization, notifications_sending_frequency: "none") } + let(:user_group) { create(:user_group) } + let(:form_params) { { user_group_id: user_group.id } } + let(:form) do + Decidim::Meetings::JoinMeetingForm.from_params(form_params).with_context(current_user: user) + end + + let(:waitlist_notification) do + { + event: "decidim.events.meetings.meeting_waitlist_added", + event_class: MeetingRegistrationNotificationEvent, + resource: meeting, + affected_users: [user] + } + end + + context "when all conditions are met" do + before do + create_list(:registration, available_slots, meeting: meeting, status: :registered) + end + + it "broadcasts ok" do + expect { subject.call }.to broadcast(:ok) + end + + it "creates a waitlist registration with correct attributes" do + expect { subject.call }.to change(Registration, :count).by(1) + last_registration = Registration.last + expect(last_registration.user).to eq(user) + expect(last_registration.meeting).to eq(meeting) + expect(last_registration.status).to eq("on_waiting_list") + end + + it "publishes waitlist notification" do + expect(Decidim::EventsManager).to receive(:publish).with(waitlist_notification) + subject.call + end + end + + context "when waitlist is disabled" do + let(:waitlist_enabled) { false } + + before do + create_list(:registration, available_slots, meeting: meeting, status: :registered) + end + + it "broadcasts invalid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the user is already registered" do + before do + create(:registration, meeting: meeting, user: user, status: :registered) + end + + it "broadcasts invalid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when meeting has available slots" do + it "broadcasts invalid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the form is invalid" do + before do + create_list(:registration, available_slots, meeting: meeting, status: :registered) + allow(form).to receive(:valid?).and_return(false) + end + + it "broadcasts invalid_form" do + expect { subject.call }.to broadcast(:invalid_form) + end + end + end +end diff --git a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb new file mode 100644 index 0000000000000..79f3c29a2db93 --- /dev/null +++ b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Meetings + describe RegistrationsController do + routes { Decidim::Meetings::Engine.routes } + + let(:organization) { create(:organization) } + let(:user) { create(:user, :confirmed, organization:) } + let(:participatory_process) { create(:participatory_process, organization:) } + let(:component) { create(:meeting_component, participatory_space: participatory_process) } + let(:meeting) { create(:meeting, :published, component:, registrations_enabled: true, available_slots: 10) } + + before do + request.env["decidim.current_organization"] = organization + request.env["decidim.current_participatory_space"] = participatory_process + request.env["decidim.current_component"] = component + end + + describe "POST create" do + let(:params) { { meeting_id: meeting.id } } + + context "when user is authenticated" do + before { sign_in user } + + context "with available slots" do + it "creates registration and redirects" do + expect do + post :create, params: params + end.to change(Registration, :count).by(1) + + expect(flash[:notice]).to eq(I18n.t("registrations.create.success", scope: "decidim.meetings")) + expect(response).to redirect_to(meeting_path(meeting)) + end + end + + context "when no available slots" do + let(:meeting) { create(:meeting, component:, available_slots: 0, registrations_enabled: true, waitlist_enabled: false) } + + it "shows error message" do + post :create, params: params + + expect(flash[:alert]).to eq(I18n.t("registrations.create.invalid", scope: "decidim.meetings")) + expect(response).to redirect_to(meeting_path(meeting)) + end + end + end + + context "when user not authenticated" do + it "redirects to login" do + post :create, params: params + expect(response).to redirect_to("/users/sign_in") + end + end + end + + describe "POST answer" do + let(:questionnaire) { create(:questionnaire, :with_questions, questionnaire_for: meeting) } + let(:question) { questionnaire.questions.first } + let(:params) do + { + meeting_id: meeting.id, + waitlist: waitlist, + questionnaire: { + responses: [ + { + body: "Answer", + question_id: question.id + } + ], + tos_agreement: true + } + } + end + let(:waitlist) { false } + + before do + sign_in user + meeting.update!( + registrations_enabled: true, + registration_form_enabled: true, + questionnaire: questionnaire + ) + end + + context "with valid params" do + before do + if waitlist + meeting.update!(waitlist_enabled: true, available_slots: 0) + else + meeting.update!(available_slots: 5) + end + end + + context "when joining directly" do + it "answers questionnaire and redirects" do + puts "Test Params: #{params.inspect}" + + expect do + post :answer, params: params + end.to change { meeting.registrations.count }.by(1) + + expect(flash[:notice]).to eq(I18n.t("registrations.create.success", scope: "decidim.meetings")) + expect(response).to redirect_to(meeting_path(meeting)) + end + end + + context "when joining waitlist" do + let(:waitlist) { true } + + it "adds user to waitlist and redirects" do + expect do + post :answer, params: params + end.to change { meeting.registrations.where(status: :on_waiting_list).count }.by(1) + + expect(flash[:notice]).to eq(I18n.t("registrations.waitlist.success", scope: "decidim.meetings")) + expect(response).to redirect_to(meeting_path(meeting)) + end + end + end + + context "with invalid params" do + let(:params) do + { + meeting_id: meeting.id, + waitlist: waitlist, + questionnaire: { responses: [] } # Пустые ответы + } + end + + before do + meeting.update!(available_slots: 0, waitlist_enabled: waitlist, registrations_enabled: true) + end + + it "shows error message" do + post :answer, params: params + + expected_key = waitlist ? "waitlist" : "create" + expect(flash[:alert]).to eq(I18n.t("registrations.#{expected_key}.invalid", scope: "decidim.meetings")) + expect(response).to render_template("decidim/forms/questionnaires/show") + end + end + end + + describe "POST join_waitlist" do + let(:meeting) { create(:meeting, component:, available_slots: available_slots, waitlist_enabled: true) } + let(:params) { { meeting_id: meeting.id } } + let(:available_slots) { 0 } + + before { sign_in user } + + context "when meeting has no available slots" do + it "adds user to waitlist" do + expect do + post :join_waitlist, params: params + end.to change(Registration.on_waiting_list, :count).by(1) + + expect(flash[:notice]).to eq(I18n.t("registrations.waitlist.success", scope: "decidim.meetings")) + expect(response).to redirect_to(meeting_path(meeting)) + end + end + end + + describe "DELETE destroy" do + let!(:registration) { create(:registration, meeting:, user:) } + let(:params) { { meeting_id: meeting.id } } + + before { sign_in user } + + it "destroys registration" do + expect do + delete :destroy, params: params + end.to change(Registration, :count).by(-1) + + expect(flash[:notice]).to match(/successfully/) + expect(response).to redirect_to(meeting_path(meeting)) + end + end + end +end From ed5d4037c49a0fad243ba0ca6974a0c64b611e83 Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Wed, 26 Feb 2025 12:28:59 +0100 Subject: [PATCH 07/14] fix tests --- .../app/models/decidim/meetings/meeting.rb | 2 +- .../meetings/registrations_controller_spec.rb | 29 +++++-------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/decidim-meetings/app/models/decidim/meetings/meeting.rb b/decidim-meetings/app/models/decidim/meetings/meeting.rb index 9f4071506b7cf..62b844187c10a 100644 --- a/decidim-meetings/app/models/decidim/meetings/meeting.rb +++ b/decidim-meetings/app/models/decidim/meetings/meeting.rb @@ -206,7 +206,7 @@ def emendation? end def has_available_slots? - return false if available_slots.zero? + return true if available_slots.zero? (available_slots - reserved_slots) > registrations.registered.count end diff --git a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb index 79f3c29a2db93..554df129ec6fb 100644 --- a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb +++ b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb @@ -36,7 +36,7 @@ module Decidim::Meetings end context "when no available slots" do - let(:meeting) { create(:meeting, component:, available_slots: 0, registrations_enabled: true, waitlist_enabled: false) } + let!(:registrations) { create_list(:registration, 10, meeting: meeting) } it "shows error message" do post :create, params: params @@ -85,18 +85,8 @@ module Decidim::Meetings end context "with valid params" do - before do - if waitlist - meeting.update!(waitlist_enabled: true, available_slots: 0) - else - meeting.update!(available_slots: 5) - end - end - context "when joining directly" do it "answers questionnaire and redirects" do - puts "Test Params: #{params.inspect}" - expect do post :answer, params: params end.to change { meeting.registrations.count }.by(1) @@ -107,6 +97,8 @@ module Decidim::Meetings end context "when joining waitlist" do + let(:meeting) { create(:meeting, component:, available_slots: 10, waitlist_enabled: true) } + let!(:registrations) { create_list(:registration, 10, meeting: meeting) } let(:waitlist) { true } it "adds user to waitlist and redirects" do @@ -124,29 +116,24 @@ module Decidim::Meetings let(:params) do { meeting_id: meeting.id, - waitlist: waitlist, - questionnaire: { responses: [] } # Пустые ответы + waitlist: false, + questionnaire: { responses: [] } } end - before do - meeting.update!(available_slots: 0, waitlist_enabled: waitlist, registrations_enabled: true) - end - it "shows error message" do post :answer, params: params - expected_key = waitlist ? "waitlist" : "create" - expect(flash[:alert]).to eq(I18n.t("registrations.#{expected_key}.invalid", scope: "decidim.meetings")) + expect(flash[:alert]).to eq(I18n.t("answer.invalid", scope: "decidim.forms.questionnaires")) expect(response).to render_template("decidim/forms/questionnaires/show") end end end describe "POST join_waitlist" do - let(:meeting) { create(:meeting, component:, available_slots: available_slots, waitlist_enabled: true) } + let(:meeting) { create(:meeting, component:, available_slots: 10, waitlist_enabled: true) } + let!(:registrations) { create_list(:registration, 10, meeting: meeting) } let(:params) { { meeting_id: meeting.id } } - let(:available_slots) { 0 } before { sign_in user } From 4dfd971a977e0286850d075fa903bf40df993b3e Mon Sep 17 00:00:00 2001 From: Anna Topalidi Date: Wed, 26 Feb 2025 15:21:08 +0100 Subject: [PATCH 08/14] fix cell, add tests --- .../registration_modal.erb | 7 +- .../meetings/join_meeting_button_cell.rb | 4 - .../meetings/join_waitlist_button/show.erb | 5 +- .../meetings/join_waitlist_button_cell.rb | 4 - .../meetings/registrations_controller.rb | 4 +- decidim-meetings/config/locales/en.yml | 1 + .../spec/system/meeting_registrations_spec.rb | 29 +++ .../spec/system/meeting_waiting_list_spec.rb | 185 ++++++++++++++++++ 8 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 decidim-meetings/spec/system/meeting_waiting_list_spec.rb diff --git a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb index da05b241e4e8c..e7894b22818ff 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb +++ b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb @@ -1,8 +1,9 @@ -<%= decidim_modal id: "meeting-registration-confirm-#{model.id}", class: "meeting__registration-modal" do %> +<% action = model.has_available_slots? ? "registration" : "waitlist" %> +<%= decidim_modal id: "meeting-#{action}-confirm-#{model.id}", class: "meeting__registration-modal" do %> <%= decidim_form_for(registration_form, url: model.has_available_slots? ? meeting_registration_path(model) : join_waitlist_meeting_registration_path(model), method: :post) do |form| %>
<%= icon "login-circle-line" %> -

<%= t("join", scope: "decidim.meetings.meetings.show") %>

+

<%= t(model.has_available_slots? ? "join" : "join_waitlist", scope: "decidim.meetings.meetings.show") %>

@@ -13,7 +14,7 @@
-