* VRFS-1684 and VRFS-1686 - updating stored procs, added /api/sessions/nonactive/, adding a bunch of tests for sms_index

This commit is contained in:
Seth Call 2014-06-17 14:10:24 -05:00
parent 5ad6db90ef
commit 106222638f
14 changed files with 635 additions and 45 deletions

View File

@ -175,3 +175,4 @@ audio_latency.sql
ams_index.sql
update_ams_index.sql
update_ams_index_2.sql
sms_index.sql

143
db/up/sms_index.sql Normal file
View File

@ -0,0 +1,143 @@
-- my_audio_latency can have a special value of -1, which means 'unknown'.
CREATE OR REPLACE FUNCTION sms_index (my_user_id VARCHAR, my_locidispid BIGINT, my_audio_latency INTEGER) RETURNS VOID STRICT VOLATILE AS $$
BEGIN
-- output table to hold tagged music sessions with latency
CREATE TEMPORARY TABLE sms_music_session_tmp (music_session_id VARCHAR(64) NOT NULL, tag INTEGER, latency INTEGER) ON COMMIT DROP;
-- populate sms_music_session_tmp as all music sessions
-- XXX: we should pass in enough info to match pagination/query to reduce the impact of this step
INSERT INTO sms_music_session_tmp SELECT DISTINCT id, NULL::INTEGER AS tag, NULL::INTEGER AS latency
FROM music_sessions where scheduled_start > (NOW() - (interval '15 minute'));
-- tag accepted rsvp as 1
UPDATE sms_music_session_tmp q SET tag = 1 FROM rsvp_slots s, rsvp_requests_rsvp_slots rrs, rsvp_requests r WHERE
q.music_session_id = s.music_session_id AND
s.id = rrs.rsvp_slot_id AND
rrs.rsvp_request_id = r.id AND
r.user_id = my_user_id AND
rrs.chosen = TRUE AND
q.tag is NULL;
-- tag invitation as 2
UPDATE sms_music_session_tmp q SET tag = 2 FROM invitations i WHERE
q.music_session_id = i.music_session_id AND
i.receiver_id = my_user_id AND
q.tag IS NULL;
-- musician access as 3
UPDATE sms_music_session_tmp q SET tag = 3 FROM music_sessions m WHERE
q.music_session_id = m.id AND
m.musician_access = TRUE AND
q.tag IS NULL;
-- delete anything not tagged
DELETE FROM sms_music_session_tmp WHERE tag IS NULL;
-- output table to hold users involved in the sms_music_session_tmp sessions and their latency
CREATE TEMPORARY TABLE sms_users_tmp (music_session_id VARCHAR(64), user_id VARCHAR(64) NOT NULL, latency INTEGER) ON COMMIT DROP;
IF my_audio_latency > -1 THEN
-- populate sms_users_tmp with users that have an approved RSVP for sessions in the sms_music_session_tmp table, accompanied with full latency and music session
INSERT INTO sms_users_tmp SELECT q.music_session_id, users.id, (s.score+my_audio_latency+users.last_jam_audio_latency)/2 AS latency
FROM sms_music_session_tmp q
INNER JOIN rsvp_slots ON rsvp_slots.music_session_id = q.music_session_id
INNER JOIN rsvp_requests_rsvp_slots ON rsvp_requests_rsvp_slots.rsvp_slot_id = rsvp_slots.id
INNER JOIN rsvp_requests ON rsvp_requests.id = rsvp_requests_rsvp_slots.rsvp_request_id
INNER JOIN users ON rsvp_requests.user_id = users.id
LEFT OUTER JOIN current_scores s ON s.alocidispid = users.last_jam_locidispid
WHERE
s.blocidispid = my_locidispid AND
rsvp_requests_rsvp_slots.chosen = TRUE;
-- populate sms_users_tmp with invited users for session in the sms_music_session_tmp table, accompanied with full latency and music session
-- specify NULL for music_session_id, because we don't want RSVP users to affect the AVG computed for each session later
INSERT INTO sms_users_tmp SELECT NULL, users.id, (s.score+my_audio_latency+users.last_jam_audio_latency)/2 AS latency
FROM sms_music_session_tmp q
INNER JOIN invitations ON invitations.music_session_id = q.music_session_id
INNER JOIN users ON invitations.receiver_id = users.id
LEFT OUTER JOIN current_scores s ON s.alocidispid = users.last_jam_locidispid
WHERE
s.blocidispid = my_locidispid AND
users.id NOT IN (SELECT user_id FROM sms_users_tmp);
END IF;
-- calculate the average latency
UPDATE sms_music_session_tmp q SET latency = (select AVG(u.latency) FROM sms_users_tmp u WHERE
q.music_session_id = u.music_session_id);
RETURN;
END;
$$ LANGUAGE plpgsql;
-- my_audio_latency can have a special value of -1, which means 'unknown'.
CREATE OR REPLACE FUNCTION ams_index (my_user_id VARCHAR, my_locidispid BIGINT, my_audio_latency INTEGER) RETURNS VOID STRICT VOLATILE AS $$
BEGIN
-- output table to hold tagged music sessions with latency
CREATE TEMPORARY TABLE ams_music_session_tmp (music_session_id VARCHAR(64) NOT NULL, tag INTEGER, latency INTEGER) ON COMMIT DROP;
-- populate ams_music_session_tmp as all music sessions
INSERT INTO ams_music_session_tmp SELECT DISTINCT id, NULL::INTEGER AS tag, NULL::INTEGER AS latency
FROM active_music_sessions;
-- TODO worry about active music session where my_user_id is the creator?
-- eh, maybe, but if the music session is active and you're the creator wouldn't you already be in it?
-- so maybe you're on another computer, so why care? plus seth is talking about auto rsvp'ing the session
-- for you, so maybe not a problem.
-- tag accepted rsvp as 1
UPDATE ams_music_session_tmp q SET tag = 1 FROM rsvp_slots s, rsvp_requests_rsvp_slots rrs, rsvp_requests r WHERE
q.music_session_id = s.music_session_id AND
s.id = rrs.rsvp_slot_id AND
rrs.rsvp_request_id = r.id AND
r.user_id = my_user_id AND
rrs.chosen = TRUE AND
q.tag is NULL;
-- tag invitation as 2
UPDATE ams_music_session_tmp q SET tag = 2 FROM invitations i WHERE
q.music_session_id = i.music_session_id AND
i.receiver_id = my_user_id AND
q.tag IS NULL;
-- musician access as 3
UPDATE ams_music_session_tmp q SET tag = 3 FROM music_sessions m WHERE
q.music_session_id = m.id AND
m.musician_access = TRUE AND
q.tag IS NULL;
-- delete anything not tagged
DELETE FROM ams_music_session_tmp WHERE tag IS NULL;
-- output table to hold users involved in the ams_music_session_tmp sessions and their latency
CREATE TEMPORARY TABLE ams_users_tmp (music_session_id VARCHAR(64), user_id VARCHAR(64) NOT NULL, latency INTEGER) ON COMMIT DROP;
IF my_audio_latency > -1 THEN
-- populate ams_users_tmp with users that have a connection for sessions in the ams_music_session_tmp table, accompanied with full latency and music session
INSERT INTO ams_users_tmp SELECT c.music_session_id, c.user_id, (s.score+my_audio_latency+c.last_jam_audio_latency)/2 AS latency
FROM ams_music_session_tmp q
INNER JOIN connections c ON c.music_session_id = q.music_session_id
LEFT OUTER JOIN current_scores s ON s.alocidispid = c.locidispid
WHERE s.blocidispid = my_locidispid;
-- populate ams_users_tmp with users that have an approved RSVP for sessions inthe ams_music_session_tmp table, accompanied with full latency and music session
-- specify NULL for music_session_id, because we don't want RSVP users to affect the AVG computed for each session later
INSERT INTO ams_users_tmp SELECT NULL, users.id, (s.score+my_audio_latency+users.last_jam_audio_latency)/2 AS latency
FROM ams_music_session_tmp q
INNER JOIN rsvp_slots ON rsvp_slots.music_session_id = q.music_session_id
INNER JOIN rsvp_requests_rsvp_slots ON rsvp_requests_rsvp_slots.rsvp_slot_id = rsvp_slots.id
INNER JOIN rsvp_requests ON rsvp_requests.id = rsvp_requests_rsvp_slots.rsvp_request_id
INNER JOIN users ON rsvp_requests.user_id = users.id
LEFT OUTER JOIN current_scores s ON s.alocidispid = users.last_jam_locidispid
WHERE
s.blocidispid = my_locidispid AND
rsvp_requests_rsvp_slots.chosen = TRUE AND
users.id NOT IN (SELECT user_id FROM ams_users_tmp);
END IF;
-- calculate the average latency
UPDATE ams_music_session_tmp q SET latency = (select AVG(u.latency) FROM ams_users_tmp u WHERE
q.music_session_id = u.music_session_id);
RETURN;
END;
$$ LANGUAGE plpgsql;

View File

@ -320,7 +320,8 @@ module JamRuby
connection = Connection.where(user_id: current_user.id, client_id: client_id).first!
my_locidispid = connection.locidispid
my_audio_latency = connection.last_jam_audio_latency || 0 # we can't assume that the user has an audio_latency when searching
# 13 is an average audio gear value we use if they have not qualified any gear
my_audio_latency = connection.last_jam_audio_latency || current_user.last_jam_audio_latency || 13
self.connection.execute("select ams_index('#{current_user.id}'::varchar, #{my_locidispid}::bigint, #{my_audio_latency}::integer)");
end
@ -329,7 +330,7 @@ module JamRuby
# (and tagged) by rsvp'd (1st), invited (2nd), and musician can join (3rd). within a group tagged the
# same, sorted by score. date seems irrelevant as these are active sessions. ams_init must be called
# first.
def self.ams_index(current_user, options = {})
def self.ams_query(current_user, options = {})
client_id = options[:client_id]
genre = options[:genre]
lang = options[:lang]
@ -379,11 +380,11 @@ module JamRuby
}
)
if (offset)
if offset
query = query.offset(offset)
end
if (limit)
if limit
query = query.limit(limit)
end
@ -421,6 +422,30 @@ module JamRuby
.order('ams_users_tmp.music_session_id, ams_users_tmp.user_id')
end
# wrap me in a transaction!
# note that these queries must be actualized before the end of the transaction
# else the temporary tables created by sms_init will be gone.
def self.ams_index(current_user, params)
ActiveMusicSession.ams_init(current_user, params)
music_sessions = ActiveMusicSession.ams_query(current_user,
client_id: params[:client_id],
genre: params[:genre],
lang: params[:lang],
keyword: params[:keyword],
offset: params[:offset],
limit: params[:limit]).all
music_session_users = ActiveMusicSession.ams_users.all
user_scores = {}
music_session_users.each do |user|
user_scores[user.id] = {latency: user.latency}
end
[music_sessions, user_scores]
end
def self.participant_create user, music_session_id, client_id, as_musician, tracks
music_session = MusicSession.find(music_session_id)

View File

@ -20,7 +20,7 @@ module JamRuby
private
def require_sender_in_music_session
unless music_session.part_of_session? sender
if music_session && !music_session.part_of_session?(sender)
errors.add(:music_session, MEMBERSHIP_REQUIRED_OF_MUSIC_SESSION)
end
end

View File

@ -567,8 +567,141 @@ module JamRuby
a.nil? ? nil : a.to_i
end
private
# initialize the two temporary tables we use to drive sms_index and sms_users
def self.sms_init(current_user, options = {})
client_id = options[:client_id]
connection = Connection.where(user_id: current_user.id, client_id: client_id).first!
my_locidispid = connection.locidispid
# 13 is an average audio gear value we use if they have not qualified any gear
my_audio_latency = connection.last_jam_audio_latency || current_user.last_jam_audio_latency || 13
self.connection.execute("select sms_index('#{current_user.id}'::varchar, #{my_locidispid}::bigint, #{my_audio_latency}::integer)");
end
# Generate a list of music sessions (that are active) filtered by genre, language, keyword, and sorted
# (and tagged) by rsvp'd (1st), invited (2nd), and musician can join (3rd). within a group tagged the
# same, sorted by score. date seems irrelevant as these are active sessions. sms_init must be called
# first.
def self.sms_query(current_user, options = {})
client_id = options[:client_id]
genre = options[:genre]
lang = options[:lang]
keyword = options[:keyword]
offset = options[:offset]
limit = options[:limit]
connection = Connection.where(user_id: current_user.id, client_id: client_id).first!
my_locidispid = connection.locidispid
my_audio_latency = connection.last_jam_audio_latency
query = MusicSession
.select('music_sessions.*')
# this is not really needed when sms_music_session_tmp is joined
# unless there is something specific we need out of active_music_sessions
# query = query.joins(
# %Q{
# INNER JOIN
# active_music_sessions
# ON
# active_music_sessions.id = music_sessions.id
# }
# )
# .select('1::integer as tag, 15::integer as latency')
# integrate sms_music_session_tmp into the processing
# then we can join sms_music_session_tmp and not join active_music_sessions
query = query.joins(
%Q{
INNER JOIN
sms_music_session_tmp
ON
sms_music_session_tmp.music_session_id = music_sessions.id
}
)
.select('sms_music_session_tmp.tag, sms_music_session_tmp.latency')
query = query.order(
%Q{
tag, latency, music_sessions.id
}
)
.group(
%Q{
tag, latency, music_sessions.id
}
)
if offset
query = query.offset(offset)
end
if limit
query = query.limit(limit)
end
# cleanse keyword so it is only word characters. ignore if less than 3 characters long or greater than 100
# TODO do we want to force match of whole words only? this matches any substring...
# TODO do we want to match more than one word in the phrase? this matches only the first word...
unless keyword.nil? or keyword.length < 3 or keyword.length > 100
w = keyword.split(/\W+/)
if w.length > 0 and w[0].length >= 3
query = query.where("music_sessions.description like '%#{w[0]}%'")
end
end
query = query.where("music_sessions.genre_id = ?", genre) unless genre.nil?
# TODO filter by lang
return query
end
# returns the set of users in a music_sessions and the music_session they are in and their latency.
# sms_init must be called first.
# user.audio_latency / 2 , + other_user.audio_latency of them / 2, + network latency /2
def self.sms_users
return User.select('users.*, sms_users_tmp.music_session_id, sms_users_tmp.latency')
.joins(
%Q{
INNER JOIN
sms_users_tmp
ON
sms_users_tmp.user_id = users.id
}
)
.order('sms_users_tmp.music_session_id, sms_users_tmp.user_id')
end
# wrap me in a transaction!
# note that these queries must be actualized before the end of the transaction
# else the temporary tables created by sms_init will be gone.
def self.sms_index(current_user, params)
MusicSession.sms_init(current_user, params)
music_sessions = MusicSession.sms_query(current_user,
client_id: params[:client_id],
genre: params[:genre],
lang: params[:lang],
keyword: params[:keyword],
offset: params[:offset],
limit: params[:limit]).all
music_session_users = MusicSession.sms_users.all
user_scores = {}
music_session_users.each do |user|
user_scores[user.id] = {latency: user.latency}
end
[music_sessions, user_scores]
end
private
def generate_share_token
token = loop do

View File

@ -13,7 +13,7 @@ FactoryGirl.define do
country "US"
musician true
terms_of_service true
last_jam_audio_latency 5
#u.association :musician_instrument, factory: :musician_instrument, user: u
@ -94,10 +94,15 @@ FactoryGirl.define do
genre JamRuby::Genre.first
association :creator, :factory => :user
open_rsvps false
scheduled_start Time.now
factory :recurring_music_session_weekly do
recurring_mode 'weekly'
end
after(:create) do |music_session|
FactoryGirl.create(:chosen_rsvp_slot, user: music_session.creator, music_session: music_session)
end
end
factory :music_session_user_history, :class => JamRuby::MusicSessionUserHistory do
@ -477,7 +482,19 @@ FactoryGirl.define do
end
factory :rsvp_slot, class: JamRuby::RsvpSlot do
proficiency_level 'beginner'
factory :chosen_rsvp_slot do
ignore do
user nil
end
after(:create) { |rsvp_slot, evaluator|
rsvp_request = FactoryGirl.create(:rsvp_request, user: evaluator.user)
rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:rsvp_slot)
}
end
end
factory :rsvp_request, class: JamRuby::RsvpRequest do
@ -485,6 +502,10 @@ FactoryGirl.define do
cancel_all false
end
factory :rsvp_request_rsvp_slot, class: JamRuby::RsvpRequestRsvpSlot do
chosen false
end
factory :latency_tester, :class => JamRuby::LatencyTester do
ignore do
connection nil

View File

@ -356,7 +356,7 @@ describe ActiveMusicSession do
ActiveMusicSession.ams_init(user, client_id: c3.client_id)
music_sessions = ActiveMusicSession.ams_index(user, client_id: c3.client_id).take(100)
music_sessions = ActiveMusicSession.ams_query(user, client_id: c3.client_id).take(100)
music_sessions.should_not be_nil
music_sessions.length.should == 2
music_sessions[0].tag.should_not be_nil
@ -364,7 +364,7 @@ describe ActiveMusicSession do
music_sessions[1].tag.should_not be_nil
music_sessions[1].latency.should_not be_nil
users = ActiveMusicSession.ams_users().take(100)
users = ActiveMusicSession.ams_users.take(100)
users.should_not be_nil
users.length.should == 2
if users[0].music_session_id == earlier_session.id

View File

@ -208,5 +208,185 @@ describe MusicSession do
@music_session.get_connection_ids(exclude_client_id: @connection2.client_id, as_musician: true).should == [@connection1.client_id]
end
end
def sms(user, params)
ActiveRecord::Base.transaction do
return MusicSession.sms_index(user, params)
end
end
describe "sms_index" do
describe "simple" do
let(:conn) { FactoryGirl.create(:connection, user: creator) }
let(:searcher) { FactoryGirl.create(:user) }
let(:searcher_conn) { FactoryGirl.create(:connection, user: searcher, ip_address: '2.2.2.2') }
let(:default_opts) { {client_id: searcher_conn.client_id} }
let(:network_score) { 20 }
before(:each) do
Score.createx(conn.locidispid, conn.client_id, conn.addr, searcher_conn.locidispid, searcher_conn.client_id, searcher_conn.addr, network_score, nil)
end
it "no results" do
music_sessions, user_scores = sms(searcher, default_opts)
music_sessions.length.should == 0
user_scores.length.should == 0
end
it "one session, one RSVP (creator)" do
creator.last_jam_locidispid = conn.locidispid
creator.save!
music_session = FactoryGirl.create(:music_session, creator: creator)
music_sessions, user_scores = sms(searcher, default_opts)
music_sessions.length.should == 1
music_sessions[0].tag.should == 3 # open session sort
music_sessions[0].latency.should == (network_score + searcher.last_jam_audio_latency + creator.last_jam_audio_latency ) / 2
user_scores.length.should == 1
user_scores[creator.id][:latency].should == (network_score + searcher.last_jam_audio_latency + creator.last_jam_audio_latency ) / 2
end
it "filters sessions in the past" do
music_session = FactoryGirl.create(:music_session, creator: creator)
music_sessions, user_scores = sms(searcher, default_opts)
music_sessions.length.should == 1
# 15 minutes is the edge of forgiveness
music_session.scheduled_start = 16.minutes.ago
music_session.save!
music_sessions, user_scores = sms(searcher, default_opts)
music_sessions.length.should == 0
# this should still fall in time
music_session.scheduled_start = 14.minutes.ago
music_session.save!
music_sessions, user_scores = sms(searcher, default_opts)
music_sessions.length.should == 1
end
it "one session, one RSVP (creator), one invitation" do
creator.last_jam_locidispid = conn.locidispid
creator.save!
# create an invitee, and friend them with the creator (you have to be friends to send an invite)
invitee = FactoryGirl.create(:user, last_jam_audio_latency: 30, last_jam_locidispid: 1)
FactoryGirl.create(:friendship, user: creator, friend: invitee)
FactoryGirl.create(:friendship, user: invitee, friend: creator)
music_session = FactoryGirl.create(:music_session, creator: creator)
FactoryGirl.create(:invitation, receiver:invitee, sender:creator, music_session: music_session)
# create a score between invitee, and searcher
Score.createx(invitee.last_jam_locidispid, 'immaterial', 1, searcher_conn.locidispid, searcher_conn.client_id, searcher_conn.addr, network_score, nil)
music_sessions, user_scores = sms(searcher, default_opts)
music_sessions.length.should == 1
music_sessions[0].tag.should == 3 # open session sort
music_sessions[0].latency.should == (network_score + searcher.last_jam_audio_latency + creator.last_jam_audio_latency ) / 2
user_scores.length.should == 2 # the creator, and the invitee
user_scores[creator.id][:latency].should == (network_score + searcher.last_jam_audio_latency + creator.last_jam_audio_latency ) / 2
user_scores[invitee.id][:latency].should == ((network_score + searcher.last_jam_audio_latency + invitee.last_jam_audio_latency ) / 2).ceil
#search with the invitee this time.
invitee_conn = FactoryGirl.create(:connection, user: invitee, ip_address: '3.3.3.3', locidispid: invitee.last_jam_locidispid)
music_sessions, user_scores = sms(invitee, {client_id: invitee_conn.client_id})
music_sessions.length.should == 1
music_sessions[0].tag.should == 2 # invited sort
music_sessions[0].latency.should == ((network_score + invitee.last_jam_audio_latency + creator.last_jam_audio_latency ) / 2).ceil
user_scores.length.should == 1 # the creator, and the invitee
user_scores[creator.id][:latency].should == ((network_score + invitee.last_jam_audio_latency + creator.last_jam_audio_latency ) / 2).ceil
end
end
describe "sorting" do
let!(:creator_1) { FactoryGirl.create(:user, last_jam_locidispid: 4, last_jam_audio_latency: 8) }
let!(:creator_conn_1) { FactoryGirl.create(:connection, user: creator_1, ip_address: '4.4.4.4', locidispid: 4, addr:4) }
let!(:creator_2) { FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 10) }
let!(:creator_conn_2) { FactoryGirl.create(:connection, user: creator_2, ip_address: '4.4.4.4', locidispid: 1, addr:1) }
let!(:creator_3) { FactoryGirl.create(:user, last_jam_locidispid: 2, last_jam_audio_latency: 12) }
let!(:creator_conn_3) { FactoryGirl.create(:connection, user: creator_3, ip_address: '5.5.5.5', locidispid: 2, addr:2) }
let!(:searcher_1) { FactoryGirl.create(:user, last_jam_locidispid: 5, last_jam_audio_latency: 6) }
let!(:searcher_conn_1) { FactoryGirl.create(:connection, user: searcher_1, ip_address: '8.8.8.8', locidispid: 5, addr:5) }
let!(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) }
let!(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) }
let!(:music_session_1) { FactoryGirl.create(:music_session, creator: creator_1) }
let!(:music_session_2) { FactoryGirl.create(:music_session, creator: creator_2) }
let!(:music_session_3) { FactoryGirl.create(:music_session, creator: creator_3) }
let(:good_network_score) { 20 }
let(:fair_network_score) { 30 }
let(:bad_network_score) { 40 }
before(:each) do
# add an RSVP for searcher_1 to music_session_1
searcher_rsvp_slot = FactoryGirl.create(:rsvp_slot, music_session: music_session_1, instrument: Instrument.find('piano'))
searcher_rsvp_request = FactoryGirl.create(:rsvp_request, user: searcher_1)
searcher_rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: searcher_rsvp_request, rsvp_slot: searcher_rsvp_slot)
# add an invitation to searcher_1 to music_session_2
FactoryGirl.create(:friendship, user: creator_2, friend: searcher_1)
FactoryGirl.create(:friendship, user: searcher_1, friend: creator_2)
FactoryGirl.create(:invitation, receiver:searcher_1, sender:creator_2, music_session: music_session_2)
end
it "searcher_1" do
# create a bad score between searcher_1 and creator_1 (but we should still see it sort 1st because it's got an RSVP to the searcher)
Score.createx(searcher_conn_1.locidispid, searcher_conn_1.client_id, searcher_conn_1.addr, creator_conn_1.locidispid, creator_conn_1.client_id, creator_conn_1.addr, bad_network_score, nil)
# create a fair score between searcher_1 and creator_2 (but we should still see it sort 2st because it's got an invitation to the searcher)
Score.createx(searcher_conn_1.locidispid, searcher_conn_1.client_id, searcher_conn_1.addr, creator_conn_2.locidispid, creator_conn_2.client_id, creator_conn_2.addr, fair_network_score, nil)
# create a good score between searcher_1 and creator_3 (but we should still see it sort last because it's an open session; no affiliation with the searcher)
Score.createx(searcher_conn_1.locidispid, searcher_conn_1.client_id, searcher_conn_1.addr, creator_conn_3.locidispid, creator_conn_3.client_id, creator_conn_3.addr, good_network_score, nil)
music_sessions, user_scores = sms(searcher_1, {client_id: searcher_conn_1.client_id})
music_sessions.length.should == 3
music_session = music_sessions[0]
music_session.should == music_session_1
music_session.tag.should == 1 # RSVP
music_session.latency.should == (bad_network_score + searcher_1.last_jam_audio_latency + creator_1.last_jam_audio_latency ) / 2
music_session = music_sessions[1]
music_session.should == music_session_2
music_session.tag.should == 2 # INVITE
music_session.latency.should == (fair_network_score + searcher_1.last_jam_audio_latency + creator_2.last_jam_audio_latency ) / 2
music_session = music_sessions[2]
music_session.should == music_session_3
music_session.tag.should == 3 # OPEN
music_session.latency.should == (good_network_score + searcher_1.last_jam_audio_latency + creator_3.last_jam_audio_latency ) / 2
user_scores.length.should == 3 # the creator, and the invitee
user_scores[creator_1.id][:latency].should == (bad_network_score + searcher_1.last_jam_audio_latency + creator_1.last_jam_audio_latency ) / 2
# let's make music_session_3 invisible, and verify the count goes to 2
music_session_3.musician_access = false
music_session_3.save!
music_sessions, user_scores = sms(searcher_1, {client_id: searcher_conn_1.client_id})
music_sessions.length.should == 2
# let's make music_session_2 invisible, but still the count should be the same (because searcher_1 have an invite)
music_session_2.musician_access = false
music_session_2.save!
music_sessions, user_scores = sms(searcher_1, {client_id: searcher_conn_1.client_id})
music_sessions.length.should == 2
# and lastly with music_session_1, make it invisible, and still it should be visible to the searcher (because searcher_1 has an invite)
music_session_1.musician_access = false
music_session_1.save!
music_sessions, user_scores = sms(searcher_1, {client_id: searcher_conn_1.client_id})
music_sessions.length.should == 2
end
end
end
end

View File

@ -60,38 +60,31 @@ class ApiMusicSessionsController < ApiController
limit: params[:limit])
end
def ams_index
def ams_index
# returns a relation which will produce a list of music_sessions which are active and augmented with attributes
# tag and latency, then sorted by tag, latency, and finally music_sessions.id (for stability). the list is
# filtered by genre, lang, and keyword, then paged by offset and limit (those are record numbers not page numbers).
# tag is 1 for chosen rsvp'd sessions, 2 for invited sessions, 3 for all others (musician_access). if you're the
# creator of a session it will be treated the same as if you had rsvp'd and been accepted.
# tag is 1 for chosen rsvp'd sessions, 2 for invited sessions, 3 for all others (musician_access).
# TODO: if you're the creator of a session it will be treated the same as if you had rsvp'd and been accepted.
ActiveRecord::Base.transaction do
ActiveMusicSession.ams_init(current_user, client_id: params[:client_id])
# note that these queries must be actualized before the end of the transaction
# else the temporary tables created by ams_init will be gone. it would be better
# to have the caller wrap the whole thing in a transaction and then remove the
# .all calls here.
@music_sessions = ActiveMusicSession.ams_index(current_user,
client_id: params[:client_id],
genre: params[:genre],
lang: params[:lang],
keyword: params[:keyword],
offset: params[:offset],
limit: params[:limit]).all
music_session_users = ActiveMusicSession.ams_users.all
@user_scores = {}
music_session_users.each do |user|
@user_scores[user.id] = {latency: user.latency}
ActiveRecord::Base.transaction do
@music_sessions, @user_scores = ActiveMusicSession.ams_index(current_user, params)
end
end
end
def sms_index
# returns a relation which will produce a list of music_sessions which are scheduled and augmented with attributes
# tag and latency, then sorted by tag, latency, and finally music_sessions.id (for stability). the list is
# filtered by genre, lang, and keyword, then paged by offset and limit (those are record numbers not page numbers).
# tag is 1 for chosen rsvp'd sessions, 2 for invited sessions, 3 for all others (musician_access).
# TODO: if you're the creator of a session it will be treated the same as if you had rsvp'd and been accepted.
ActiveRecord::Base.transaction do
@music_sessions, @user_scores = MusicSession.sms_index(current_user, params)
end
end
def scheduled
@music_sessions = MusicSession.scheduled(current_user)
end

View File

@ -72,6 +72,10 @@ else
child({:invitations => :invitations}) {
attributes :id, :sender_id, :receiver_id
node do |invitation|
{ latency: user_score(invitation.receiver.id) }
end
}
child({:approved_rsvps => :approved_rsvps}) {
@ -83,7 +87,6 @@ else
}
child(:active_music_session => :active_music_session) {
attributes :claimed_recording_initiator_id, :track_changes_counter

View File

@ -0,0 +1,3 @@
object @music_sessions
extends "api_music_sessions/show_history"

View File

@ -154,6 +154,7 @@ SampleApp::Application.routes.draw do
match '/sessions/scheduled' => 'api_music_sessions#scheduled', :via => :get
match '/sessions/legacy' => 'api_music_sessions#create_legacy', :via => :post
match '/sessions/active' => 'api_music_sessions#ams_index', :via => :get
match '/sessions/nonactive' => 'api_music_sessions#sms_index', :via => :get
match '/sessions/:id' => 'api_music_sessions#show', :via => :get, :as => 'api_session_detail'
match '/sessions/:id' => 'api_music_sessions#update', :via => :put
match '/sessions/:id' => 'api_music_sessions#session_update', :via => :post

View File

@ -4,19 +4,19 @@ describe ApiMusicSessionsController do
render_views
let(:tracks) { [{'sound' => 'mono', 'client_track_id' => 'abc', 'instrument_id' => 'piano'}] }
let(:user) { FactoryGirl.create(:user) }
let (:conn) { FactoryGirl.create(:connection, :user => user) }
let(:other) { FactoryGirl.create(:user) }
let (:other_conn) { FactoryGirl.create(:connection, user: other, ip_address: '2.2.2.2') }
let(:user) { FactoryGirl.create(:user, last_jam_locidispid: 1) }
let(:conn) { FactoryGirl.create(:connection, :user => user, ip_address: '1.1.1.1', locidispid:1, addr:1) }
let(:other) { FactoryGirl.create(:user, last_jam_locidispid: 2) }
let(:other_conn) { FactoryGirl.create(:connection, user: other, ip_address: '2.2.2.2', locidispid:2, addr:2) }
let(:third_user) { FactoryGirl.create(:user) }
let(:network_score) { 20 }
before(:each) do
controller.current_user = user
ActiveMusicSession.delete_all
MusicSession.delete_all
Score.delete_all
end
describe "ams_index" do
@ -57,7 +57,7 @@ describe ApiMusicSessionsController do
ams = FactoryGirl.create(:active_music_session, creator: other)
other_conn.join_the_session(ams.music_session, true, tracks, other, 10)
other_conn.errors.any?.should be_false
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, 20, nil)
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, network_score, nil)
get :ams_index, {client_id: conn.client_id}
json = JSON.parse(response.body, :symbolize_names => true)
@ -75,8 +75,9 @@ describe ApiMusicSessionsController do
third_user.last_jam_audio_latency = 10 # RSVP's are an 'offline' search, meaning they use user.last_jam_audio_latency instead of connection.last_jam_audio_latency
third_user.last_jam_locidispid = conn.locidispid
third_user.save!
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, 20, nil)
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, network_score, nil)
# set up a second RSVP (other than the creators, pointing to the third_user)
rsvp_slot = FactoryGirl.create(:rsvp_slot, music_session: ams.music_session, instrument: Instrument.find('piano'))
rsvp_request = FactoryGirl.create(:rsvp_request, user: third_user)
rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:rsvp_slot)
@ -85,8 +86,76 @@ describe ApiMusicSessionsController do
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[0][:active_music_session][:participants][0][:user][:latency].should_not be_nil
json[0][:approved_rsvps][0][:id].should == third_user.id
json[0][:approved_rsvps][0][:latency].should == 17.5.ceil # ( 20 (network score) + 10 (third_user.last_jam_audio_latency)+ 5 (user.last_jam_audio_latency) ) / 2
json[0][:approved_rsvps].length.should == 2
if json[0][:approved_rsvps][0][:id] == third_user.id
found_third_user = json[0][:approved_rsvps][0]
found_creator = json[0][:approved_rsvps][1]
found_creator[:id].should == other.id
else
found_third_user = json[0][:approved_rsvps][1]
found_creator = json[0][:approved_rsvps][0]
found_third_user[:id].should == third_user.id
found_creator[:id].should == other.id
end
found_third_user[:latency].should == nil
found_creator[:latency].should == ((network_score + user.last_jam_audio_latency + other.last_jam_audio_latency ) / 2 ).ceil
end
end
describe "sms_index" do
it "no results" do
get :sms_index, {client_id: conn.client_id}
response.should be_success
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 0
end
it "just self" do
# create a session with self in it
sms = FactoryGirl.create(:music_session, creator: user)
get :sms_index, {client_id: conn.client_id}
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[0][:approved_rsvps][0][:id].should == user.id
json[0][:approved_rsvps][0][:latency].should be_nil # you don't get scores to self
end
it "someone else with no score with self" do
# create a session with someone else in it, but no score
sms = FactoryGirl.create(:music_session, creator: other)
get :sms_index, {client_id: conn.client_id}
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[0][:approved_rsvps][0][:id].should == other.id
json[0][:approved_rsvps][0][:latency].should be_nil # there is no score with 'other '
end
it "scores with invitees and RSVP's" do
# create a session with someone else in it, but no score
sms = FactoryGirl.create(:music_session, creator: other)
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, network_score, nil)
invitee = FactoryGirl.create(:user, last_jam_audio_latency: 30, last_jam_locidispid: 3)
FactoryGirl.create(:friendship, user: other, friend: invitee)
FactoryGirl.create(:friendship, user: invitee, friend: other)
FactoryGirl.create(:invitation, sender:other, receiver:invitee, music_session: sms)
Score.createx(invitee.last_jam_locidispid, 'immaterial', 1, conn.locidispid, conn.client_id, conn.addr, network_score, nil)
get :sms_index, {client_id: conn.client_id}
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[0][:approved_rsvps][0][:id].should == other.id
json[0][:approved_rsvps][0][:latency].should == ((network_score + user.last_jam_audio_latency + other.last_jam_audio_latency) / 2).ceil
json[0][:invitations][0][:receiver_id].should == invitee.id
json[0][:invitations][0][:latency].should == ((network_score + user.last_jam_audio_latency + invitee.last_jam_audio_latency) / 2).ceil
end
end
end

View File

@ -111,10 +111,16 @@ FactoryGirl.define do
genre JamRuby::Genre.first
association :creator, :factory => :user
open_rsvps false
scheduled_start Time.now
factory :recurring_music_session_weekly do
recurring_mode 'weekly'
end
after(:create) do |music_session|
FactoryGirl.create(:chosen_rsvp_slot, user: music_session.creator, music_session: music_session)
end
end
factory :music_session_user_history, :class => JamRuby::MusicSessionUserHistory do
@ -475,9 +481,21 @@ FactoryGirl.define do
message Faker::Lorem.characters(10)
end
end
factory :rsvp_slot, class: JamRuby::RsvpSlot do
proficiency_level 'beginner'
factory :chosen_rsvp_slot do
ignore do
user nil
end
after(:create) { |rsvp_slot, evaluator|
rsvp_request = FactoryGirl.create(:rsvp_request, user: evaluator.user)
rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:rsvp_slot)
}
end
end
factory :rsvp_request, class: JamRuby::RsvpRequest do