diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index 47c8fe303..24b4f852f 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -475,40 +475,20 @@ module JamRuby end def access_description - return "#{musician_access_description}. #{fan_access_description}." + "#{musician_access_description}. #{fan_access_description}." end # retrieve users that have approved RSVPs def approved_rsvps - users = User.find_by_sql(%Q{select u.id, u.photo_url, u.first_name, u.last_name, rs.instrument_id, ii.description, rs.proficiency_level, rr.id as rsvp_request_id, rrrs.id as rsvp_request_rsvp_slot_id + User.find_by_sql(%Q{select distinct ON(u.id) u.id, u.photo_url, u.first_name, u.last_name, json_agg(ii.id) as instrument_ids, json_agg(ii.description) as instrument_descriptions, json_agg(rs.proficiency_level) as instrument_proficiencies, json_agg(rr.id) as rsvp_request_ids from rsvp_slots rs inner join rsvp_requests_rsvp_slots rrrs on rrrs.rsvp_slot_id = rs.id inner join rsvp_requests rr on rrrs.rsvp_request_id = rr.id - inner join instruments ii on ii.id = rs.instrument_id + left join instruments ii on ii.id = rs.instrument_id inner join users u on u.id = rr.user_id - where rrrs.chosen = true and rs.music_session_id = '#{self.id}' - order by u.id} + where rrrs.chosen = true AND rs.music_session_id = '#{self.id}' AND rr.canceled != TRUE + group by u.id order by u.id} ) - - users_collapsed_instruments = [] - user = User.new - - # build User array with instruments collapsed - users.each_with_index do |u, index| - if index == 0 || users[index].id != users[index-1].id - user = User.new - user.id = u.id - user.photo_url = u.photo_url - user.first_name = u.first_name - user.last_name = u.last_name - user.instrument_list = [{id: u.instrument_id, desc: u.description, level: u.proficiency_level}] - users_collapsed_instruments << user - else - user.instrument_list << {id: u.instrument_id, desc: u.description, level: u.proficiency_level} - end - end - - users_collapsed_instruments end # get all slots for this session and perform a set difference with all chosen slots; diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 5e7a8f99f..ba0d7d494 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -19,9 +19,6 @@ module JamRuby # updating_password corresponds to a lost_password attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json - # contains a list of instrument IDs (used to aggregate instruments under a user for various screens) - attr_accessor :instrument_list - # checks if user has submitted RSVP to a session attr_accessor :has_rsvp diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 41c9f5f2c..b28b059f3 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -504,6 +504,22 @@ FactoryGirl.define do factory :rsvp_request, class: JamRuby::RsvpRequest do canceled false cancel_all false + association :user, :factory => :user + + # creates *number* slots for a new rsvp_request + factory :rsvp_request_for_multiple_slots do + ignore do + music_session nil + number nil + end + + after(:create) { |rsvp_request, evaluator | + evaluator.number.times do |i| + slot = FactoryGirl.create(:rsvp_slot, music_session: evaluator.music_session, instrument: Instrument.order(:id).limit(1).offset(i).first, proficiency_level: 1) + FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:slot) + end + } + end end factory :rsvp_request_rsvp_slot, class: JamRuby::RsvpRequestRsvpSlot do diff --git a/ruby/spec/jam_ruby/models/music_session_spec.rb b/ruby/spec/jam_ruby/models/music_session_spec.rb index 20dc08663..acfa689d0 100644 --- a/ruby/spec/jam_ruby/models/music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/music_session_spec.rb @@ -209,6 +209,63 @@ describe MusicSession do end end + describe "approved_rsvps" do + it "aggregrates instrument info" do + + creators_slot = music_session1.rsvp_slots[0] + music_session1.approved_rsvps.length.should == 1 + approved_user = music_session1.approved_rsvps[0] + JSON.parse(approved_user[:instrument_ids])[0].should == creators_slot.instrument.id + JSON.parse(approved_user[:instrument_descriptions])[0].should == creators_slot.instrument.description + JSON.parse(approved_user[:instrument_proficiencies])[0].should == creators_slot.proficiency_level + JSON.parse(approved_user[:rsvp_request_ids])[0].should == creators_slot.rsvp_requests[0].id + + end + + it "unstructured rsvps should still be returned" do + music_session1.rsvp_slots.length.should == 1 + creators_slot = music_session1.rsvp_slots[0] + + # now take out the instrument and proficiency of the rsvp_slot (make it unstructured) + creators_slot.is_unstructured_rsvp = true + creators_slot.instrument = nil + creators_slot.proficiency_level = nil + creators_slot.save! + + puts RsvpSlot.all.inspect + + music_session = MusicSession.find(music_session1.id) + approved_rsvps = music_session.approved_rsvps + approved_rsvps.length.should == 1 + approved_user = approved_rsvps[0] + JSON.parse(approved_user[:instrument_ids])[0].should == nil + JSON.parse(approved_user[:instrument_descriptions])[0].should == nil + JSON.parse(approved_user[:instrument_proficiencies])[0].should == nil + JSON.parse(approved_user[:rsvp_request_ids])[0].should == creators_slot.rsvp_requests[0].id + end + + it "handles 2 instruments for a single request correctly" do + + rsvp_request = FactoryGirl.create(:rsvp_request_for_multiple_slots, user: some_user, music_session: music_session1, number: 2) + + approved_rsvps = music_session1.approved_rsvps + approved_rsvps.length.should == 2 + + # find the user who made the request for 2 rsvp slots in the approved_users array + approved_some_user = approved_rsvps.find {|s| puts s.inspect; s.id == some_user.id} + + instrument_ids = JSON.parse(approved_some_user[:instrument_ids]) + instrument_ids.should =~ rsvp_request.rsvp_slots.map {|slot| slot.instrument_id } + + instrument_descriptions = JSON.parse(approved_some_user[:instrument_descriptions]) + instrument_descriptions.should =~ rsvp_request.rsvp_slots.map {|slot| slot.instrument.description } + instrument_proficiencies = JSON.parse(approved_some_user[:instrument_proficiencies]) + instrument_proficiencies.should =~ rsvp_request.rsvp_slots.map {|slot| slot.proficiency_level } + JSON.parse(approved_some_user[:rsvp_request_ids])[0].should == rsvp_request.id + + end + end + describe "scheduled" do it "excludes based on time-range" do session = FactoryGirl.create(:music_session, scheduled_start: Time.now) diff --git a/web/app/assets/javascripts/accounts_session_detail.js b/web/app/assets/javascripts/accounts_session_detail.js index ba39633e6..68b2b7626 100644 --- a/web/app/assets/javascripts/accounts_session_detail.js +++ b/web/app/assets/javascripts/accounts_session_detail.js @@ -5,6 +5,7 @@ context.JK = context.JK || {}; context.JK.AccountSessionDetail = function (app) { + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var rest = context.JK.Rest(); var sessionUtils = context.JK.SessionUtils; @@ -47,7 +48,13 @@ var rsvpCancelDlg = new context.JK.RsvpCancelDialog(app, sessionData.id, sessionData.rsvpId); rsvpCancelDlg.initialize(); - context.JK.app.layout.showDialog('rsvp-cancel-dialog'); + app.layout.showDialog('rsvp-cancel-dialog') + .one(EVENTS.RSVP_CANCELED, function() { + refreshSessionDetail(); + }) + .one(EVENTS.DIALOG_CLOSED, function() { + $(this).unbind(EVENTS.RSVP_CANCELED); + }); } @@ -381,10 +388,6 @@ this.beforeShow = beforeShow; this.afterShow = afterShow; - $(document).on("rsvpCancelEvent", function() { - refreshSessionDetail(); - }); - return this; }; diff --git a/web/app/assets/javascripts/dialog/rsvpCancelDialog.js b/web/app/assets/javascripts/dialog/rsvpCancelDialog.js index 2156cec2e..82f5fbe49 100644 --- a/web/app/assets/javascripts/dialog/rsvpCancelDialog.js +++ b/web/app/assets/javascripts/dialog/rsvpCancelDialog.js @@ -3,9 +3,10 @@ "use strict"; context.JK = context.JK || {}; context.JK.RsvpCancelDialog = function(app, sessionId, rsvpRequestId) { + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var rest = context.JK.Rest(); - var $screen = null; + var $dialog = null; var dialogId = 'rsvp-cancel-dialog'; var $btnCancel = $("#btnCancelRsvp"); @@ -17,11 +18,11 @@ rest.getSessionHistory(sessionId) .done(function(response) { if (response) { - $('.session-name', $screen).html(response.name); - $('.scheduled-start', $screen).html(response.scheduled_start); + $('.session-name', $dialog).html(response.name); + $('.scheduled-start', $dialog).html(response.scheduled_start); if (response.recurring_mode !== null) { - $('.schedule-recurrence', $screen).html("Recurs " + response.recurring_mode + " on this day at this time"); + $('.schedule-recurrence', $dialog).html("Recurs " + response.recurring_mode + " on this day at this time"); } } }) @@ -43,10 +44,10 @@ e.preventDefault(); var error = false; - var cancelOption = $('input[name="cancel"]:checked', $screen).val(); + var cancelOption = $('input[name="cancel"]:checked', $dialog).val(); rest.cancelRsvpRequest(sessionId, rsvpRequestId, cancelOption) .done(function(response) { - var comment = $.trim($('#txtComment', $screen).val()); + var comment = $.trim($('#txtComment', $dialog).val()); if (comment.length > 0) { rest.addSessionInfoComment(sessionId, comment) .done(function(response) { @@ -54,20 +55,21 @@ }) .fail(function(xhr) { error = true; - $('.error', $screen).html("Unexpected error occurred while saving message (" + xhr.status + ")"); - $('.error', $screen).show(); + $('.error', $dialog).html("Unexpected error occurred while saving message (" + xhr.status + ")"); + $('.error', $dialog).show(); }); } if (!error) { + $dialog.triggerHandler(EVENTS.RSVP_CANCELED); app.layout.closeDialog(dialogId); - $btnCancel.trigger("rsvpCancelEvent"); } }) .fail(function(xhr) { - $('.error', $screen).html("Unexpected error occurred while cancelling RSVP request (" + xhr.status + ")"); - $('.error', $screen).show(); + $('.error', $dialog).html("Unexpected error occurred while cancelling RSVP request (" + xhr.status + ")"); + $('.error', $dialog).show(); }); + return false; }); } @@ -81,7 +83,7 @@ app.bindDialog(dialogId, dialogBindings); - $screen = $('[layout-id="' + dialogId + '"]'); + $dialog = $('[layout-id="' + dialogId + '"]'); events(); } diff --git a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js index abab504d3..dd260a42c 100644 --- a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js +++ b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js @@ -98,6 +98,8 @@ $('.error', $screen).html(error.message); $('.error', $screen).show(); }); + + return false; }); } diff --git a/web/app/assets/javascripts/findSession.js b/web/app/assets/javascripts/findSession.js index bb1ed8090..12a920cfa 100644 --- a/web/app/assets/javascripts/findSession.js +++ b/web/app/assets/javascripts/findSession.js @@ -97,7 +97,6 @@ function renderActiveSessions(sessions) { $.each(sessions, function(i, session) { - logger.debug('Rendering session ID = ' + session.id); sessionList.renderActiveSession(session, $(CATEGORY.ACTIVE.id)); }); @@ -200,7 +199,6 @@ function renderScheduledSessions(sessions) { $.each(sessions, function(i, session) { - logger.debug('Rendering session ID = ' + session.id); sessionList.renderInactiveSession(session, $(CATEGORY.SCHEDULED.id)); }); afterLoadScheduledSessions(sessions); diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 4cacf3c4f..23e560f8e 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -30,7 +30,8 @@ context.JK.EVENTS = { DIALOG_CLOSED : 'dialog_closed', SHOW_SIGNUP : 'show_signup', - SHOW_SIGNIN : 'show_signin' + SHOW_SIGNIN : 'show_signin', + RSVP_CANCELED : 'rsvp_canceled' } context.JK.ALERT_NAMES = { diff --git a/web/app/assets/javascripts/scheduled_session.js b/web/app/assets/javascripts/scheduled_session.js index 08604d719..1369a60ea 100644 --- a/web/app/assets/javascripts/scheduled_session.js +++ b/web/app/assets/javascripts/scheduled_session.js @@ -268,7 +268,7 @@ $.each(session.approved_rsvps, function(index, user) { if (user.id == context.JK.currentUserId) { $.each(user.instrument_list, function(index, instrument) { - instruments_me.push(instrument.desc); + instruments_me.push(instrument.desc ? instrument.desc : 'Any Instrument'); }); } }); diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 6d5f6d7a6..a1026573a 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -4,6 +4,7 @@ context.JK = context.JK || {}; context.JK.SessionList = function(app) { + var EVENTS = context.JK.EVENTS; var gearUtils = context.JK.GearUtils; var logger = context.JK.logger; var rest = context.JK.Rest(); @@ -130,8 +131,8 @@ var openSlots = false; var hasInvitation = false; - var hasApprovedRsvp = false; - var currentUserHasRsvp = session.user_has_rsvp; + var approvedRsvpId = null; // if set, the user has an accepted RSVP + var pendingRsvpId = null; // if set, the user has a pending RSVP var hasPendingOrDeclinedRsvp = false; var openRsvps = session.open_rsvps; @@ -140,16 +141,22 @@ var i = 0; var rsvpUsersHtml = '', openSlotsHtml = '', latencyHtml = '', notationFileHtml = ''; + context._.each(session.pending_rsvp_requests, function(pending_rsvp_request) { + if(pending_rsvp_request.user_id == context.JK.currentUserId) { + pendingRsvpId = pending_rsvp_request.id; + } + }); + // render users who have approved RSVPs if (session.approved_rsvps && session.approved_rsvps.length > 0) { - for (i=0; i < session.approved_rsvps.length; i++) { - if (session.approved_rsvps[i].id === context.JK.currentUserId) { - hasApprovedRsvp = true; + context._.each(session.approved_rsvps, function(approved_rsvp) { + if (approved_rsvp.id === context.JK.currentUserId) { + approvedRsvpId = approved_rsvp.rsvp_request_id; } - var rsvpUserInfo = createRsvpUser(session.approved_rsvps[i], session); + var rsvpUserInfo = createRsvpUser(approved_rsvp, session); rsvpUsersHtml += rsvpUserInfo[0]; latencyHtml += rsvpUserInfo[1]; - } + }); } // this provides a buffer at the top to shift the first latency tag down in the event there are NO RSVP musicians else { @@ -178,11 +185,48 @@ } } - if ( (openRsvps || hasInvitation) && openSlots && !hasApprovedRsvp && !currentUserHasRsvp ) { - showRsvpLink = true; - } - else { + var showRsvpLink = true; + var noLinkText = ''; + + if (!openRsvps && !hasInvitation) { showRsvpLink = false; + noLinkText = 'You need an invitation to RSVP to this session.'; + } + else if(!openSlots) { + showRsvpLink = false + noLinkText = 'No more openings in this session.'; + } + else if(approvedRsvpId) { + showRsvpLink = false; + noLinkText = $('You have been confirmed for this session. Cancel'); + noLinkText.find('a').click(function() { + var rsvpCancelDlg = new context.JK.RsvpCancelDialog(app, session.id, approvedRsvpId); + rsvpCancelDlg.initialize(); + app.layout.showDialog('rsvp-cancel-dialog') + .one(EVENTS.RSVP_CANCELED, function() { + // VRFS-1891 + }) + .one(EVENTS.DIALOG_CLOSED, function() { + $(this).unbind(EVENTS.RSVP_CANCELED); + }); + return false; + }); + } + else if(pendingRsvpId) { + showRsvpLink = false; + noLinkText = $('You have RSVP\'ed to this session. Cancel'); + noLinkText.find('a').click(function() { + var rsvpCancelDlg = new context.JK.RsvpCancelDialog(app, session.id, pendingRsvpId); + rsvpCancelDlg.initialize(); + app.layout.showDialog('rsvp-cancel-dialog') + .one(EVENTS.RSVP_CANCELED, function() { + // VRFS-1891 + }) + .one(EVENTS.DIALOG_CLOSED, function() { + $(this).unbind(EVENTS.RSVP_CANCELED); + }); + return false; + }); } // notation files @@ -215,10 +259,11 @@ var $parentRow = $('tr[data-session-id=' + session.id + ']', tbGroup); $('.rsvp-link', $parentRow).click(function(evt) { ui.launchRsvpSubmitDialog(session.id); + return false; }); } else { - $('.rsvp-msg', $parentRow).show(); + $('.rsvp-msg', $parentRow).html(noLinkText).show(); } } diff --git a/web/app/assets/stylesheets/client/sessionList.css.scss b/web/app/assets/stylesheets/client/sessionList.css.scss index 96468523d..c137f1fbb 100644 --- a/web/app/assets/stylesheets/client/sessionList.css.scss +++ b/web/app/assets/stylesheets/client/sessionList.css.scss @@ -1,3 +1,5 @@ +@import "client/common"; + div.findsession-scroll-container { max-height:275px; overflow-y:scroll; @@ -67,6 +69,15 @@ table.findsession-table, table.local-recordings { a:hover { color:#227985; } + + .rsvp-section { + text-align:center; + vertical-align:middle; + + a { + color: $ColorLink; + } + } } .latency-grey { diff --git a/web/app/helpers/users_helper.rb b/web/app/helpers/users_helper.rb index b3edd0a32..002a2617a 100644 --- a/web/app/helpers/users_helper.rb +++ b/web/app/helpers/users_helper.rb @@ -9,4 +9,24 @@ module UsersHelper gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: "#{user.first_name} #{user.last_name}", class: "#{hclass}") end + + # this must be a user created by the MusicSession.approved_rsvp method + def process_approved_rsvps(approved_rsvp) + + instrument_list = [] + + # instruments are all denormalized into json arrays. fix em up into an instrument_list on the user object + instrument_ids = JSON.parse(approved_rsvp[:instrument_ids]) + instrument_descs = JSON.parse(approved_rsvp[:instrument_descriptions]) + instrument_proficiencies = JSON.parse(approved_rsvp[:instrument_proficiencies]) + rsvp_request_id = JSON.parse(approved_rsvp[:rsvp_request_ids])[0] # there should always be only one + + instrument_ids.each_with_index do |instrument_id, i| + desc = instrument_descs[i] + level = instrument_proficiencies[i] + instrument_list.push({ id: instrument_id, desc: desc, level: level}) + end + + instrument_list + end end diff --git a/web/app/views/api_music_sessions/show_history.rabl b/web/app/views/api_music_sessions/show_history.rabl index ad322d7dd..6ee368d9a 100644 --- a/web/app/views/api_music_sessions/show_history.rabl +++ b/web/app/views/api_music_sessions/show_history.rabl @@ -31,10 +31,6 @@ else [item.genre.description] # XXX: need to return single genre; not array end - node :user_has_rsvp do |history| - current_user.has_rsvp(history) - end - node :scheduled_start do |history| history.scheduled_start.utc.strftime("%a %e %B %Y %H:%M:%S") if history.scheduled_start end @@ -91,10 +87,14 @@ else } child({:approved_rsvps => :approved_rsvps}) { - attributes :id, :photo_url, :first_name, :last_name, :instrument_list, :name, :resolved_photo_url + attributes :id, :photo_url, :first_name, :last_name, :name, :resolved_photo_url, :rsvp_request_id node do |user| - { latency: user_score(user.id), name: user.name } + { + latency: user_score(user.id), + instrument_list: process_approved_rsvps(user), + rsvp_request_id: JSON.parse(user.rsvp_request_ids)[0] # there must always be a rsvp_request_id; and they should all be the same + } end } diff --git a/web/app/views/clients/_findSession.html.erb b/web/app/views/clients/_findSession.html.erb index 6f146559d..ef7a97cb2 100644 --- a/web/app/views/clients/_findSession.html.erb +++ b/web/app/views/clients/_findSession.html.erb @@ -203,7 +203,7 @@