diff --git a/app/controllers/check_ins_controller.rb b/app/controllers/check_ins_controller.rb index 7b59e4304..89343201d 100644 --- a/app/controllers/check_ins_controller.rb +++ b/app/controllers/check_ins_controller.rb @@ -117,6 +117,8 @@ def mark_attended(invitation) attrs[:automated_rsvp] = true end invitation.update!(attrs) + MemberActivityRecorder.record(actor: invitation.member, key: 'member.checked_in', + trackable: invitation) end def permitted_role diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb index 88bd6043a..f8e3f312e 100644 --- a/app/controllers/invitations_controller.rb +++ b/app/controllers/invitations_controller.rb @@ -35,6 +35,9 @@ def attend if @invitation.student_spaces? || @invitation.coach_spaces? @invitation.update!(attending: true) + MemberActivityRecorder.record(actor: @invitation.member, key: 'event_invitation.rsvp', + trackable: @invitation) + notice = t('messages.invitations.spot_confirmed', event: @invitation.event.name) unless event.confirmation_required || event.surveys_required @@ -61,6 +64,8 @@ def reject end @invitation.update!(attending: false) + MemberActivityRecorder.record(actor: @invitation.member, key: 'event_invitation.rejected', + trackable: @invitation) redirect_back( fallback_location: root_path, notice: t('messages.rejected_invitation', name: @invitation.member.name) @@ -74,6 +79,8 @@ def rsvp_meeting meeting = invitation.meeting if invitation.update(attending: true) + MemberActivityRecorder.record(actor: current_user, key: 'meeting_invitation.rsvp', + trackable: invitation) MeetingInvitationMailer.attending(meeting, current_user).deliver_now redirect_to meeting_path(meeting, token: invitation.token), notice: t('messages.invitations.meeting.rsvp') @@ -87,6 +94,9 @@ def cancel_meeting @invitation.update!(attending: false) + MemberActivityRecorder.record(actor: @invitation.member, key: 'meeting_invitation.cancelled', + trackable: @invitation) + redirect_back fallback_location: root_path, notice: t('messages.invitations.meeting.cancel') end diff --git a/app/controllers/waiting_lists_controller.rb b/app/controllers/waiting_lists_controller.rb index 37df15a16..471def73d 100644 --- a/app/controllers/waiting_lists_controller.rb +++ b/app/controllers/waiting_lists_controller.rb @@ -7,12 +7,14 @@ class WaitingListsController < ApplicationController # FeedbackController#submit (PR #2641, Rollbar #535). skip_forgery_protection only: %i[create destroy] - def create + def create # rubocop:disable Metrics/MethodLength @invitation.assign_attributes(invitation_params) return back_with_message(@invitation.errors.full_messages) unless @invitation.valid?(:waitinglist) @invitation.save && WaitingList.add(@invitation, auto_rsvp) + MemberActivityRecorder.record(actor: @invitation.member, key: 'waiting_list.joined', + trackable: @invitation) message = if auto_rsvp 'You have been added to the waiting list' @@ -25,6 +27,8 @@ def create def destroy WaitingList.find_by(invitation_id: @invitation.id).destroy + MemberActivityRecorder.record(actor: @invitation.member, key: 'waiting_list.left', + trackable: @invitation) redirect_to invitation_path(@invitation), notice: 'You have been removed from the waiting list' end diff --git a/app/controllers/workshop_invitation_controller.rb b/app/controllers/workshop_invitation_controller.rb index e9166437c..6b3369864 100644 --- a/app/controllers/workshop_invitation_controller.rb +++ b/app/controllers/workshop_invitation_controller.rb @@ -45,6 +45,8 @@ def accept return back_with_message(t('messages.no_available_seats')) unless available_spaces?(@workshop, @invitation) if @invitation.update(invitation_params.merge!(attending: true, rsvp_time: Time.zone.now)) + MemberActivityRecorder.record(actor: @invitation.member, key: 'workshop_invitation.rsvp', + trackable: @invitation) @workshop.send_attending_email(@invitation) back_with_message(t('messages.accepted_invitation', name: @invitation.member.name)) else @@ -63,6 +65,8 @@ def reject notice: t('messages.not_attending_already')) else @invitation.update!(attending: false) + MemberActivityRecorder.record(actor: @invitation.member, key: 'workshop_invitation.rejected', + trackable: @invitation) next_spot = WaitingList.next_spot(@invitation.workshop, @invitation.role) diff --git a/spec/requests/member_activity_check_in_spec.rb b/spec/requests/member_activity_check_in_spec.rb new file mode 100644 index 000000000..47a219def --- /dev/null +++ b/spec/requests/member_activity_check_in_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Check-in activity' do + let(:invitation) { Fabricate(:workshop_invitation) } + let(:member) { invitation.member } + let(:workshop) { invitation.workshop } + let(:code) { 'my-check-code' } + + before do + Fabricate(:auth_service, member:, provider: 'github', uid: 'checkin-uid-1') + mock_auth_hash(provider: 'github', uid: 'checkin-uid-1', email: member.email) + post '/auth/github/callback' + workshop.update!(date_and_time: 1.hour.ago, check_in_code: code) + end + + it 'records member.checked_in on valid self-check-in' do + post check_in_w_path(code:), params: { role: 'Student' } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'member.checked_in')).to be(true) + end + + it 'does not record for an invalid role' do + post check_in_w_path(code:), params: { role: 'Invalid' } + + expect(PublicActivity::Activity.exists?(key: 'member.checked_in')).to be(false) + end +end diff --git a/spec/requests/member_activity_rsvps_spec.rb b/spec/requests/member_activity_rsvps_spec.rb new file mode 100644 index 000000000..3daa337c0 --- /dev/null +++ b/spec/requests/member_activity_rsvps_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +RSpec.describe 'Event and meeting RSVP activity' do + let(:member) { Fabricate(:member) } + + before do + ApplicationController.prepend(LoginHelpers::LoginStub) unless ApplicationController < LoginHelpers::LoginStub + LoginHelpers::LoginStub.current_user = member + end + + after { LoginHelpers::LoginStub.current_user = nil } + + describe 'event RSVPs' do + let(:invitation) { Fabricate(:invitation, member:) } + + it 'records event_invitation.rsvp on attend' do + post event_attend_path(invitation.event.id, invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'event_invitation.rsvp')).to be(true) + end + + it 'records event_invitation.rejected on reject' do + invitation.update!(attending: true) + post event_reject_path(invitation.event.id, invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'event_invitation.rejected')).to be(true) + end + end + + describe 'meeting RSVPs' do + let(:invitation) { Fabricate(:meeting_invitation, member:) } + + it 'records meeting_invitation.rsvp' do + get meeting_invitation_path(invitation.meeting), params: { token: invitation.token } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'meeting_invitation.rsvp')).to be(true) + end + + it 'records meeting_invitation.cancelled' do + invitation.update!(attending: true) + get meeting_cancel_path(invitation.meeting, invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'meeting_invitation.cancelled')).to be(true) + end + end +end diff --git a/spec/requests/member_activity_token_rsvps_spec.rb b/spec/requests/member_activity_token_rsvps_spec.rb new file mode 100644 index 000000000..0533b012d --- /dev/null +++ b/spec/requests/member_activity_token_rsvps_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe 'Token-based workshop RSVP activity' do + let(:invitation) { Fabricate(:workshop_invitation) } + let(:member) { invitation.member } + + it 'records workshop_invitation.rsvp on accept' do + invitation.workshop.update!(date_and_time: 2.days.from_now, rsvp_closes_at: 1.day.from_now) + # Setup the @workshop presenter for available_spaces? — the fabricator's default 10 student seats satisfy + post accept_invitation_path(invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'workshop_invitation.rsvp')).to be(true) + end + + it 'records workshop_invitation.rejected on reject' do + invitation.update!(attending: true) + + get reject_invitation_path(invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'workshop_invitation.rejected')).to be(true) + end + + it 'records waiting_list.joined and waiting_list.left' do + # Make workshop full so the invitation can be on waiting list + invitation.workshop.update!(student_spaces: 0) + invitation.update!(attending: false, role: 'Student') + + post invitation_waiting_list_path(invitation) + expect(PublicActivity::Activity.exists?(owner: member, key: 'waiting_list.joined')).to be(true) + + delete invitation_waiting_list_path(invitation) + expect(PublicActivity::Activity.exists?(owner: member, key: 'waiting_list.left')).to be(true) + end +end