* VRFS-1889 completed; VRFS-1879 - partially done

This commit is contained in:
Seth Call 2014-07-08 13:34:03 -05:00
parent 27fe4f4ae9
commit 0020a8eb0d
20 changed files with 270 additions and 139 deletions

View File

@ -184,4 +184,5 @@ change_scheduled_start_music_session.sql
music_sessions_iso_639_3.sql
discardable_claimed_recordings.sql
fix_null_scheduled_start.sql
fix_use_open_rsvp.sql
fix_use_open_rsvp.sql
allow_unspecified_rsvps.sql

View File

@ -0,0 +1,3 @@
ALTER TABLE music_sessions ADD COLUMN is_unstructured_rsvp BOOLEAN DEFAULT FALSE;
ALTER TABLE rsvp_slots ADD COLUMN is_unstructured_rsvp BOOLEAN DEFAULT FALSE;
ALTER TABLE rsvp_slots ALTER COLUMN proficiency_level DROP NOT NULL;

View File

@ -23,6 +23,10 @@ module JamRuby
"#{base_url}/account/sessionDetail/#{music_session.id}"
end
def self.find_session
"#{base_url}/findSession"
end
private
def self.base_url

View File

@ -46,6 +46,7 @@ module JamRuby
validates :fan_access, :inclusion => {:in => [true, false]}
validates :approval_required, :inclusion => {:in => [true, false]}
validates :musician_access, :inclusion => {:in => [true, false]}
validates :is_unstructured_rsvp, :inclusion => {:in => [true, false]}
validates :legal_terms, :inclusion => {:in => [true]}, :on => :create
validates :creator, :presence => true
validate :creator_is_musician
@ -268,6 +269,7 @@ module JamRuby
ms.legal_terms = true
ms.open_rsvps = options[:open_rsvps] if options[:open_rsvps]
ms.creator = user
ms.is_unstructured_rsvp = options[:isUnstructuredRsvp] if options[:isUnstructuredRsvp]
ms.save
@ -510,11 +512,13 @@ module JamRuby
# get all slots for this session and perform a set difference with all chosen slots;
# this will return those that are not filled yet
# this method excludes rsvp_slots marked as 'is_unstructured_rsvp = true'
def open_slots
RsvpSlot.find_by_sql(%Q{select rs.*, ii.description
from rsvp_slots rs
inner join instruments ii on ii.id = rs.instrument_id
where rs.music_session_id = '#{self.id}'
and rs.is_unstructured_rsvp = false
except
select distinct rs.*, iii.description
from rsvp_slots rs

View File

@ -77,8 +77,19 @@ module JamRuby
# (3) verify user has not already requested this slot
# (4) create RsvpRequestRsvpSlot
# (5) create RsvpRequest
# special case: if the slot is the value 'unstructured',
# we will also on-demand create a RsvpSlot.
slot_ids.each do |id|
rsvp_slot = RsvpSlot.where(:id => id).first
if id == "unstructured"
rsvp_slot = RsvpSlot.new
rsvp_slot.is_unstructured_rsvp = true
rsvp_slot.music_session = music_session
unless rsvp_slot.save
raise StateError, 'Unable to auto-create RSVP Slot'
end
else
rsvp_slot = RsvpSlot.where(:id => id).first
end
# verify slot exists in db
if rsvp_slot.nil?

View File

@ -2,16 +2,23 @@ module JamRuby
class RsvpSlot < ActiveRecord::Base
belongs_to :instrument, :class_name => "JamRuby::Instrument"
belongs_to :music_session
belongs_to :music_session, :class_name => "JamRuby::MusicSession"
has_many :rsvp_requests_rsvp_slots, :class_name => "JamRuby::RsvpRequestRsvpSlot", :foreign_key => "rsvp_slot_id"
has_many :rsvp_requests, :class_name => "JamRuby::RsvpRequest", :through => :rsvp_requests_rsvp_slots
validates :instrument, presence: true, if: :is_not_unstructured_rsvp?
validates :is_unstructured_rsvp, :inclusion => {:in => [true, false]}
validates :proficiency_level, presence: true, if: :is_not_unstructured_rsvp?
attr_accessor :chosen, :proficiency_desc
class << self
@@proficiency_map = ["Any Skill Level", "Beg", "Beg/Int", "Int", "Int/Adv", "Adv"]
end
def is_not_unstructured_rsvp?
!is_unstructured_rsvp?
end
# TODO: validates :proficiency_level
def self.index(music_session)

View File

@ -130,6 +130,17 @@ describe RsvpRequest do
rs1.chosen.should == true
rs2.chosen.should == true
end
it "should allow RSVP to unstructured music session" do
@music_session.is_unstructured_rsvp = true
@music_session.save!
rsvp_request = RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => ['unstructured']}, @non_session_invitee)
rsvp_request.rsvp_slots.length.should == 1
rsvp_request.rsvp_slots[0].is_unstructured_rsvp.should be_true
rsvp_request.rsvp_slots[0].proficiency_level.should be_nil
rsvp_request.rsvp_slots[0].instrument.should be_nil
end
end
describe "index" do

View File

@ -13,10 +13,8 @@ describe RsvpSlot do
User.delete_all
@session_invitee = FactoryGirl.create(:user)
@session_invitee.save
@session_creator = FactoryGirl.create(:user)
@session_creator.save
# session invitations require sender and receiver to be friends
FactoryGirl.create(:friendship, :user => @session_invitee, :friend => @session_creator)
@ -25,14 +23,11 @@ describe RsvpSlot do
@music_session = FactoryGirl.build(:music_session, :creator => @session_creator)
@music_session.save
@slot1 = FactoryGirl.build(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('electric guitar'))
@slot1.save
@slot1 = FactoryGirl.create(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('electric guitar'))
@slot2 = FactoryGirl.build(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('drums'))
@slot2.save
@slot2 = FactoryGirl.create(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('drums'))
@invitation = FactoryGirl.build(:invitation, :sender => @session_creator, :receiver => @session_invitee, :music_session => @music_session)
@invitation.save
@invitation = FactoryGirl.create(:invitation, :sender => @session_creator, :receiver => @session_invitee, :music_session => @music_session)
end
describe "index" do
@ -74,4 +69,17 @@ describe RsvpSlot do
slots.where(:instrument_id => 'drums').first.chosen.should == false
end
end
describe "validators" do
it "require instrument if not unstructured" do
rsvp_slot = FactoryGirl.build(:rsvp_slot, instrument: nil)
rsvp_slot.valid?.should be_false
rsvp_slot.errors[:instrument].should == ["can't be blank"]
end
it "not require instrument if unstructured" do
rsvp_slot = FactoryGirl.build(:rsvp_slot, is_unstructured_rsvp: true, instrument: nil)
rsvp_slot.valid?.should be_true
end
end
end

View File

@ -33,30 +33,22 @@
if (response.recurring_mode !== null && response.recurring_mode === 'weekly') {
$('.schedule-recurrence', $screen).html("Recurs " + response.recurring_mode + " on this day at this time");
}
if (response['is_unstructured_rsvp?']) {
$('.rsvp-instruments', $screen).append('<input type="checkbox" value="unstructured"/>Any Instrument<br/>');
}
if (response.open_slots && response.open_slots.length > 0) {
$.each(response.open_slots, function(index, val) {
var instrument = val.instrument_id;
var instrumentTitleCase = context.JK.toTitleCase(instrument);
$('.rsvp-instruments', $screen).append('<input type="checkbox" value="' + val.id + '"/>' + instrumentTitleCase + "<br/>");
});
}
}
})
.fail(function(xhr) {
});
rest.getOpenSessionSlots(sessionId, true)
.done(function(response) {
if (response && response.length > 0) {
$.each(response, function(index, val) {
var instrument = val.instrument_id;
var instrumentTitleCase = context.JK.toTitleCase(instrument);
$('.rsvp-instruments', $screen).append('<input type="checkbox" value="' + val.id + '"/>' + instrumentTitleCase + "<br/>");
});
}
else {
$('.slot-instructions', $screen).hide();
$('.rsvp-instruments', $screen).hide();
}
})
.fail(function(xhr) {
});
.fail(app.ajaxError);
}
function afterHide() {
@ -83,7 +75,7 @@
var error = false;
rest.submitRsvpRequest(sessionId, slotIds)
.done(function(response) {
var comment = $.trim($('#txtComment', $screen).val());
var comment = $.trim($('.txtComment', $screen).val());
if (comment.length > 0) {
rest.addSessionInfoComment(sessionId, comment)
.done(function(response) {

View File

@ -594,6 +594,8 @@
data.rsvp_slots.push(slot);
});
var otherInstruments = instrumentRSVP.getSelectedInstruments();
data.isUnstructuredRsvp = otherInstruments.length == 0;
$.each(instrumentRSVP.getSelectedInstruments(), function(index, instrument) {
for (var i = 0; i < instrument.count; i++) {
var slot = {};

View File

@ -25,8 +25,6 @@
UNKNOWN: {description: "UNKNOWN", style: "latency-grey", min: -2, max: -2}
};
var instrument_logo_map = context.JK.getInstrumentIconMap24();
function renderActiveSession(session, tbGroup) {
$('#actionHeader', tbGroup).html('JOIN');
@ -72,6 +70,11 @@
}
}
// render if anyone interested
if(session['is_unstructured_rsvp?']) {
openSlotsHtml += createOpenSlot({description: 'Any Instrument'})
}
// render open slots
if (session.open_slots) {
for (i=0; i < session.open_slots.length; i++) {
@ -152,6 +155,11 @@
latencyHtml += "<div style='height:15px;'>&nbsp;</div>";
}
if(session['is_unstructured_rsvp?']) {
openSlots = true;
openSlotsHtml += createOpenSlot({description: 'Any Instrument'})
}
// render open slots
if (session.open_slots) {
for (i=0; i < session.open_slots.length; i++) {
@ -238,10 +246,7 @@
for (j=0; j < participant.tracks.length; j++) {
var track = participant.tracks[j];
logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks);
var inst = '../assets/content/icon_instrument_default24.png';
if (track.instrument_id in instrument_logo_map) {
inst = instrument_logo_map[track.instrument_id].asset;
}
var inst = context.JK.getInstrumentIcon24(track.instrument_id);
instrumentLogoHtml += '<img src="' + inst + '" width="24" height="24" />&nbsp;';
}
@ -271,10 +276,7 @@
if ("instrument_list" in user) {
for (j=0; j < user.instrument_list.length; j++) {
var instrument = user.instrument_list[j];
var inst = '../assets/content/icon_instrument_default24.png';
if (instrument.id in instrument_logo_map) {
inst = instrument_logo_map[instrument.id].asset;
}
var inst = context.JK.getInstrumentIcon24(instrument.id);
instrumentLogoHtml += '<img src="' + inst + '" width="24" height="24" />&nbsp;';
}
}
@ -303,7 +305,6 @@
else {
var latency = user.latency;
console.log("latency = %o", latency);
if (!latency || latency === 1000) {
// 1000 is a magical number returned by new scoring API to indicate one or more people in the session have an unknown score
@ -333,15 +334,21 @@
}
function createOpenSlot(slot) {
var inst = '../assets/content/icon_instrument_default24.png';
if ("instrument_id" in slot) {
inst = instrument_logo_map[slot.instrument_id].asset;
var inst = context.JK.getInstrumentIcon24(slot.instrument_id);
var proficiency_desc = slot.proficiency_desc;
if(!proficiency_desc) {
// this is to allow unstructured RSVPs to not specify proficiency_desc
proficiency_desc = "Any Skill Level";
}
if(!slot.proficiency_desc) {
proficiency_desc
}
var slot = {
instrument_url: inst,
instrument: slot.description,
proficiency: slot.proficiency_desc
proficiency: proficiency_desc
};
return context.JK.fillTemplate($openSlotsTemplate.html(), slot);
@ -462,6 +469,4 @@
this.renderInactiveSession = renderInactiveSession;
return this;
};
})(window,jQuery);
}})(window,jQuery);

View File

@ -225,15 +225,18 @@
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_UPDATE, function(header, payload) {
logger.debug("Handling FRIEND_UPDATE msg " + JSON.stringify(payload));
friends[payload.user_id].online = payload.online;
updateFriendList(friends);
var friend = friends[payload.user_id];
if(friend) {
friend.online = payload.online;
updateFriendList(friends);
var online_text = payload.online ? "online" : "offline";
app.notify({
"title": "Friend is now " + online_text,
"text": payload.msg,
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
});
var online_text = payload.online ? "online" : "offline";
app.notify({
"title": "Friend is now " + online_text,
"text": payload.msg,
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
});
}
});
}

View File

@ -62,7 +62,7 @@
"cello": "cello",
"clarinet": "clarinet",
"computer": "computer",
"default": "default",
"_default": "default",
"drums": "drums",
"electric guitar": "electric_guitar",
"euphonium": "euphonium",
@ -89,8 +89,7 @@
var instrumentIconMap45 = {};
var instrumentIconMap256 = {};
$.each(context._.keys(icon_map_base), function (index, instrumentId) {
var icon = icon_map_base[instrumentId];
$.each(icon_map_base, function (instrumentId, icon) {
instrumentIconMap24[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "24.png", name: instrumentId};
instrumentIconMap45[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "45.png", name: instrumentId};
instrumentIconMap256[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "256.png", name: instrumentId};
@ -215,14 +214,12 @@
$("[profileaction='band']", $parent).unbind('click');
$("[profileaction='band']", $parent).click(function(evt) {
closeDialogs();
console.log("navigating to band profile %o", $(this).attr('band-id'));
window.location = "/client#/bandProfile/" + $(this).attr('band-id');
});
$("[profileaction='musician']", $parent).unbind('click');
$("[profileaction='musician']", $parent).click(function(evt) {
closeDialogs();
console.log("navigating to musician profile %o", $(this).attr('user-id'));
window.location = "/client#/profile/" + $(this).attr('user-id');
});
}
@ -394,7 +391,7 @@
return instrumentIconMap24[instrument].asset;
}
return instrumentIconMap24["default"].asset;
return instrumentIconMap24["_default"].asset;
};
context.JK.getInstrumentIcon45 = function (instrument) {
@ -402,7 +399,7 @@
return instrumentIconMap45[instrument].asset;
}
return instrumentIconMap45["default"].asset;
return instrumentIconMap45["_default"].asset;
};
context.JK.getInstrumentIcon256 = function (instrument) {
@ -410,7 +407,7 @@
return instrumentIconMap256[instrument].asset;
}
return instrumentIconMap256["default"].asset;
return instrumentIconMap256["_default"].asset;
};
// meant to pass in a bunch of images with an instrument-id attribute on them.

View File

@ -56,6 +56,7 @@
*= require ./acceptFriendRequestDialog
*= require ./launchAppDialog
*= require ./editRecordingDialog
*= require ./rsvpDialog
*= require ./iconInstrumentSelect
*= require ./terms
*= require ./createSession

View File

@ -0,0 +1,63 @@
@import "client/common";
#rsvp-submit-dialog {
min-height:initial;
.session-name {
margin:3px 0 0;
}
.scheduled-start {
margin:3px 0 0;
}
.schedule-recurrence {
margin:3px 0 0;
}
.part {
margin-top:30px;
}
.comment-instructions {
margin-top:30px;
}
.buttons {
position:absolute;
bottom:20px;
left:0;
width:100%;
@include border_box_sizing;
padding: 0 25px;
.left {
a {
margin-left:2px;
}
}
.right {
a:last-child {
margin-right:2px;
}
}
}
.rsvp-instruments {
height:auto;
overflow:auto;
background-color:#202020;
padding:8px;
margin-top:5px;
max-height:75px;
@include border_box_sizing;
}
.txtComment {
@include border_box_sizing;
width:100%;
font-size:15px;
padding:5px;
margin-bottom:32px; // to ensure buttons don't overlap
}
}

View File

@ -171,13 +171,6 @@ a img {border:none;}
small, .small {font-size:11px;}
.bold {font-weight:bold;}
.rsvp-instruments {
height:auto;
overflow:auto;
background-color:#202020;
padding:8px;
margin-top:5px;
}
.button-grey {
margin:0px 8px 0px 8px;

View File

@ -19,7 +19,7 @@ else
attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat,
:band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_duration,
:language, :recurring_mode, :language_description, :scheduled_start_time, :access_description, :timezone, :timezone_description,
:musician_access_description, :fan_access_description, :session_removed_at, :legal_policy, :open_rsvps
:musician_access_description, :fan_access_description, :session_removed_at, :legal_policy, :open_rsvps, :is_unstructured_rsvp?
node :share_url do |history|
unless history.share_token.nil?

View File

@ -224,7 +224,7 @@
<a user-id="{userId}" hoveraction="musician" href="{profile_url}">{musician_name}</a>
</td>
<td>
<div id="instruments" class="nowrap">{instruments}</div>
<div class="instruments" class="nowrap">{instruments}</div>
</td>
</tr>
</script>
@ -235,7 +235,7 @@
<img src="{instrument_url}" />
</td>
<td>
<div id="instruments" class="nowrap">{instrument} ({proficiency})</div>
<div class="instruments" class="nowrap">{instrument} ({proficiency})</div>
</td>
</tr>
</script>

View File

@ -4,23 +4,19 @@
%h1 rsvp
.dialog-inner
%h2 SESSION
%span.session-name
%br/
%span.scheduled-start
%br/
%span.schedule-recurrence
%br/
%br/
%span.slot-instructions Check the box(es) next to the track(s) you want to play in the session:
.error{:style => 'display:none'} You must select at least 1 instrument.
.rsvp-instruments
%br/
Enter a message to the other musicians in the session (optional):
%textarea.w95.p5.f15{id: 'txtComment', rows: '2', placeholder: 'Enter a comment...'}
%br/
%br/
.left
%a.button-grey{:href => 'http://jamkazam.desk.com', :rel => 'external', :target => '_blank'} HELP
.right
%a.button-grey{:id => 'btnCancel', 'layout-action' => 'close'} CANCEL
%a.button-orange{:id => 'btnSubmitRsvp'} SUBMIT RSVP
.session-name
.scheduled-start
.schedule-recurrence
.part
.slot-instructions Check the box(es) next to the track(s) you want to play in the session:
.error{:style => 'display:none'} You must select at least 1 instrument.
.rsvp-instruments
.comment-instructions Enter a message to the other musicians in the session (optional):
%textarea.txtComment{rows: '2', placeholder: 'Enter a comment...'}
.buttons
.left
%a.button-grey{:href => 'http://jamkazam.desk.com', :rel => 'external', :target => '_blank'} HELP
.right
%a.button-grey{:id => 'btnCancel', 'layout-action' => 'close'} CANCEL
%a.button-orange{:id => 'btnSubmitRsvp'} SUBMIT RSVP

View File

@ -4,70 +4,100 @@ describe "Find Session", :js => true, :type => :feature, :capybara_feature => tr
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 30 # these tests are SLOOOOOW
end
let(:user) { FactoryGirl.create(:user) }
let(:finder) { FactoryGirl.create(:user) }
before(:each) do
UserMailer.deliveries.clear
ActiveMusicSession.delete_all
emulate_client
sign_in_poltergeist user
visit "/client#/findSession"
FactoryGirl.create(:friendship, :user => user, :friend => finder)
SessionInfoComment.delete_all
Notification.delete_all
RsvpRequestRsvpSlot.delete_all
RsvpRequest.delete_all
RsvpSlot.delete_all
Invitation.delete_all
MusicSession.delete_all
end
# when no sessions have been created:
it "shows there are no sessions" do
# verify no sessions are found
expect(page).to have_selector('#no-active-sessions')
expect(page).to have_selector('#no-scheduled-sessions')
end
describe "basic" do
it "finds another public session" do
create_join_session(user, [finder])
end
before(:each) do
emulate_client
sign_in_poltergeist user
visit "/client#/findSession"
FactoryGirl.create(:friendship, :user => user, :friend => finder)
end
describe "listing behavior" do
describe "one slush session" do
before do
@session1 = FactoryGirl.create(:single_user_session)
end
# when no sessions have been created:
it "shows there are no sessions" do
# verify no sessions are found
expect(page).to have_selector('#no-active-sessions')
expect(page).to have_selector('#no-scheduled-sessions')
end
it "find general population user" do
pending
find('#btn-refresh').trigger(:click)
sleep 1
find('div#sessions-active')
page.all('div#sessions-active .found-session').count.should == 1
end
it "finds another public session" do
create_join_session(user, [finder])
end
describe "tons of slush sessions" do
describe "listing behavior" do
describe "one slush session" do
before do
20.times do
FactoryGirl.create(:single_user_session)
end
@session1 = FactoryGirl.create(:single_user_session)
end
it "find many general users" do
it "find general population user" do
pending
find('#btn-refresh').trigger(:click)
sleep 1
find('div#sessions-active')
page.all('div#sessions-active .found-session').count.should == 20
page.all('div#sessions-active .found-session').count.should == 1
end
# attempt to scroll down--the end of session list should show, and there should now be 21 items
# page.execute_script('jQuery("#findSession .content-body-scroller").scrollTo("100%",100)') #scroll to the bottom of the element
# find('#end-of-session-list')
# page.all('div#sessions-active .found-session').count.should == 21
describe "tons of slush sessions" do
before do
20.times do
FactoryGirl.create(:single_user_session)
end
end
it "find many general users" do
pending
find('#btn-refresh').trigger(:click)
sleep 1
find('div#sessions-active')
page.all('div#sessions-active .found-session').count.should == 20
# attempt to scroll down--the end of session list should show, and there should now be 21 items
# page.execute_script('jQuery("#findSession .content-body-scroller").scrollTo("100%",100)') #scroll to the bottom of the element
# find('#end-of-session-list')
# page.all('div#sessions-active .found-session').count.should == 21
end
end
end
end
end
describe "rsvp behavior" do
before(:each) do
stub_const("APP_CONFIG", web_config)
end
it "handles is_unstructured_rsvp sessions correctly" do
music_session = FactoryGirl.create(:music_session, creator: user, is_unstructured_rsvp: true)
fast_signin(finder, Nav.find_session)
find("#sessions-scheduled tr.found-session div.instruments", text: 'Any Instrument (Any Skill Level)')
find('.rsvp-link').trigger(:click)
# bring up the RSVP dialog
find('.rsvp-instruments input[value="unstructured"]').trigger(:click)
end
end
end