Merge branch 'develop' of https://bitbucket.org/jamkazam/jam-cloud into develop

This commit is contained in:
root 2014-07-30 07:13:19 +02:00
commit 915bb4ad13
65 changed files with 1823 additions and 795 deletions

View File

@ -97,7 +97,7 @@ end
# gem 'capistrano'
# To use debugger
gem 'debugger'
#gem 'debugger' # not working with 2.1.2p95
group :development, :test do
gem 'capybara'

View File

@ -0,0 +1,100 @@
ActiveAdmin.register_page "Download CSV" do
menu :parent => 'Score'
page_action :create_csv, :method => :post do
puts params.inspect
start_time = params[:score_exports][:start]
end_time = params[:score_exports][:end]
if start_time.blank?
start_time = '1900-01-01'
end
start_time = "#{start_time}"
if end_time.blank?
end_time = (Time.now + 1.days).strftime('%F')
else
end_time = "#{end_time}"
end
scores = ScoreHistory
.select("from_city, from_regions.regionname as from_region_name, from_countries.countryname as from_country_name, from_isp,
to_city, to_regions.regionname as to_region_name, to_countries.countryname as to_country_name, to_isp,
min(score_histories.score) as min_latency, max(score_histories.score) as max_latency, avg(score_histories.score) as mean_latency, median(CAST(score_histories.score AS NUMERIC)) as median_latency, count(score_histories.score) as score_count")
.joins('LEFT JOIN countries AS from_countries ON from_countries.countrycode = from_country')
.joins('LEFT JOIN countries AS to_countries ON to_countries.countrycode = to_country')
.joins('LEFT JOIN regions AS from_regions ON from_regions.region = from_region')
.joins('LEFT JOIN regions AS to_regions ON to_regions.region = to_region')
.where("score_dt BETWEEN DATE '#{start_time}' AND DATE '#{end_time}'")
.order('from_city, from_regions.regionname, from_countries.countryname, from_isp, to_city, to_regions.regionname, to_countries.countryname, to_isp')
.group('from_city, from_regions.regionname, from_countries.countryname, from_isp, to_city, to_regions.regionname, to_countries.countryname, to_isp')
.limit(1_000_000)
csv_string = CSV.generate do |csv|
csv << ["From Country", "From Region", "From City", "From ISP", "To Country", "To Region", "To City", "To ISP", "Min Latency", "Max Latency", "Median Latency", "Mean Latency", 'Score Count']
scores.each do |score|
puts score.inspect
csv << [score.from_country_name, score.from_region_name, score.from_city, score.from_isp,
score.to_country_name, score.to_region_name, score.to_city, score.to_isp,
score[:min_latency], score[:max_latency], score[:median_latency], score[:mean_latency], score[:score_count]]
end
end
send_data csv_string,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename=score_export-#{start_time}-#{end_time}.csv"
end
content :title => "Export Score" do
columns do
column do
semantic_form_for :score_exports, :url => admin_download_csv_create_csv_path, :builder => ActiveAdmin::FormBuilder do |f|
f.inputs do
f.input :start, :as => :datepicker
f.input :end, :as => :datepicker
end
f.actions do
f.action :submit, :label => 'Download CSV'
end
end
end
column do
panel "Usage" do
span "Select a start day and end day to generate a CSV with a score summary. Both fields are optional."
end
panel "Limitation 1" do
div do
span do "The system limits the number of rows exported to 1,000,000" end
end
end
panel "Limitation 2" do
div do
span do "This report uses the score_histories table, which can lag up to 1 hour behind data. You can force a score_history sweep by going to" end
span do link_to "Resque", "#{Gon.global.prefix}/resque/schedule" end
span do " and then clicking 'Queue Now' for ScoreHistorySweeper. When the job count goes from 1 to 0, the score_histories table is now completely up-to-date, and you can make a 'fresh' CSV." end
end
end
end
end
#panel "Upaid Registrations" do
# table_for Registration.unpaid.limit(10).order('created_at desc') do
# column "Registration" do |registration|
# link_to registration.id, admin_registration_path(registration)
# end
# column :user
# column :tour
# column "Payment" do |registration|
# status_tag((registration.paid? ? "Received" : "Pending"), (registration.paid? ? :ok : :warning))
# end
# end
#end
end
end

View File

@ -2,36 +2,81 @@ ActiveAdmin.register JamRuby::ScoreHistory, :as => 'Score History' do
menu :parent => 'Score'
config.batch_actions = false
config.sort_order = 'score_dt_desc'
config.clear_action_items!
config.filters = true
config.per_page = 100
filter :score
filter :score_dt
#filter :from_user_id_eq, :as => :autocomplete, :url => "#{Gon.global.prefix}/admin/users/autocomplete_user_email",
# :label => "From User", :required => false,
# :wrapper_html => { :style => "list-style: none" }
#autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name
filter :from_user_id, as: :string
filter :from_latency_tester_id
filter :from_isp
filter :from_country
filter :from_region
filter :from_city
filter :from_postal
filter :from_latitude
filter :from_latitude
filter :from_longitude
filter :to_user_id, as: :string
filter :to_latency_tester_id
filter :to_isp
filter :to_country
filter :to_region
filter :to_city
filter :to_postal
filter :to_latitude
filter :to_latitude
filter :to_longitude
before_filter only: :index do
@per_page = 1_000_000 if request.format == 'text/csv'
end
index do
column :score
column :score_dt
column "Score", :score
column "When", :score_dt
column "From User", :from_user_id do |score|
link_to score.from_user, admin_user_path(score.from_user) if score.from_user_id
end
column "From Latency Tester", :from_latency_tester_id do |score|
link_to score.from_latency_tester_id, admin_latency_testers_path if score.from_latency_tester_id
end
column "From IP", :from_addr do |score|
IPAddr.new(score.from_addr, Socket::AF_INET).to_s if score.from_addr
end
column "From ISP", :from_isp
column "From Country", :from_country
column "From Region", :from_region
column "From City", :from_city
column "From Postal", :from_postal
column "From Lat", :from_latitude
column "From Long", :from_longitude
column "From Client", :from_client_id
column :from_client_id
column :from_user_id
column :from_latency_tester_id
column :from_addr
column :from_isp
column :from_country
column :from_region
column :from_city
column :from_postal
column :from_latitude
column :from_longitude
column :to_client_id
column :to_user_id
column :to_latency_tester_id
column :to_addr
column :to_isp
column :to_country
column :to_region
column :to_city
column :to_postal
column :to_latitude
column :to_longitude
column "To User", :to_user_id do |score|
link_to score.to_user, admin_user_path(score.to_user) if score.to_user_id
end
column "To Latency Tester", :to_latency_tester_id do |score|
link_to score.to_latency_tester_id, admin_latency_testers_path if score.to_latency_tester_id
end
column "To IP", :to_addr do |score|
IPAddr.new(score.to_addr, Socket::AF_INET).to_s if score.to_addr
end
column "To ISP", :to_isp
column "To Country", :to_country
column "To Region", :to_region
column "To City", :to_city
column "To Postal", :to_postal
column "To Lat", :to_latitude
column "To Long", :to_longitude
column "To Client", :to_client_id
end
end

View File

@ -17,7 +17,7 @@
return $.ajax({
type: "POST",
dataType: "json",
url: gon.global.prefix + 'api/mix/' + mixId + '/enqueue',
url: gon.global.prefix + '/api/mix/' + mixId + '/enqueue',
contentType: 'application/json',
processData: false
});

View File

@ -9,7 +9,7 @@
var $link = $(this);
restAdmin.tryMixAgain({mix_id: $link.attr('data-mix-id')})
.done(function(response) {
$link.closest('div.mix-again').find('div.mix-again-dialog').html('<div>Mix enqueued</div><a href="' + gon.global.prefix + 'resque">Resque Web</a>').dialog();
$link.closest('div.mix-again').find('div.mix-again-dialog').html('<div>Mix enqueued</div><a href="' + gon.global.prefix + '/resque">Resque Web</a>').dialog();
})
.error(function(jqXHR) {
$link.closest('div.mix-again').find('div.mix-again-dialog').html('Mix failed: ' + jqXHR.responseText).dialog();

View File

@ -1 +1 @@
Gon.global.prefix = ENV['RAILS_RELATIVE_URL_ROOT'] || '/'
Gon.global.prefix = ENV['RAILS_RELATIVE_URL_ROOT'] || ''

2
admin/migrate.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
bundle exec jam_db up --connopts=dbname:jam host:localhost user:postgres password:postgres --verbose

View File

@ -195,4 +195,8 @@ max_mind_releases.sql
score_histories.sql
update_sms_index.sql
connection_allow_null_locidispid.sql
track_user_in_scores.sql
track_user_in_scores.sql
median_aggregate.sql
current_scores_use_median.sql
current_scores_ams_index_sms_index_use_user_instrument.sql
locidispid_in_score_histories.sql

View File

@ -0,0 +1,163 @@
-- this adds the user's latency, if available
DROP VIEW current_scores;
CREATE OR REPLACE VIEW current_scores AS
SELECT * FROM (SELECT * , row_number() OVER (PARTITION BY alocidispid, blocidispid, scorer ORDER BY full_score DESC) AS pcnum FROM
(SELECT * FROM
(SELECT percent_rank() over (PARTITION BY alocidispid, blocidispid ORDER BY full_score ASC) AS pc, * FROM
(SELECT tmp.*, (COALESCE(a_users.last_jam_audio_latency, 13) + COALESCE(b_users.last_jam_audio_latency, 13) + tmp.score) AS full_score, a_users.last_jam_audio_latency AS a_audio_latency, b_users.last_jam_audio_latency AS b_audio_latency FROM
(SELECT *, row_number() OVER (PARTITION BY alocidispid, blocidispid ORDER BY scores.created_at DESC) AS rownum FROM scores) tmp
LEFT JOIN users as a_users ON a_users.id = tmp.auserid
LEFT JOIN users as b_users ON b_users.id = tmp.buserid
WHERE rownum < 6) AS score_ranked)
AS tmp2 WHERE pc <= .5 ORDER BY pc DESC) pcs )
AS final WHERE pcnum < 2;
-- check that the music_sessions does not currently have an active_music_sessions
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 IS NULL OR scheduled_start > (NOW() - (interval '15 minute')))
AND canceled = FALSE
AND id NOT IN (SELECT id FROM active_music_sessions);
-- 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.open_rsvps = 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.full_score/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.full_score/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.full_score/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.full_score/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

@ -0,0 +1,13 @@
-- this results in a rough median; the only problem is that we don't avg if it's an even number. not a big deal truthfully, since eventually you'll have > 5
DROP VIEW current_scores;
CREATE OR REPLACE VIEW current_scores AS
SELECT * FROM (SELECT * , row_number() OVER (PARTITION BY alocidispid, blocidispid, scorer ORDER BY score DESC) AS pcnum FROM
(SELECT * FROM
(SELECT percent_rank() over (PARTITION BY alocidispid, blocidispid ORDER BY score ASC) AS pc, * FROM
(SELECT * FROM
(SELECT *, row_number() OVER (PARTITION BY alocidispid, blocidispid ORDER BY created_at DESC) AS rownum FROM scores) tmp
WHERE rownum < 6) AS score_ranked)
AS tmp2 WHERE pc <= .5 ORDER BY pc DESC) pcs )
AS final WHERE pcnum < 2;

View File

@ -0,0 +1,5 @@
-- https://jamkazam.atlassian.net/browse/VRFS-1968
-- store both locids in score_histories
ALTER TABLE score_histories ADD COLUMN from_locidispid BIGINT NOT NULL;
ALTER TABLE score_histories ADD COLUMN to_locidispid BIGINT NOT NULL;

View File

@ -0,0 +1,23 @@
-- from here: https://wiki.postgresql.org/wiki/Aggregate_Median
CREATE OR REPLACE FUNCTION _final_median(numeric[])
RETURNS numeric
AS
$body$
SELECT AVG(val)
FROM (
SELECT val
FROM unnest($1) val
ORDER BY 1
LIMIT 2 - MOD(array_upper($1, 1), 2)
OFFSET CEIL(array_upper($1, 1) / 2.0) - 1
) sub;
$body$
LANGUAGE sql ;
-- IMMUTABLE not accepted by pg migrate
CREATE AGGREGATE median(numeric) (
SFUNC=array_append,
STYPE=numeric[],
FINALFUNC=_final_median,
INITCOND='{}'
);

View File

@ -86,10 +86,10 @@ CREATE OR REPLACE FUNCTION generate_scores_dataset () RETURNS VOID STRICT VOLATI
INSERT INTO cities (city, region, countrycode) select distinct city, region, countrycode from geoiplocations where length(city) > 0 and length(countrycode) > 0;
DELETE FROM regions;
INSERT INTO regions (region, countrycode) select distinct region, countrycode from cities;
INSERT INTO regions (region, regionname, countrycode) select distinct region, region, countrycode from cities;
DELETE FROM countries;
INSERT INTO countries (countrycode) select distinct countrycode from regions;
INSERT INTO countries (countrycode, countryname) select distinct countrycode, countrycode from regions;
END IF;
RETURN;

View File

@ -111,6 +111,7 @@ module JamRuby
new_slot = RsvpSlot.new
new_slot.instrument_id = slot.instrument_id
new_slot.proficiency_level = slot.proficiency_level
new_slot.is_unstructured_rsvp = slot.is_unstructured_rsvp
new_session.rsvp_slots << new_slot
# get the request for this slot that was approved (should only be ONE)
@ -409,7 +410,7 @@ module JamRuby
end
def has_mount?
active_music_session && active_music_session.mount
!active_music_session.nil? && !active_music_session.mount.nil?
end
def can_cancel? user

View File

@ -253,7 +253,7 @@ module JamRuby
# send notification
if music_session.creator.id == user.id
Notification.send_scheduled_session_rsvp_cancelled_org(music_session, user)
Notification.send_scheduled_session_rsvp_cancelled_org(music_session, rsvp_request.user)
else
Notification.send_scheduled_session_rsvp_cancelled(music_session, user)
end

View File

@ -4,6 +4,9 @@ module JamRuby
self.table_name = 'score_histories'
belongs_to :from_user, class_name: 'JamRuby::User', foreign_key: 'from_user_id'
belongs_to :to_user, class_name: 'JamRuby::User', foreign_key: 'to_user_id'
def self.migrate_scores
generic_state = GenericState.singleton
@ -12,14 +15,15 @@ module JamRuby
result = connection.execute(
"INSERT INTO score_histories
(from_client_id, from_user_id, from_latency_tester_id, from_addr, from_isp, from_country, from_region, from_city, from_postal, from_latitude, from_longitude,
to_client_id, to_user_id, to_latency_tester_id, to_addr, to_isp, to_country, to_region, to_city, to_postal, to_latitude, to_longitude,
(from_client_id, from_user_id, from_latency_tester_id, from_addr, from_locidispid, from_isp, from_country, from_region, from_city, from_postal, from_latitude, from_longitude,
to_client_id, to_user_id, to_latency_tester_id, to_addr, to_locidispid, to_isp, to_country, to_region, to_city, to_postal, to_latitude, to_longitude,
score, score_dt, scoring_data)
SELECT
s.anodeid AS from_client_id,
s.auserid AS from_user_id,
s.alatencytestid AS from_latency_tester_id,
s.aaddr AS from_addr,
s.alocidispid AS from_locidispid,
x.company AS from_isp,
a.countrycode AS from_country,
a.region AS from_region,
@ -31,6 +35,7 @@ module JamRuby
s.buserid AS to_user_id,
s.blatencytestid AS to_latency_tester_id,
s.baddr AS to_addr,
s.blocidispid AS to_locidispid,
y.company AS to_isp,
b.countrycode AS to_country,
b.region AS to_region,

View File

@ -98,8 +98,11 @@ module JamRuby
M_ORDER_FOLLOWS = ['Most Followed', :followed]
M_ORDER_PLAYS = ['Most Plays', :plays]
M_ORDER_PLAYING = ['Playing Now', :playing]
ORDERINGS = B_ORDERINGS = M_ORDERINGS = [M_ORDER_FOLLOWS, M_ORDER_PLAYS, M_ORDER_PLAYING]
B_ORDERING_KEYS = M_ORDERING_KEYS = M_ORDERINGS.collect { |oo| oo[1] }
M_ORDER_LATENCY = ['Latency To Me', :latency]
M_ORDERINGS = [M_ORDER_LATENCY, M_ORDER_FOLLOWS, M_ORDER_PLAYS]
ORDERINGS = B_ORDERINGS = [M_ORDER_FOLLOWS, M_ORDER_PLAYS, M_ORDER_PLAYING]
M_ORDERING_KEYS = M_ORDERINGS.collect { |oo| oo[1] }
B_ORDERING_KEYS = B_ORDERINGS.collect { |oo| oo[1] }
DISTANCE_OPTS = B_DISTANCE_OPTS = M_DISTANCE_OPTS = [['Any', 0], [1000.to_s, 1000], [500.to_s, 500], [250.to_s, 250], [100.to_s, 100], [50.to_s, 50], [25.to_s, 25]]
@ -150,7 +153,7 @@ module JamRuby
# puts "================ user #{user.inspect}"
# puts "================ conn #{conn.inspect}"
rel = User.musicians_geocoded
rel = User.musicians # not musicians_geocoded on purpose; we allow 'unknowns' to surface in the search page
rel = rel.select('users.*')
rel = rel.group('users.id')
@ -164,7 +167,7 @@ module JamRuby
# filter on scores using selections from params
# see M_SCORE_OPTS
score_limit = TEST_SCORE
score_limit = ANY_SCORE
l = params[:score_limit]
unless l.nil?
score_limit = l
@ -182,6 +185,7 @@ module JamRuby
score_join = 'left outer' # or 'inner'
score_min = nil
score_max = nil
# these score_min, score_max come from here (doubled): https://jamkazam.atlassian.net/browse/VRFS-1962
case score_limit
when GOOD_SCORE
score_join = 'inner'
@ -190,14 +194,14 @@ module JamRuby
when MODERATE_SCORE
score_join = 'inner'
score_min = 40
score_max = 80
score_max = 70
when POOR_SCORE
score_join = 'inner'
score_min = 80
score_max = 120
score_max = 100
when UNACCEPTABLE_SCORE
score_join = 'inner'
score_min = 120
score_min = 100
score_max = nil
when SCORED_SCORE
score_join = 'inner'
@ -216,16 +220,20 @@ module JamRuby
rel = rel.joins("#{score_join} join current_scores on current_scores.alocidispid = users.last_jam_locidispid")
.where(['(current_scores.blocidispid = ? or current_scores.blocidispid is null)', locidispid])
rel = rel.where(['current_scores.score > ?', score_min]) unless score_min.nil?
rel = rel.where(['current_scores.score <= ?', score_max]) unless score_max.nil?
rel = rel.joins('LEFT JOIN regions ON regions.countrycode = users.country AND regions.region = users.state')
rel = rel.select('current_scores.score')
rel = rel.group('current_scores.score')
rel = rel.where(['current_scores.full_score > ?', score_min]) unless score_min.nil?
rel = rel.where(['current_scores.full_score <= ?', score_max]) unless score_max.nil?
rel = rel.select('current_scores.full_score, current_scores.score, current_scores.b_audio_latency as audio_latency, regions.regionname')
rel = rel.group('current_scores.full_score, current_scores.score, current_scores.b_audio_latency, regions.regionname')
end
ordering = self.order_param(params)
# puts "================ ordering #{ordering}"
case ordering
when :latency
# nothing to do. the sort added below 'current_scores.score ASC NULLS LAST' handles this
when :plays # FIXME: double counting?
# sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}"
rel = rel.select('COUNT(records.id)+COUNT(sessions.id) AS search_play_count')
@ -242,7 +250,7 @@ module JamRuby
end
unless locidispid.nil?
rel = rel.order('current_scores.score ASC NULLS LAST')
rel = rel.order('current_scores.full_score ASC NULLS LAST')
end
rel = rel.order('users.created_at DESC')
@ -386,15 +394,15 @@ module JamRuby
# an offline process and thus uses the last jam location as "home base"
locidispid = usr.last_jam_locidispid
score_limit = 60
score_limit = 70
limit = 50
rel = User.musicians_geocoded
.where(['created_at >= ? AND users.id != ?', since_date, usr.id])
.where(['users.created_at >= ? AND users.id != ?', since_date, usr.id])
.joins('inner join current_scores on users.last_jam_locidispid = current_scores.alocidispid')
.where(['current_scores.blocidispid = ?', locidispid])
.where(['current_scores.score <= ?', score_limit])
.order('current_scores.score') # best scores first
.where(['current_scores.full_score <= ?', score_limit])
.order('current_scores.full_score') # best scores first
.order('users.created_at DESC') # then most recent
.limit(limit)
@ -418,7 +426,7 @@ module JamRuby
rel = GeoIpLocations.where_latlng(rel, params, current_user)
sel_str = 'bands.*'
case ordering = self.order_param(params)
case ordering = self.order_param(params, B_ORDERING_KEYS)
when :plays # FIXME: double counting?
sel_str = "COUNT(records)+COUNT(msh) AS play_count, #{sel_str}"
rel = rel.joins("LEFT JOIN music_sessions AS msh ON msh.band_id = bands.id")

View File

@ -116,6 +116,9 @@ module JamRuby
# diagnostics
has_many :diagnostics, :class_name => "JamRuby::Diagnostic"
# score history
has_many :from_score_histories, :class_name => "JamRuby::ScoreHistory", foreign_key: 'from_user_id'
has_many :to_score_histories, :class_name => "JamRuby::ScoreHistory", foreign_key: 'to_user_id'
# This causes the authenticate method to be generated (among other stuff)
#has_secure_password

View File

@ -27,8 +27,8 @@ module JamRuby
end
def run
# get all weekly sessions that have ended in the last 15 minutes
criteria = "recurring_mode = 'weekly' AND session_removed_at is not null AND canceled = false AND next_session_scheduled = false"
# get all weekly sessions that started at least 4 hours ago
criteria = "recurring_mode = 'weekly' AND scheduled_start + interval '4hours' < NOW() AND canceled = false AND next_session_scheduled = false"
MusicSession.find_each(:conditions => criteria) do |music_session|
music_session.copy
end

View File

@ -354,8 +354,8 @@ describe ActiveMusicSession do
user = FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 5)
c3 = FactoryGirl.create(:connection, user: user, locidispid: 1, last_jam_audio_latency: 5)
Score.createx(c1.locidispid, c1.client_id, c1.addr, c3.locidispid, c3.client_id, c3.addr, 20, nil)
Score.createx(c2.locidispid, c2.client_id, c2.addr, c3.locidispid, c3.client_id, c3.addr, 30, nil)
Score.createx(c1.locidispid, c1.client_id, c1.addr, c3.locidispid, c3.client_id, c3.addr, 20, nil, nil, {auserid: creator.id, buserid: user.id})
Score.createx(c2.locidispid, c2.client_id, c2.addr, c3.locidispid, c3.client_id, c3.addr, 30, nil, nil, {auserid: creator2.id, buserid: user.id})
# make a transaction

View File

@ -421,7 +421,7 @@ describe MusicSession do
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)
Score.createx(conn.locidispid, conn.client_id, conn.addr, searcher_conn.locidispid, searcher_conn.client_id, searcher_conn.addr, network_score, nil, nil, {auserid: creator.id, buserid: searcher.id})
end
it "no results" do
@ -499,7 +499,7 @@ describe MusicSession do
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)
Score.createx(invitee.last_jam_locidispid, 'immaterial', 1, searcher_conn.locidispid, searcher_conn.client_id, searcher_conn.addr, network_score, nil, nil, {auserid: invitee.id, buserid: searcher.id})
music_sessions, user_scores = sms(searcher, default_opts)
music_sessions.length.should == 1
@ -580,14 +580,16 @@ describe MusicSession do
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)
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, nil, {auserid: searcher_1.id, buserid: creator_1.id})
# 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)
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, nil, {auserid: searcher_1.id, buserid: creator_2.id})
# 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)
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, nil, {auserid: searcher_1.id, buserid: creator_3.id})
music_sessions, user_scores = sms(searcher_1, {client_id: searcher_conn_1.client_id})

View File

@ -30,7 +30,10 @@ describe 'Musician search' do
@geomusicians << @user4
@geomusicians << @user5
Score.delete_all
# from these scores:
# user1 has scores other users in user1 location, and with user2, user3, user4
# user2 has scores with users in user3 and user4 location
# Score.delete_all
Score.createx(1, 'a', 1, 1, 'a', 1, 10)
Score.createx(1, 'a', 1, 2, 'b', 2, 20)
Score.createx(1, 'a', 1, 3, 'c', 3, 30)
@ -43,46 +46,46 @@ describe 'Musician search' do
it "finds all musicians" do
# expects all the musicians (geocoded)
results = Search.musician_filter
results = Search.musician_filter({score_limit: Search::TEST_SCORE})
results.search_type.should == :musicians_filter
results.results.count.should == @geomusicians.length
results.results.should eq @geomusicians.reverse
results.results.count.should == @musicians.length
results.results.should eq @musicians.reverse
end
it "finds all musicians page 1" do
# expects all the musicians
results = Search.musician_filter({page: 1})
results = Search.musician_filter({page: 1, score_limit: Search::TEST_SCORE})
results.search_type.should == :musicians_filter
results.results.count.should == @geomusicians.length
results.results.should eq @geomusicians.reverse
results.results.count.should == @musicians.length
results.results.should eq @musicians.reverse
end
it "finds all musicians page 2" do
# expects no musicians (all fit on page 1)
results = Search.musician_filter({page: 2})
results = Search.musician_filter({page: 2, score_limit: Search::TEST_SCORE})
results.search_type.should == :musicians_filter
results.results.count.should == 0
end
it "finds all musicians page 1 per_page 3" do
# expects three of the musicians
results = Search.musician_filter({per_page: 3})
results = Search.musician_filter({per_page: 3, score_limit: Search::TEST_SCORE})
results.search_type.should == :musicians_filter
results.results.count.should == 3
results.results.should eq @geomusicians.reverse.slice(0, 3)
results.results.should eq @musicians.reverse.slice(0, 3)
end
it "finds all musicians page 2 per_page 3" do
# expects two of the musicians
results = Search.musician_filter({page: 2, per_page: 3})
results = Search.musician_filter({page: 2, per_page: 3, score_limit: Search::TEST_SCORE})
results.search_type.should == :musicians_filter
results.results.count.should == 2
results.results.should eq @geomusicians.reverse.slice(3, 3)
results.results.count.should == 3
results.results.should eq @musicians.reverse.slice(3, 3)
end
it "finds all musicians page 3 per_page 3" do
# expects two of the musicians
results = Search.musician_filter({page: 3, per_page: 3})
results = Search.musician_filter({page: 3, per_page: 3, score_limit: Search::TEST_SCORE})
results.search_type.should == :musicians_filter
results.results.count.should == 0
end
@ -146,7 +149,7 @@ describe 'Musician search' do
f3.save
# @user2.followers.concat([@user3, @user4, @user2])
results = Search.musician_filter({ :per_page => @musicians.size }, @user3)
results = Search.musician_filter({ :per_page => @musicians.size, score_limit: Search::TEST_SCORE, orderby: 'followed'}, @user3)
expect(results.results[0].id).to eq(@user2.id)
# check the follower count for given entry
@ -157,7 +160,7 @@ describe 'Musician search' do
it 'paginates properly' do
# make sure pagination works right
params = { :per_page => 2, :page => 1 }
params = { :per_page => 2, :page => 1 , score_limit: Search::TEST_SCORE}
results = Search.musician_filter(params)
expect(results.results.count).to be 2
end
@ -218,7 +221,7 @@ describe 'Musician search' do
# create friendship record
Friendship.save(@user1.id, @user2.id)
# search on user2
results = Search.musician_filter({}, @user2)
results = Search.musician_filter({score_limit: Search::TEST_SCORE}, @user2)
friend = results.results.detect { |mm| mm.id == @user1.id }
expect(friend).to_not be_nil
expect(results.friend_count(friend)).to be 1
@ -239,7 +242,7 @@ describe 'Musician search' do
expect(recording.claimed_recordings.length).to be 1
expect(@user1.recordings.detect { |rr| rr == recording }).to_not be_nil
results = Search.musician_filter({},@user1)
results = Search.musician_filter({score_limit: Search::TEST_SCORE},@user1)
# puts "====================== results #{results.inspect}"
uu = results.results.detect { |mm| mm.id == @user1.id }
expect(uu).to_not be_nil
@ -257,7 +260,7 @@ describe 'Musician search' do
make_recording(@user1)
# order results by num recordings
results = Search.musician_filter({ :orderby => 'plays' }, @user2)
results = Search.musician_filter({ orderby: 'plays', score_limit: Search::TEST_SCORE}, @user2)
# puts "========= results #{results.inspect}"
expect(results.results.length).to eq(2)
expect(results.results[0].id).to eq(@user1.id)
@ -266,23 +269,24 @@ describe 'Musician search' do
# add more data and make sure order still correct
make_recording(@user3)
make_recording(@user3)
results = Search.musician_filter({ :orderby => 'plays' }, @user2)
results = Search.musician_filter({ :orderby => 'plays', score_limit: Search::TEST_SCORE }, @user2)
expect(results.results.length).to eq(2)
expect(results.results[0].id).to eq(@user3.id)
expect(results.results[1].id).to eq(@user1.id)
end
it "by now playing" do
pending "these tests worked, so leaving them in, but we don't currently have 'Now Playing' in the find musicians screen"
# should get 1 result with 1 active session
make_session(@user1)
results = Search.musician_filter({ :orderby => 'playing' }, @user2)
results = Search.musician_filter({ :orderby => 'playing', score_limit: Search::TEST_SCORE}, @user2)
expect(results.results.count).to be 1
expect(results.results.first.id).to eq(@user1.id)
# should get 2 results with 2 active sessions
# sort order should be created_at DESC
make_session(@user3)
results = Search.musician_filter({ :orderby => 'playing' }, @user2)
results = Search.musician_filter({ :orderby => 'playing', score_limit: Search::TEST_SCORE}, @user2)
expect(results.results.count).to be 2
expect(results.results[0].id).to eq(@user3.id)
expect(results.results[1].id).to eq(@user1.id)
@ -299,7 +303,7 @@ describe 'Musician search' do
@user1.reload
ii = @user1.instruments.detect { |inst| inst.id == 'tuba' }
expect(ii).to_not be_nil
results = Search.musician_filter({ :instrument => ii.id })
results = Search.musician_filter({ :instrument => ii.id, score_limit: Search::TEST_SCORE })
results.results.each do |rr|
expect(rr.instruments.detect { |inst| inst.id=='tuba' }.id).to eq(ii.id)
end
@ -313,13 +317,13 @@ describe 'Musician search' do
# short distance
results = Search.musician_filter({ :per_page => num,
:distance => 10,
:city => 'Apex' }, @user1)
:city => 'Apex', score_limit: Search::TEST_SCORE }, @user1)
expect(results.results.count).to be num
# long distance
results = Search.musician_filter({ :per_page => num,
:distance => 1000,
:city => 'Miami',
:state => 'FL' }, @user1)
:state => 'FL', score_limit: Search::TEST_SCORE }, @user1)
expect(results.results.count).to be num
end
@ -327,14 +331,14 @@ describe 'Musician search' do
pending 'distance search changes'
expect(@user1.lat).to_not be_nil
# uses the location of @user1
results = Search.musician_filter({ :distance => 10, :per_page => User.musicians.count }, @user1)
results = Search.musician_filter({ :distance => 10, :per_page => User.musicians.count, score_limit: Search::TEST_SCORE }, @user1)
expect(results.results.count).to be User.musicians.count
end
it "finds no musicians within a given distance of location" do
pending 'distance search changes'
expect(@user1.lat).to_not be_nil
results = Search.musician_filter({ :distance => 10, :city => 'San Francisco' }, @user1)
results = Search.musician_filter({ :distance => 10, :city => 'San Francisco', score_limit: Search::TEST_SCORE }, @user1)
expect(results.results.count).to be 0
end

View File

@ -38,6 +38,7 @@ describe ScoreHistory do
score_history.from_user_id.should == score1.auserid
score_history.from_latency_tester_id.should == score1.alatencytestid
score_history.from_addr.should == score1.aaddr
score_history.from_locidispid.should == score1.alocidispid
score_history.from_isp.should == austin[:jamisp].jam_company.company
score_history.from_country.should == austin[:geoiplocation].countrycode
score_history.from_region.should == austin[:geoiplocation].region
@ -49,6 +50,7 @@ describe ScoreHistory do
score_history.to_user_id.should == score1.buserid
score_history.to_latency_tester_id.should == score1.blatencytestid
score_history.to_addr.should == score1.baddr
score_history.to_locidispid.should == score1.blocidispid
score_history.to_isp.should == dallas[:jamisp].jam_company.company
score_history.to_country.should == dallas[:geoiplocation].countrycode
score_history.to_region.should == dallas[:geoiplocation].region

View File

@ -10,141 +10,305 @@ describe Score do
let(:latency_tester2) { FactoryGirl.create(:latency_tester) }
let(:score_with_latency_tester) { s1, s2 = Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo', { alatencytestid: latency_tester1.id, blatencytestid: latency_tester2.id}); s1 }
before do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 30, nil)
Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 40, Time.new.utc-3600)
end
it "count" do
Score.count.should == 6
end
it 'a to b' do
s = Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first
s.should_not be_nil
s.alocidispid.should == 1234
s.anodeid.should eql('anodeid')
s.aaddr.should == 0x01020304
s.blocidispid.should == 2345
s.bnodeid.should eql('bnodeid')
s.baddr.should == 0x02030405
s.score.should == 20
s.scorer.should == 0
s.score_dt.should_not be_nil
s.scoring_data.should eq('foo')
end
it 'b to a' do
s = Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first
s.should_not be_nil
s.alocidispid.should == 2345
s.anodeid.should eql('bnodeid')
s.aaddr.should == 0x02030405
s.blocidispid.should == 1234
s.bnodeid.should eql('anodeid')
s.baddr.should == 0x01020304
s.score.should == 20
s.scorer.should == 1
s.score_dt.should_not be_nil
s.scoring_data.should be_nil
end
it 'a to c' do
s = Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first
s.should_not be_nil
s.alocidispid.should == 1234
s.anodeid.should eql('anodeid')
s.aaddr.should == 0x01020304
s.blocidispid.should == 3456
s.bnodeid.should eql('cnodeid')
s.baddr.should == 0x03040506
s.score.should == 30
s.scorer.should == 0
s.score_dt.should_not be_nil
s.scoring_data.should be_nil
end
it 'c to a' do
s = Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first
s.should_not be_nil
s.alocidispid.should == 3456
s.anodeid.should eql('cnodeid')
s.aaddr.should == 0x03040506
s.blocidispid.should == 1234
s.bnodeid.should eql('anodeid')
s.baddr.should == 0x01020304
s.score.should == 30
s.scorer.should == 1
s.score_dt.should_not be_nil
s.scoring_data.should be_nil
end
it 'delete a to c' do
Score.deletex(1234, 3456)
Score.count.should == 2
Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first.should be_nil
Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first.should be_nil
Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first.should_not be_nil
Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first.should_not be_nil
end
it 'findx' do
Score.findx(1234, 1234).should == -1
Score.findx(1234, 2345).should == 20
Score.findx(1234, 3456).should == 30
Score.findx(2345, 1234).should == 20
Score.findx(2345, 2345).should == -1
Score.findx(2345, 3456).should == -1
Score.findx(3456, 1234).should == 30
Score.findx(3456, 2345).should == -1
Score.findx(3456, 3456).should == -1
end
it "test shortcut for making scores from connections" do
user1 = FactoryGirl.create(:user)
conn1 = FactoryGirl.create(:connection, user: user1, addr: 0x01020304, locidispid: 5)
user2 = FactoryGirl.create(:user)
conn2 = FactoryGirl.create(:connection, user: user2, addr: 0x11121314, locidispid: 6)
user3 = FactoryGirl.create(:user)
conn3 = FactoryGirl.create(:connection, user: user3, addr: 0x21222324, locidispid: 7)
Score.findx(5, 6).should == -1
Score.findx(6, 5).should == -1
Score.findx(5, 7).should == -1
Score.findx(7, 5).should == -1
Score.findx(6, 7).should == -1
Score.findx(7, 6).should == -1
Score.score_conns(conn1, conn2, 12)
Score.score_conns(conn1, conn3, 13)
Score.score_conns(conn2, conn3, 23)
Score.findx(5, 6).should == 12
Score.findx(6, 5).should == 12
Score.findx(5, 7).should == 13
Score.findx(7, 5).should == 13
Score.findx(6, 7).should == 23
Score.findx(7, 6).should == 23
end
describe "createx" do
it "creates with user info" do
score_with_user.touch
score_with_user.auserid.should == user1.id
score_with_user.buserid.should == user2.id
score_with_user.alatencytestid.should be_nil
score_with_user.blatencytestid.should be_nil
describe "with default scores" do
before do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 30, nil)
Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 40, Time.new.utc-3600)
end
it "creates with latency-tester info" do
score_with_latency_tester.touch
score_with_latency_tester.auserid.should be_nil
score_with_latency_tester.buserid.should be_nil
score_with_latency_tester.alatencytestid.should == latency_tester1.id
score_with_latency_tester.blatencytestid.should == latency_tester2.id
it "count" do
Score.count.should == 6
end
it 'a to b' do
s = Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first
s.should_not be_nil
s.alocidispid.should == 1234
s.anodeid.should eql('anodeid')
s.aaddr.should == 0x01020304
s.blocidispid.should == 2345
s.bnodeid.should eql('bnodeid')
s.baddr.should == 0x02030405
s.score.should == 20
s.scorer.should == 0
s.score_dt.should_not be_nil
s.scoring_data.should eq('foo')
end
it 'b to a' do
s = Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first
s.should_not be_nil
s.alocidispid.should == 2345
s.anodeid.should eql('bnodeid')
s.aaddr.should == 0x02030405
s.blocidispid.should == 1234
s.bnodeid.should eql('anodeid')
s.baddr.should == 0x01020304
s.score.should == 20
s.scorer.should == 1
s.score_dt.should_not be_nil
s.scoring_data.should be_nil
end
it 'a to c' do
s = Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first
s.should_not be_nil
s.alocidispid.should == 1234
s.anodeid.should eql('anodeid')
s.aaddr.should == 0x01020304
s.blocidispid.should == 3456
s.bnodeid.should eql('cnodeid')
s.baddr.should == 0x03040506
s.score.should == 30
s.scorer.should == 0
s.score_dt.should_not be_nil
s.scoring_data.should be_nil
end
it 'c to a' do
s = Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first
s.should_not be_nil
s.alocidispid.should == 3456
s.anodeid.should eql('cnodeid')
s.aaddr.should == 0x03040506
s.blocidispid.should == 1234
s.bnodeid.should eql('anodeid')
s.baddr.should == 0x01020304
s.score.should == 30
s.scorer.should == 1
s.score_dt.should_not be_nil
s.scoring_data.should be_nil
end
it 'delete a to c' do
Score.deletex(1234, 3456)
Score.count.should == 2
Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first.should be_nil
Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first.should be_nil
Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first.should_not be_nil
Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first.should_not be_nil
end
it 'findx' do
Score.findx(1234, 1234).should == -1
Score.findx(1234, 2345).should == 20
Score.findx(1234, 3456).should == 30
Score.findx(2345, 1234).should == 20
Score.findx(2345, 2345).should == -1
Score.findx(2345, 3456).should == -1
Score.findx(3456, 1234).should == 30
Score.findx(3456, 2345).should == -1
Score.findx(3456, 3456).should == -1
end
it "test shortcut for making scores from connections" do
user1 = FactoryGirl.create(:user)
conn1 = FactoryGirl.create(:connection, user: user1, addr: 0x01020304, locidispid: 5)
user2 = FactoryGirl.create(:user)
conn2 = FactoryGirl.create(:connection, user: user2, addr: 0x11121314, locidispid: 6)
user3 = FactoryGirl.create(:user)
conn3 = FactoryGirl.create(:connection, user: user3, addr: 0x21222324, locidispid: 7)
Score.findx(5, 6).should == -1
Score.findx(6, 5).should == -1
Score.findx(5, 7).should == -1
Score.findx(7, 5).should == -1
Score.findx(6, 7).should == -1
Score.findx(7, 6).should == -1
Score.score_conns(conn1, conn2, 12)
Score.score_conns(conn1, conn3, 13)
Score.score_conns(conn2, conn3, 23)
Score.findx(5, 6).should == 12
Score.findx(6, 5).should == 12
Score.findx(5, 7).should == 13
Score.findx(7, 5).should == 13
Score.findx(6, 7).should == 23
Score.findx(7, 6).should == 23
end
describe "createx" do
it "creates with user info" do
score_with_user.touch
score_with_user.auserid.should == user1.id
score_with_user.buserid.should == user2.id
score_with_user.alatencytestid.should be_nil
score_with_user.blatencytestid.should be_nil
end
it "creates with latency-tester info" do
score_with_latency_tester.touch
score_with_latency_tester.auserid.should be_nil
score_with_latency_tester.buserid.should be_nil
score_with_latency_tester.alatencytestid.should == latency_tester1.id
score_with_latency_tester.blatencytestid.should == latency_tester2.id
end
end
end
# current_scores is a view that tries to take the median of up to the last 5 entries
describe "current_scores" do
it "works with empty data set" do
result = Score.connection.execute('SELECT * FROM current_scores')
result.check
result.ntuples.should == 0
end
it "works with one score" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo')
result = Score.connection.execute('SELECT * FROM current_scores')
result.check
result.ntuples.should == 2
result[0]['alocidispid'].to_i.should == 1234
result[0]['scorer'].to_i.should == 0
result[1]['alocidispid'].to_i.should == 2345
result[1]['scorer'].to_i.should == 1
end
it "works with two scores in same location" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # median
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 25, nil, 'foo')
result = Score.connection.execute('SELECT * FROM current_scores')
result.check
result.ntuples.should == 2
result[0]['score'].to_i.should == 20
result[1]['score'].to_i.should == 20
end
it "works with three scores in same location" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 25, nil, 'foo') # median
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo')
result = Score.connection.execute('SELECT * FROM current_scores')
result.check
result.ntuples.should == 2
result[0]['score'].to_i.should == 25
result[1]['score'].to_i.should == 25
end
it "works with six scores in same location" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # we'll make sure this is old, so it won't be in the set
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 25, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 31, nil, 'foo')# median
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 32, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 33, nil, 'foo')
Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{1.days.ago}' WHERE score = 20").cmdtuples.should == 2
result = Score.connection.execute('SELECT * FROM current_scores')
result.check
result.ntuples.should == 2
result[0]['score'].to_i.should == 31
result[1]['score'].to_i.should == 31
# now push back score with 33 to the very back, which will shift the median up to 30
Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{2.days.ago}' WHERE score = 33").check
result = Score.connection.execute('SELECT * FROM current_scores')
result.check
result.ntuples.should == 2
result[0]['score'].to_i.should == 30
result[1]['score'].to_i.should == 30
end
it "works with one score each in different locations" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # median
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo')
result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score')
result.check
result.ntuples.should == 4
result[0]['score'].to_i.should == 20
result[1]['score'].to_i.should == 20
result[2]['score'].to_i.should == 25
result[3]['score'].to_i.should == 25
end
it "works with multiple scores in different locations" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo') # median
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo') # median
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 35, nil, 'foo')
result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score')
result.check
result.ntuples.should == 4
result[0]['score'].to_i.should == 20
result[1]['score'].to_i.should == 20
result[2]['score'].to_i.should == 25
result[3]['score'].to_i.should == 25
end
it "works with multiple scores in different locations" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo') # median
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 35, nil, 'foo') # median
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 40, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 45, nil, 'foo')
result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score')
result.check
result.ntuples.should == 4
result[0]['score'].to_i.should == 30
result[1]['score'].to_i.should == 30
result[2]['score'].to_i.should == 35
result[3]['score'].to_i.should == 35
end
it "works with over 6 scores in different locations" do
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 25, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 30, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 35, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 40, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 45, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 45, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 50, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 55, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 60, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 65, nil, 'foo')
Score.createx(1234, 'anodeid', 0x01020304, 2346, 'bnodeid', 0x02030405, 70, nil, 'foo')
Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{1.days.ago}' WHERE score = 20 OR score = 25").cmdtuples.should == 4
result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score')
result.check
result.ntuples.should == 4
result[0]['score'].to_i.should == 45
result[1]['score'].to_i.should == 45
result[2]['score'].to_i.should == 50
result[3]['score'].to_i.should == 50
Score.connection.execute("UPDATE scores set created_at = TIMESTAMP '#{2.days.ago}' WHERE score = 65 OR score = 70").cmdtuples.should == 4
result = Score.connection.execute('SELECT * FROM current_scores ORDER BY score')
result.check
result.ntuples.should == 4
result[0]['score'].to_i.should == 40
result[1]['score'].to_i.should == 40
result[2]['score'].to_i.should == 45
result[3]['score'].to_i.should == 45
end
end

View File

@ -18,8 +18,8 @@ describe "MusicSessionScheduler" do
MusicSession.all.count.should == 1
# end the session
ms.session_removed_at = Time.now
# move the session back more than 4 hours
ms.scheduled_start = Time.now - 5.hours
ms.save!
# run the scheduler again
@ -27,7 +27,6 @@ describe "MusicSessionScheduler" do
MusicSession.all.count.should == 2
MusicSession.where(:session_removed_at => nil).count.should == 1
MusicSession.where(:next_session_scheduled => true).count.should == 1
end
@ -40,7 +39,7 @@ describe "MusicSessionScheduler" do
@scheduler.run
MusicSession.all.count.should == 1
ms.session_removed_at = Time.now
ms.scheduled_start = Time.now - 3.hours
ms.save!
@scheduler.run

View File

@ -2,7 +2,10 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
#require 'resque/tasks'
#require 'resque/scheduler/tasks'
require 'resque/tasks'
require 'resque/scheduler/tasks'
require File.expand_path('../config/application', __FILE__)
SampleApp::Application.load_tasks

View File

@ -21,6 +21,8 @@
var $templateOpenSlots = null;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
var invitationDialog = null;
var inviteMusiciansUtil = null;
var friendInput=null;
var LATENCY = {
@ -42,7 +44,11 @@
function inviteMusicians(e) {
e.preventDefault();
invitationDialog.showEmailDialog();
friendInput = inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians',
sessionId);
inviteMusiciansUtil.loadFriends();
$(friendInput).show();
// invitationDialog.showEmailDialog();
}
function cancelRsvpRequest(e) {
@ -120,6 +126,7 @@
$sessionPageBtn.on('click', openSessionPage);
$screen.find(".approveRsvpRequest").on('click', approveRsvpRequest);
$screen.find(".declineRsvpRequest").on('click', declineRsvpRequest);
$(friendInput).focus(function() { $(this).val(''); })
$screen.find(".cancelSessionRsvp").on('click', function(e) {
e.preventDefault();
@ -393,6 +400,10 @@
$sessionDetail = $screen.find("#account-session-detail-div");
$shareUrl = $screen.find('.share-url');
invitationDialog = invitationDlg;
inviteMusiciansUtil = new JK.InviteMusiciansUtil(JK.app);
inviteMusiciansUtil.initialize(JK.FriendSelectorDialogInstance);
$templateOpenSlots = $('#template-open-slots');
}

View File

@ -28,11 +28,15 @@
$('.schedule-recurrence', $dialog).html("Recurs " + response.recurring_mode + " on this day at this time");
}
var hasOpenSlots = response.open_slots && response.open_slots.length > 0;
if (response['is_unstructured_rsvp?']) {
$('.rsvp-instruments', $dialog).append('<input type="checkbox" value="unstructured"/>Any Instrument<br/>');
var checkedState = hasOpenSlots ? '' : 'checked="checked"'
$('.rsvp-instruments', $dialog).append('<input type="checkbox" ' + checkedState + ' value="unstructured"/>Play Any Instrument You Like<br/>');
}
if (response.open_slots && response.open_slots.length > 0) {
if (hasOpenSlots) {
$.each(response.open_slots, function(index, val) {
var instrument = val.instrument_id;

View File

@ -12,6 +12,7 @@
var did_show_musician_page = false;
var page_num=1, page_count=0;
var textMessageDialog = null;
var $results = null;
function loadMusicians(queryString) {
// squelch nulls and undefines
@ -88,8 +89,8 @@
// these are raw scores as reported by client (round trip times)
if (score == null) return "purple";
if (0 < score && score <= 40) return "green";
if (40 < score && score <= 80) return "yellow";
if (80 < score && score <= 120) return "red";
if (40 < score && score <= 70) return "yellow";
if (70 < score && score <= 100) return "red";
return "blue";
}
@ -97,11 +98,26 @@
// these are raw scores as reported by client (round trip times)
if (score == null) return "missing";
if (0 < score && score <= 40) return "good";
if (40 < score && score <= 80) return "moderate";
if (80 < score && score <= 120) return "poor";
if (40 < score && score <= 70) return "moderate";
if (70 < score && score <= 100) return "poor";
return "unacceptable";
}
function formatLocation(musician) {
if(musician.city && musician.state) {
return musician.city + ', ' + musician.regionname
}
else if(musician.city) {
return musician.city
}
else if(musician.state) {
return musician.regionname
}
else {
return 'Location Unavailable'
}
}
function renderMusicians() {
var ii, len;
var mTemplate = $('#template-find-musician-row').html();
@ -110,6 +126,7 @@
var mVals, musician, renderings='';
var instr_logos, instr;
var follows, followVals, aFollow;
var myAudioLatency = musicianList.my_audio_latency;
for (ii=0, len=musicians.length; ii < len; ii++) {
musician = musicians[ii];
@ -149,12 +166,12 @@
};
var musician_actions = context.JK.fillTemplate(aTemplate, actionVals);
var joined_score = musician['joined_score']
var full_score = musician['full_score'];
mVals = {
avatar_url: context.JK.resolveAvatarUrl(musician.photo_url),
profile_url: "/client#/profile/" + musician.id,
musician_name: musician.name,
musician_location: musician.city + ', ' + musician.state,
musician_location: formatLocation(musician),
instruments: instr_logos,
biography: musician['biography'],
follow_count: musician['follow_count'],
@ -164,14 +181,27 @@
musician_id: musician['id'],
musician_follow_template: follows,
musician_action_template: musician_actions,
musician_one_way_score: score_to_text(joined_score),
musician_score_color: score_to_color(joined_score),
musician_score_color_alt: score_to_color_alt(joined_score)
musician_one_way_score: score_to_text(full_score),
musician_score_color: score_to_color(full_score),
musician_score_color_alt: score_to_color_alt(full_score)
};
var musician_row = context.JK.fillTemplate(mTemplate, mVals);
renderings += musician_row;
var $rendering = $(context.JK.fillTemplate(mTemplate, mVals))
var $offsetParent = $results.closest('.content');
var options = {positions: ['top', 'bottom', 'right', 'left'], offsetParent: $offsetParent};
var scoreOptions = {positions: ['right', 'top', 'bottom', 'left'], offsetParent: $offsetParent, width:'600px'};
context.JK.helpBubble($('.follower-count', $rendering), 'musician-follower-count', {}, options);
context.JK.helpBubble($('.friend-count', $rendering), 'musician-friend-count', {}, options);
context.JK.helpBubble($('.recording-count', $rendering), 'musician-recording-count', {}, options);
context.JK.helpBubble($('.session-count', $rendering), 'musician-session-count', {}, options);
context.JK.helpBubble($('.score-count', $rendering), 'musician-score-count',
{full_score: full_score ? Math.round(full_score / 2) : null, my_gear_latency: myAudioLatency, their_gear_latency:musician['audio_latency'], internet_latency: musician['score']},
scoreOptions)
$results.append($rendering);
}
$('#musician-filter-results').append(renderings);
$('.search-m-friend').on('click', friendMusician);
$('.search-m-follow').on('click', followMusician);
@ -279,6 +309,8 @@
};
app.bindScreen('musicians', screenBindings);
$results = $('#musician-filter-results');
events();
}

View File

@ -59,6 +59,8 @@
addInvitation(dd.name, dd.id);
});
}).fail(app.ajaxError);
return friendInput;
}
this.clearSelections = function() {
@ -133,7 +135,7 @@
} else {
$(friendInput).select();
context.alert('Invitation already exists for this musician.');
// context.alert('Invitation already exists for this musician.');
}
}

View File

@ -309,10 +309,15 @@
}
var $action_btn = $notification.find($btnNotificationAction);
$action_btn.text(actionText);
$action_btn.click(function() {
callback(payload);
});
if (actionText === '') {
$action_btn.hide();
}
else {
$action_btn.text(actionText);
$action_btn.click(function() {
callback(payload);
});
}
}
else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) {

View File

@ -117,14 +117,14 @@
var firstSession = function() {
var $firstSession = $scheduledSessions.children().first().find('input[name="scheduled-session-info"]');
$firstSession.attr('checked', 'checked');
createSessionSettings.selectedSessionId = $firstSession.attr('id');
createSessionSettings.selectedSessionId = $firstSession.attr('data-session-id');
};
if (createSessionSettings.selectedSessionId == null) {
firstSession();
}
else {
var $selectedSession = $scheduledSessions.children().first().find('input[name="scheduled-session-info"][id="' + createSessionSettings.selectedSessionId + '"]');
var $selectedSession = $scheduledSessions.children().first().find('input[name="scheduled-session-info"][data-session-id="' + createSessionSettings.selectedSessionId + '"]');
if ($selectedSession.length)
$selectedSession.attr('checked', 'checked');
else
@ -349,6 +349,7 @@
function beforeMoveStep1() {
if (createSessionSettings.createType == 'start-scheduled') {
createSessionSettings.selectedSessionId = $scheduledSessions.find('.iradio_minimal.checked input[name="scheduled-session-info"]').attr('data-session-id');
var session = scheduledSessions[createSessionSettings.selectedSessionId];
if(session == null) {
@ -382,7 +383,7 @@
var startTime = new Date(session.scheduled_start);
var diffTime = startTime.getTime() - currentTime.getTime();
if (diffTime > ONE_HOUR) {
var confirmDialog = new context.JK.ConfirmDialog(app, "Start Session Now",
var confirmDialog = new context.JK.ConfirmDialog(app, "START SESSION NOW",
"You are starting a session that is scheduled to begin more than one hour from now. Are you sure you want to do this?",
"Future Session", moveToFinish);
confirmDialog.initialize();
@ -416,7 +417,7 @@
createSessionSettings.startTime = $startTimeList.val();
createSessionSettings.endTime = $endTimeList.val();
createSessionSettings.notations = [];
createSessionSettings.selectedSessionId = $scheduledSessions.find('input[name="scheduled-session-info"][checked="checked"]').attr('id');
createSessionSettings.selectedSessionId = $scheduledSessions.find('.iradio_minimal.checked input[name="scheduled-session-info"]').attr('data-session-id');
createSessionSettings.timezone.value = $timezoneList.val();
createSessionSettings.timezone.label = $timezoneList.get(0).options[$timezoneList.get(0).selectedIndex].text;
createSessionSettings.recurring_mode.label = $recurringModeList.get(0).options[$recurringModeList.get(0).selectedIndex].text;

View File

@ -32,6 +32,7 @@
var playbackControls = null;
var promptLeave = false;
var rateSessionDialog = null;
var friendInput=null;
var rest = context.JK.Rest();
@ -133,6 +134,7 @@
.done(function(){
initializeSession();
})
}
function notifyWithUserInfo(title , text, clientId) {
@ -1354,7 +1356,10 @@
}
function inviteMusicians() {
inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians', sessionId);
friendInput = inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians',
sessionId);
inviteMusiciansUtil.loadFriends();
$(friendInput).show();
}
function events() {
@ -1365,6 +1370,7 @@
$('#recording-start-stop').on('click', startStopRecording);
$('#open-a-recording').on('click', openRecording);
$('#session-invite-musicians').on('click', inviteMusicians);
$('#session-invite-musicians2').on('click', inviteMusicians);
$('#track-settings').click(function() {
configureTrackDialog.refresh();
configureTrackDialog.showVoiceChatPanel(true);
@ -1376,6 +1382,7 @@
.on('pause', onPause)
.on('play', onPlay)
.on('change-position', onChangePlayPosition);
$(friendInput).focus(function() { $(this).val(''); })
}
this.initialize = function(localRecordingsDialogInstance, recordingFinishedDialogInstance, friendSelectorDialog) {

View File

@ -21,9 +21,9 @@
var LATENCY = {
GOOD : {description: "GOOD", style: "latency-green", min: 0.0, max: 20.0},
MEDIUM : {description: "MEDIUM", style: "latency-yellow", min: 20.0, max: 40.0},
POOR : {description: "POOR", style: "latency-red", min: 40.0, max: 10000000000.0},
UNREACHABLE: {description: "UNREACHABLE", style: "latency-grey", min: -1, max: -1},
MEDIUM : {description: "MEDIUM", style: "latency-yellow", min: 20.0, max: 35.0},
POOR : {description: "POOR", style: "latency-red", min: 35.0, max: 50},
UNACCEPTABLE: {description: "UNACCEPTABLE", style: "latency-grey", min: 50, max: 10000000},
UNKNOWN: {description: "UNKNOWN", style: "latency-grey", min: -2, max: -2}
};
@ -350,7 +350,10 @@
}
function createLatency(user) {
var latencyStyle = LATENCY.UNREACHABLE.style, latencyDescription = LATENCY.UNREACHABLE.description
var latencyStyle;
var latencyDescription;
if (user.id === context.JK.currentUserId) {
latencyStyle = LATENCY.GOOD.style, latencyDescription = LATENCY.GOOD.description;
}
@ -358,24 +361,25 @@
else {
var latency = user.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
if (!latency) {
latencyDescription = LATENCY.UNKNOWN.description;
latencyStyle = LATENCY.UNKNOWN.style;
}
else if (latency <= LATENCY.GOOD.max) {
latencyDescription = LATENCY.GOOD.description;
latencyStyle = LATENCY.GOOD.style;
}
else if (latency > LATENCY.MEDIUM.min && latency <= LATENCY.MEDIUM.max) {
latencyDescription = LATENCY.MEDIUM.description;
latencyStyle = LATENCY.MEDIUM.style;
}
else if (latency > LATENCY.POOR.min && latency <= LATENCY.UNACCEPTABLE.max) {
latencyDescription = LATENCY.POOR.description;
latencyStyle = LATENCY.POOR.style;
}
else {
if (latency <= LATENCY.GOOD.max) {
latencyDescription = LATENCY.GOOD.description;
latencyStyle = LATENCY.GOOD.style;
}
else if (latency > LATENCY.MEDIUM.min && latency <= LATENCY.MEDIUM.max) {
latencyDescription = LATENCY.MEDIUM.description;
latencyStyle = LATENCY.MEDIUM.style;
}
else {
latencyDescription = LATENCY.POOR.description;
latencyStyle = LATENCY.POOR.style;
}
latencyStyle = LATENCY.UNREACHABLE.style
latencyDescription = LATENCY.UNREACHABLE.description
}
}

View File

@ -106,12 +106,14 @@
if (!data) {
data = {}
}
if(!options) {
options = {}
}
var helpText = context._.template($('#template-help-' + templateName).html(), data, { variable: 'data' });
var holder = $('<div class="hover-bubble help-bubble"></div>');
holder.append(helpText);
context.JK.hoverBubble($element, helpText, options);
}
@ -183,7 +185,7 @@
cornerRadius: 0,
cssStyles: {
fontSize: '11px',
color: 'white',
color: '#cccccc',
whiteSpace: 'normal'
}
};

View File

@ -174,7 +174,10 @@
$('.call-to-action').html('Tell the session organizer if you can no longer join this session');
$btnAction.html('CANCEL RSVP');
$btnAction.click(function(e) {
ui.launchRsvpCancelDialog(musicSessionId, rsvp.id);
ui.launchRsvpCancelDialog(musicSessionId, rsvp.id)
.one(EVENTS.RSVP_CANCELED, function() {
location.reload();
});
});
}
}
@ -184,7 +187,10 @@
$('.call-to-action').html("Tell the session organizer you'd like to play in this session");
$btnAction.html('RSVP NOW!');
$btnAction.click(function(e) {
ui.launchRsvpSubmitDialog(musicSessionId);
ui.launchRsvpSubmitDialog(musicSessionId)
.one(EVENTS.RSVP_SUBMITTED, function() {
location.reload();
})
});
}
})
@ -201,14 +207,6 @@
});
addLatencyDetails();
$(document).on(EVENTS.RSVP_SUBMITTED, function() {
location.reload();
});
$(document).on(EVENTS.RSVP_CANCELED, function() {
location.reload();
});
}
this.initialize = initialize;

View File

@ -53,6 +53,7 @@
*= require ./searchResults
*= require ./clientUpdate
*= require ./musician
*= require ./help
*= require ./jquery-ui-overrides
*= require web/audioWidgets
*= require web/recordings

View File

@ -0,0 +1,43 @@
.screen {
.bt-wrapper {
.bt-content {
color:#cccccc;
font-size:14px;
p {
font-size:14px;
}
ul {
font-size:14px;
}
li {
margin-left:1em;
margin-bottom:.5em;
}
.definition {
font-weight:bold;
}
.help-musician-score-count {
.measurement {
}
.measurement-value {
font-size:24px;
}
.measurement-absent {
font-style:italic;
font-size:12px;
display:block;
}
}
}
}
}

View File

@ -24,6 +24,18 @@
}
}
#musicians-screen {
.filter-element.desc.instrument-selector {
margin-left:2px;
float:left;
& + .dropdown-wrapper .dropdown-container {
width:150px;
}
}
}
#musician-filter-results {
margin: 0 10px 0px 10px;

View File

@ -599,9 +599,6 @@ strong {
}
}
body.jam.web.welcome .no-websocket-connection {
display:none;
}
body.jam.web.register .no-websocket-connection {
body.jam.web .no-websocket-connection {
display:none;
}

View File

@ -228,9 +228,6 @@ class UsersController < ApplicationController
if !@user.nil? && !@user.errors.any?
UserMailer.welcome_message(@user).deliver
sign_in @user
redirect_to :client
return
elsif !@user.nil?
# new user with validation errors;
logger.debug("#{@user} has errors. can not sign in until remedied. #{@user.errors.inspect}")
@ -463,6 +460,9 @@ JS
end
def load_location(remote_ip, location = nil)
# useful if you need to repro something on 127.0.0.1
# remote_ip = ' 23.119.29.89'
@location = location
if @location.nil?
@ -471,7 +471,6 @@ JS
@location[:country] = "US" if @location[:country].nil?
# right now we only accept US signups for beta
@countriesx = MaxMindManager.countries
# populate regions based on current country
@regions = MaxMindManager.regions(@location[:country])

View File

@ -60,7 +60,7 @@ glue :music_session do
attributes :id, :name, :location, :photo_url
}
child(:active_music_session => :music_session) do
child(:active_music_session => :active_music_session) do
# only show mount info if fan_access is public. Eventually we'll also need to show this in other scenarios, like if invited
child({:mount => :mount}, :if => lambda { |music_session| music_session.fan_access}) {
attributes :id, :name, :sourced, :listeners, :bitrate, :subtype, :url

View File

@ -41,9 +41,13 @@ if @search.musicians_filter_search?
node :page_count do |foo|
@search.page_count
end
node :my_audio_latency do |user|
current_user.last_jam_audio_latency
end
child(:results => :musicians) {
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online, :musician, :photo_url, :biography, :joined_score
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :audio_latency
node :is_friend do |musician|
@search.is_friend?(musician)

View File

@ -16,7 +16,7 @@
.right
%a.cancel-rsvp.button-orange{href: "#"} CANCEL RSVP
%a.session-detail-page.button-orange{href: "#", rel:'external'} SESSION PAGE
%a.invite-others.button-orange{href: "#"} INVITE OTHERS
%a.invite-others.button-orange{'layout-link' => 'select-invites','href' => "#"} INVITE OTHERS
.clearall
#account-session-detail-div
@ -160,4 +160,4 @@
%script{type: 'text/template', id: 'template-account-session-latency'}
.latency{class: "{{data.latency_style}}"}
{{data.latency_text}}
{{data.latency_text}}

View File

@ -80,7 +80,6 @@
Fan Access:
.right-column
%select#session-prop-fans-access
%option{value: "listen-chat-band"} Fans may listen, chat with the band
%option{value: "listen-chat-each", selected: "selected"} Fans may listen, chat with each other
%option{value: "no-listen-chat"} Fans may not listen to session
.clearall.right-no-left
@ -95,7 +94,6 @@
%option{value: "Standard"} Standard
%option{value: "Creative Commons"} Creative Commons
%option{value: "Offline"} Offline
%option{value: "Jamtracks"} Jamtracks
.clearall.left-column
Notation Files:
.right-column

View File

@ -40,4 +40,42 @@
<script type="text/template" id="template-help-minimum-output-channels">
To be a valid output audio device, it must have at least 2 output ports.
</script>
<script type="text/template" id="template-help-musician-follower-count">
The number of followers that this user has.
</script>
<script type="text/template" id="template-help-musician-friend-count">
The number of friends that this user has.
</script>
<script type="text/template" id="template-help-musician-recording-count">
The number of recordings that this user has made.
</script>
<script type="text/template" id="template-help-musician-session-count">
The number of sessions that this user has played in.
</script>
<script type="text/template" id="template-help-musician-score-count">
<div class="help-musician-score-count">
<p>The score shown is the one-way latency (or delay) in milliseconds from you to this user. This score is calculated using the following three values that JamKazam gathers:</p>
<ul>
<li><span class="definition">Your Audio Gear Latency:</span> <span class="measurement my-gear-latency"><span class="measurement-value">{{data.my_gear_latency ? data.my_gear_latency + ' ms': '13 ms*'}}</span> <span class="measurement-absent">{{data.my_gear_latency ? '' : "(you have not qualified any gear, so we picked an average gear latency)"}}</span></span></li>
<li><span class="definition">Their Audio Gear Latency:</span> <span class="measurement their-gear-latency"><span class="measurement-value">{{data.their_gear_latency ? data.their_gear_latency + ' ms': '13 ms*'}}</span> <span class="measurement-absent">{{data.their_gear_latency ? '' : "(they have not qualified any gear, so we picked an average gear latency)"}}</span></span></li>
<li><span class="definition">Round-trip Internet Latency:</span> <span class="measurement internet-latency"><span class="measurement-value">{{data.internet_latency ? data.internet_latency + ' ms': '?'}}</span> <span class="measurement-absent">{{data.internet_latency ? '' : "(we have not scored you with this user yet)"}}</span></span></li>
</ul>
<p> <span class="definition">Total One-Way Latency:</span> <span class="measurement my-gear-latency"><span class="measurement-value">( {{data.my_gear_latency ? data.my_gear_latency: '13'}} + {{data.their_gear_latency ? data.their_gear_latency: '13'}} + {{data.internet_latency ? data.internet_latency: '?'}} ) / 2 = {{data.full_score ? data.full_score + ' ms' : "?"}}</span> <span class="measurement-absent">{{data.full_score ? '' : "(when we don't know internet latency, we don't try to guess your one-way latency)"}}</span></span>
<p>We categorize this score as good, fair, poor, unacceptable, or unknown. Those categories are defined as follows:
<ul>
<li><span class="definition">Good:</span> 20ms or less <img src="/assets/content/icon_green_score.png" /></li>
<li><span class="definition">Fair:</span> 20ms to 35ms <img src="/assets/content/icon_yellow_score.png" /></li>
<li><span class="definition">Poor:</span> 35ms to 50ms <img src="/assets/content/icon_red_score.png" /></li>
<li><span class="definition">Unacceptable:</span> Above 50ms <img src="/assets/content/icon_blue_score.png" /></li>
<li><span class="definition">Unknown:</span> No internet score is available between you and them. <img src="/assets/content/icon_purple_score.png" /></li>
</ul>
</div>
</script>

View File

@ -1,5 +1,5 @@
<!-- Musician Screen -->
<%= content_tag(:div, :layout => 'screen', 'layout-id' => 'musicians', :class => "screen secondary") do -%>
<%= content_tag(:div, :layout => 'screen', 'layout-id' => 'musicians', :class => "screen secondary", id: 'musicians-screen') do -%>
<%= content_tag(:div, :class => :content) do -%>
<%= content_tag(:div, :class => 'content-head') do -%>
<%= content_tag(:div, image_tag("content/icon_musicians.png", {:height => 19, :width => 19}), :class => 'content-icon') %>
@ -56,13 +56,13 @@
</div>
<div class="clearleft"></div>
</div>
<div class="button-row" data-hint="button-row">
<div class="button-row " data-hint="button-row">
<div class="lcol stats left">
{friend_count} <img src="../assets/content/icon_friend.png" alt="friends" width="14" height="12" align="absmiddle" style="margin-right:4px;"/>
{follow_count} <img src="../assets/content/icon_followers.png" alt="follows" width="22" height="12" align="absmiddle" style="margin-right:4px;"/>
{recording_count} <img src="../assets/content/icon_recordings.png" alt="recordings" width="12" height="13" align="absmiddle" style="margin-right:4px;"/>
{session_count} <img src="../assets/content/icon_session_tiny.png" alt="sessions" width="12" height="12" align="absmiddle" style="margin-right:4px;"/>
{musician_one_way_score} <img src="../assets/content/icon_{musician_score_color}_score.png" alt="{musician_score_color_alt} score" width="12" height="12" align="absmiddle" style="margin-right:4px;"/>
<span class="friend-count">{friend_count} <img src="../assets/content/icon_friend.png" alt="friends" width="14" height="12" align="absmiddle" style="margin-right:4px;"/></span>
<span class="follower-count">{follow_count} <img src="../assets/content/icon_followers.png" alt="follows" width="22" height="12" align="absmiddle" style="margin-right:4px;"/></span>
<span class="recording-count">{recording_count} <img src="../assets/content/icon_recordings.png" alt="recordings" width="12" height="13" align="absmiddle" style="margin-right:4px;"/></span>
<span class="session-count">{session_count} <img src="../assets/content/icon_session_tiny.png" alt="sessions" width="12" height="12" align="absmiddle" style="margin-right:4px;"/></span>
<span class="score-count">{musician_one_way_score} <img src="../assets/content/icon_{musician_score_color}_score.png" alt="{musician_score_color_alt} score" width="12" height="12" align="absmiddle" style="margin-right:4px;"/></span>
</div>
<div class="result-list-button-wrapper" data-musician-id={musician_id}>
{musician_action_template}

View File

@ -456,7 +456,7 @@
<!-- Scheduled session template -->
<script type="text/template" id="template-scheduled-session">
<li>
<input type="radio" name="scheduled-session-info" id="{{data.id}}" value="false" />
<input type="radio" name="scheduled-session-info" data-session-id="{{data.id}}" value="false" />
<label for="{{data.id}}" class="radio-text w85">
{{data.scheduled_start}} : {{data.name}}
</label>

View File

@ -78,7 +78,7 @@
<div id="session-livetracks-container">
<p class="when-empty">
No Live Tracks:<br/>
<a>Invite Other Musicians</a> to<br/>
<a layout-link="select-invites", href="#" id="session-invite-musicians2">Invite Other Musicians</a> to<br/>
Add Live Tracks
</p>
</div>

View File

@ -16,15 +16,15 @@
<!-- @end sort filter -->
<% else %>
<!-- @begin order by filter -->
<%= content_tag(:div, 'Filter By:', :class => 'filter-element desc') %>
<%= select_tag("#{filter_label}_order_by", options_for_select(Search::ORDERINGS), {:class => "#{filter_label}-order-by easydropdown"} ) %>
<%= content_tag(:div, 'Order By:', :class => 'filter-element desc') %>
<%= select_tag("#{filter_label}_order_by", options_for_select(Search::M_ORDERINGS), {:class => "#{filter_label}-order-by easydropdown"} ) %>
<!-- @end order by filter -->
<% end %>
<% end -%>
<%= content_tag(:div, :class => 'filter-element wrapper') do -%>
<% if :musician == filter_label %>
<!-- @begin instrument filter -->
<%= content_tag(:div, 'Instrument:', :class => 'filter-element desc') %>
<%= content_tag(:div, 'Instrument:', :class => 'filter-element desc instrument-selector') %>
<%= select_tag("#{filter_label}_instrument",
options_for_select([['Any', '']].concat(JamRuby::Instrument.all.collect { |ii| [ii.description, ii.id] })), {:class=> "easydropdown"}) %>
<!-- @end instrument filter -->

View File

@ -12,7 +12,7 @@
.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):
.comment-instructions Enter a message to the other musicians in the session (optional):
%textarea.txtComment{rows: '2', placeholder: 'Enter a comment...'}
.buttons
.left

View File

@ -96,7 +96,10 @@
%br/
.left.w65.ib.still-needed
%strong Still Needed
- if @open_slots.blank?
- if @music_session.is_unstructured_rsvp
.clearall.left.w100.h20.ib.mb10
Open to any instrument
- elsif @open_slots.blank?
.clearall.left.w100.h20.ib.mb10
All slots are taken
- else
@ -108,8 +111,6 @@
= slot.instrument_id.capitalize
= "(#{slot.proficiency_desc})"
%br{:clear => "all"}/
%br/
.w65.ib.invited
%strong Invited

View File

@ -55,7 +55,7 @@
<option value="" <%= @location[:state].blank? ? "selected" : "" %>>State/Province</option>
<% @regions.each do |region| %>
<% unless region.blank? %>
<option value="<%= region[:region] %>" <%= @location[:state] == region ? "selected" : "" %>><%= region[:name] %></option>
<option value="<%= region[:region] %>" <%= @location[:state] == region[:region] ? "selected" : "" %>><%= region[:name] %></option>
<% end %>
<% end %>

View File

@ -1,8 +1,7 @@
<% provide(:title, 'Signup Confirmation') %>
<% if @user.nil? %>
<h1>Signup Already Confirmed</h1>
<h1>Signup Already Confirmed</h1>
<br/>
<br/>
<p>
@ -13,5 +12,14 @@
Please proceed to <%= link_to 'the home page', root_path %> and log in.
</p>
<% else %>
The server had a problem. Please try to confirm your email later.
<h1>Email Confirmed</h1>
<br/>
<br/>
<p>
You have successfully confirmed this email address.
</p>
<br/>
<p>
You can proceed to the website <%= link_to 'home page', root_path %> and sign in, or close this page in your browser.
</p>
<% end %>

View File

@ -1,7 +1,6 @@
#!/bin/bash
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# 'target' is the output directory

View File

@ -1,8 +1,8 @@
# Resque tasks
require 'resque/tasks'
require 'resque_scheduler/tasks'
require 'resque/scheduler/tasks'
require 'resque'
require 'resque_scheduler'
require 'resque-scheduler'
task :scheduler => :environment do

View File

@ -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, network_score, nil)
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, network_score, nil, nil, {auserid: user.id, buserid: other.id})
get :ams_index, {client_id: conn.client_id}
json = JSON.parse(response.body, :symbolize_names => true)
@ -75,7 +75,7 @@ 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, network_score, nil)
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, network_score, nil, nil, {auserid: user.id, buserid: other.id})
# 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'))
@ -139,14 +139,14 @@ describe ApiMusicSessionsController do
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)
Score.createx(conn.locidispid, conn.client_id, conn.addr, other_conn.locidispid, other_conn.client_id, other_conn.addr, network_score, nil, nil, {auserid: user.id, buserid: other.id})
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)
Score.createx(invitee.last_jam_locidispid, 'immaterial', 1, conn.locidispid, conn.client_id, conn.addr, network_score, nil, nil, {auserid: invitee.id, buserid: user.id})
get :sms_index, {client_id: conn.client_id}
json = JSON.parse(response.body, :symbolize_names => true)

View File

@ -1,6 +1,6 @@
require 'spec_helper'
describe "Create Session Flow", :js => true, :type => :feature, :capybara_feature => true do
describe "Create Session UI", :js => true, :type => :feature, :capybara_feature => true do
let(:user1) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
@ -13,8 +13,32 @@ describe "Create Session Flow", :js => true, :type => :feature, :capybara_featur
page.driver.resize(1500, 800) # makes sure all the elements are visible
emulate_client
sign_in_poltergeist user1
wait_until_curtain_gone
visit "/client#/createSession"
page.find('.createsession').trigger(:click)
end
end
# VRFS-1976
it "create two sessions; select the second one" do
# this is tricky because of iCheck and it's custom way of dealing with 'checked' state
start1 = 15.minutes.ago
session1 = FactoryGirl.create(:music_session, description: 'My Session 1', creator: user1, scheduled_start: start1)
session2 = FactoryGirl.create(:music_session, description: 'My Session 2', creator: user1)
in_client(user1) do
# should reload the session page, so that we see new sessions
visit '/client#/createSession'
# pick the second session
page.find("#scheduled-session-list input[data-session-id='#{session2.id}'] + ins").trigger(:click)
page.find('.btn-next').trigger(:click)
# check if future dialog is showing; if so, accept
accept = first('#btn-confirm-ok', text: "START SESSION NOW")
accept.trigger(:click) if accept
page.find('#session-description-disp', text: 'My Session 2')
end
end
@ -211,48 +235,4 @@ describe "Create Session Flow", :js => true, :type => :feature, :capybara_featur
end
end
end
context "create session flow backend" do
describe "schedule a session" do
it "schedule a session" do
schedule_session({creator: user1})
end
it "start a session after scheduling" do
MusicSession.delete_all
schedule_session({creator: user1})
in_client(user1) do
visit "/client#/createSession"
find('li[create-type="start-scheduled"] ins').trigger(:click)
find('.btn-next').trigger(:click)
find('.btn-next').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
end
it "start quick session" do
page.driver.resize(1500, 800) # makes sure all the elements are visible
emulate_client
sign_in_poltergeist user1
wait_until_curtain_gone
visit "/client#/createSession"
expect(page).to have_selector('h1', text: 'create session')
find('li[create-type="quick-start"] ins').trigger(:click)
find('div[info-id="quick-start"]')
find('.btn-next').trigger(:click)
find('.btn-next', text: 'START SESSION').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
end

View File

@ -0,0 +1,232 @@
require 'spec_helper'
describe "Create Session", :js => true, :type => :feature, :capybara_feature => true do
let(:user1) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
context "functionally test all ways to Create Session" do
context "I have already scheduled a session..." do
let (:now) { Time.now - 5.hours }
let (:first_session) { FactoryGirl.create(:music_session, creator: user1, name: "First one", scheduled_start: now + 5.minutes) }
let (:second_session) { FactoryGirl.create(:music_session, creator: user1, name: "Second one", scheduled_start: now + 2.hours) }
let (:third_session) { FactoryGirl.create(:music_session, creator: user1, name: "Third one", scheduled_start: now + 17.days) }
let (:not_my_session) { FactoryGirl.create(:music_session, creator: user2, name: "Who cares", scheduled_start: now + 30.minutes) }
before do
#instantiate these test sessions in non-sequential order
third_session.touch; first_session.touch; not_my_session.touch; second_session.touch;
#[first_session, second_session, third_session, not_my_session].each { |s| puts "#{s.name}: #{s.id}" }
emulate_client
page.driver.resize(1500, 800) #purely aesthetic
sign_in_poltergeist user1
find('.createsession').trigger(:click)
wait_for_ajax
expect(page).to have_selector 'li[create-type="start-scheduled"] ins'
end
it "sessions are shown in schedule order on the Create Session screen" do
sleep 2 #arg
radio_buttons = page.all('ul#scheduled-session-list li')
first, second, third = *radio_buttons[0..2]
expect(first.text).to include first_session.name
expect(second.text).to include second_session.name
expect(third.text).to include third_session.name
expect(first).to have_selector 'input[checked=checked]'
expect(page).to_not have_text not_my_session.name
end
it "future sessions can be edited from the Create Session screen" do
#pending "possible bug, does not occur when testing manually"
page.find('a#edit_scheduled_sessions').trigger(:click)
#expect(page).to have_selector "div[data-id='#{first_session.id}']" #see pending note
expect(page).to have_selector "div[data-id='#{second_session.id}']"
expect(page).to have_selector "div[data-id='#{third_session.id}']"
expect(page).to_not have_selector "div[data-id='#{not_my_session.id}']"
end
context "...start it now" do
it "starts the first one" do
sleep 1
find('.btn-next').trigger(:click)
sleep 1
expect(page).to have_selector('.session-step-title', text: 'Review & Confirm')
expect(page).to have_content first_session.name
find('.btn-next').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
context "attempt to start a session more than an hour from now" do
let (:first_session) { FactoryGirl.create(:music_session, creator: user1, scheduled_start: now + 75.minutes) }
it "warns the user that session starts in the future, and user can start session" do
pending "play with Database.db_timezone on this one"
sleep 1
find('.btn-next').trigger(:click)
sleep 2
expect(page).to have_selector('h1', text: 'Future Session')
expect(page).to have_content "Are you sure"
find('#btn-confirm-ok', text: 'START SESSION NOW').trigger(:click)
sleep 1
expect(page).to have_selector('.session-step-title', text: 'Review & Confirm')
expect(page).to have_content first_session.name
find('.btn-next').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
end
end
shared_examples_for :a_future_session do
specify "creator can see the session on Create Session page" do
in_client(creator) do
page.find('.createsession').trigger(:click)
expect(page).to have_selector('h1', text: 'create session')
sessions = page.first('ul#scheduled-session-list li')
expect(sessions.text).to include session_name
end
end
specify "creator can see the session on Find Session page" do
in_client(creator) do
visit "/client#/findSession"
wait_until_curtain_gone
expect(page).to have_selector('#session-name-disp', text: "#{session_name} (#{session_genre})")
#expect(page).to have_selector('#session-name-disp', text: @session_genre)
end
end
specify "another user can see the session on Find Session page" do
in_client(someone_else) do
emulate_client
page.driver.resize(1500, 800)
sign_in_poltergeist someone_else
visit "/client#/findSession"
wait_until_curtain_gone
expect(find('table#sessions-scheduled')).to have_content session_name
expect(find('table#sessions-scheduled')).to have_content session_genre
end
end
specify "another user can RSVP to the session" do
in_client(someone_else) do
emulate_client
page.driver.resize(1500, 800)
sign_in_poltergeist someone_else
visit "/client#/findSession"
wait_until_curtain_gone
within('table#sessions-scheduled') do
find('a.rsvp-link').trigger(:click)
end
within('div.dialog-inner') do
find('div.session-name').should have_content session_name
find('div.slot-instructions').should have_content "Check the box(es) next to the track(s) you want to play"
# fill_in '.txtComment', with: "Looking forward to the session"
#first('div.rsvp-instruments input').trigger(:click)
sleep 1
find('#btnSubmitRsvp').trigger(:click)
sleep 2
end
end
end
specify "creator can start the session" do
in_client(creator) do
page.find('.createsession').trigger(:click)
expect(page).to have_selector('h1', text: 'create session')
expect(page).to have_content session_name
find('li[create-type="start-scheduled"] ins').trigger(:click)
find('.btn-next').trigger(:click)
find('.btn-next').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
end
context "I want to schedule a session for a specific future time" do
before do
MusicSession.delete_all
@creator, @session_name, @session_genre = schedule_session(creator: user1)
end
it_should_behave_like :a_future_session do
let(:creator) { @creator }
let(:session_name) { @session_name }
let(:session_genre) { @session_genre }
let(:someone_else) { FactoryGirl.create(:user) }
end
end
context "I want to choose the time after others RSVP to my session" do
before do
MusicSession.delete_all
@creator, @session_name, @session_genre = schedule_session(creator: user1, rsvp: true)
end
it_should_behave_like :a_future_session do
let(:creator) { @creator }
let(:session_name) { @session_name }
let(:session_genre) { @session_genre }
let(:someone_else) { FactoryGirl.create(:user) }
end
end
context "I want to start a new session right now for others to join" do
before do
MusicSession.delete_all
@creator, @session_name, @session_genre = schedule_session(creator: user1, immediate: true)
end
specify "creator is in the session" do
in_client @creator do
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
specify "another user can see this active session on Find Session page" do
in_client(user2) do
emulate_client
page.driver.resize(1500, 800)
sign_in_poltergeist user2
visit "/client#/findSession"
wait_until_curtain_gone
expect(find('table#sessions-active')).to have_content @session_name
expect(find('table#sessions-active')).to have_content @session_genre
end
end
end
context "I want to quick start a test session just for me" do
before do
MusicSession.delete_all
@creator, @session_name, @session_genre = schedule_session(creator: user1, quickstart: true)
end
specify "creator is in the session" do
in_client @creator do
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
specify "another user does NOT see the session on Find Session page" do
in_client(user2) do
emulate_client
page.driver.resize(1500, 800)
sign_in_poltergeist user2
visit "/client#/findSession"
wait_until_curtain_gone
expect(find('table#sessions-scheduled')).to_not have_content @session_name
end
end
end
end
end

View File

@ -10,6 +10,22 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
end
describe "regressions" do
describe "mount" do
it "should render when has mount" do
# regression for VRFS-1987
ams = FactoryGirl.create(:active_music_session)
FactoryGirl.create(:icecast_mount, music_session_id: ams.id)
fast_signin user, "/client#/feed"
find('#feedScreen')
find(".feed-entry.music-session-history-entry[data-music-session='#{ams.id}']")
end
end
end
describe "sessions" do
before(:each) do
@ -40,6 +56,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
# it " and render artist hover bubble"
# end
it "should render stats" do
visit "/client#/feed"

View File

@ -207,6 +207,10 @@ describe "Session Info", :js => true, :type => :feature, :capybara_feature => tr
# musician_access = true, approval_required = false
it "should allow anyone to view for 'at will' option after session starts" do
@music_session.musician_access = true
@music_session.approval_required = false
@music_session.save!
# attempt to access with musician who was invited but didn't RSVP
fast_signin(@session_invitee, @url)
ensure_success({:show_cta => true, :button_text => 'RSVP NOW!'})
@ -235,6 +239,10 @@ describe "Session Info", :js => true, :type => :feature, :capybara_feature => tr
# musician_access = true, approval_required = true
it "should allow anyone to view for 'join by approval' option after session starts" do
@music_session.musician_access = true
@music_session.approval_required = true
@music_session.save!
# attempt to access with musician who was invited but didn't RSVP
fast_signin(@session_invitee, @url)
ensure_success({:show_cta => true, :button_text => 'RSVP NOW!'})

View File

@ -7,12 +7,21 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do
before(:each) do
@mac_client = FactoryGirl.create(:artifact_update)
UserMailer.deliveries.clear
MaxMindManager.create_phony_database
end
describe "signup page" do
before { visit signup_path }
it { should have_selector('h2', text: 'Create a JamKazam account') }
it "should initialize successfully" do
should have_selector('h2', text: 'Create a JamKazam account')
# we should see these locations in the signup form already chosen
location = GeoIpLocations.lookup('127.0.0.1')
find('.field.country .easydropdown .selected', text:location[:country])
find('.field.state .easydropdown .selected', text:location[:state])
find('.field.city .easydropdown .selected', text:location[:city])
end
describe "with valid musician information" do
before do
@ -30,7 +39,12 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do
it {
should have_title("JamKazam | Congratulations")
should have_content("You have successfully registered as a JamKazam musician.")
User.find_by_email('newuser1@jamkazam.com').musician_instruments.length.should == 1
user = User.find_by_email('newuser1@jamkazam.com')
user.musician_instruments.length.should == 1
location = GeoIpLocations.lookup('127.0.0.1')
user.country.should == location[:country]
user.state.should == location[:state]
user.city.should == location[:city]
# an email is sent on no-invite signup
UserMailer.deliveries.length.should == 1
UserMailer.deliveries[0].html_part.body.include?("To confirm this email address")== 1
@ -45,10 +59,10 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do
end
it {
should have_title("JamKazam")
should have_selector('h2', text: "musicians")
should have_title("Signup Confirmation")
should have_selector('h1', text: "Email Confirmed")
UserMailer.deliveries.length.should == 1
UserMailer.deliveries[0].html_part.body.include?("Following are links to some resources")== 1
UserMailer.deliveries[0].html_part.body.include?("Following are links to some resources") == 1
}
end
end

View File

@ -61,7 +61,7 @@ describe "Musician Search API", :type => :api do
it "gets musicians for long-distance locations" do
get_query({score_limit: Search::GOOD_SCORE})
good_response
(json['musicians'] || []).count.should == 5
(json['musicians'] || []).count.should == 3 # only users that have the 10 latency are low enough
end
end
@ -106,8 +106,9 @@ describe "Musician Search API", :type => :api do
f5.save
expect(@user4.followers.count).to be 3
get_query
get_query(orderby: :followed)
good_response
puts last_response.body
musician = json["musicians"][0]
expect(musician["id"]).to eq(@user4.id)
followings = musician['followings']

View File

@ -335,6 +335,9 @@ def schedule_session(options = {})
fan_chat = options[:fan_chat].nil? ? false : options[:fan_chat]
musician_access_value = 'Musicians may join by approval'
fan_permission_value = 'Fans may listen, chat with each other'
rsvp = options[:rsvp]
immediate = options[:immediate]
quickstart = options[:quickstart]
if musician_access && !approval_required
musician_access_value = 'Musicians may join at will'
@ -356,26 +359,40 @@ def schedule_session(options = {})
expect(page).to have_selector('h1', text: 'create session')
within('#create-session-form') do
find('li[create-type="schedule-future"] ins').trigger(:click)
find('.btn-next').trigger(:click)
jk_select(genre, '#create-session-form select[name="genres"]')
fill_in('session-name', :with => unique_session_name)
fill_in('session-description', :with => unique_session_desc)
find('.btn-next').trigger(:click)
if rsvp
find('li[create-type="rsvp"] ins').trigger(:click)
elsif immediate
find('li[create-type="immediately"] ins').trigger(:click)
elsif quickstart
find('li[create-type="quick-start"] ins').trigger(:click)
else
find('li[create-type="schedule-future"] ins').trigger(:click)
end
find('.btn-next').trigger(:click)
find('div#divSessionPolicy ins').trigger(:click)
jk_select(musician_access_value, '#session-musician-access')
jk_select(fan_permission_value, '#session-fans-access')
find('.btn-next').trigger(:click)
unless quickstart
jk_select(genre, '#create-session-form select[name="genres"]')
fill_in('session-name', :with => unique_session_name)
fill_in('session-description', :with => unique_session_desc)
find('.btn-next').trigger(:click)
find('.btn-next', text: 'PUBLISH SESSION').trigger(:click)
find('.btn-next').trigger(:click)
find('div#divSessionPolicy ins').trigger(:click)
jk_select(musician_access_value, '#session-musician-access')
jk_select(fan_permission_value, '#session-fans-access')
find('.btn-next').trigger(:click)
end
unless quickstart || immediate
find('.btn-next', text: 'PUBLISH SESSION').trigger(:click)
else
find('.btn-next', text: 'START SESSION').trigger(:click)
end
end
find('h2', text: 'create session')
# find('h2', text: 'create session') unless quickstart || immediate
sleep 1 # to get rid of this, we need to verify that the URL is /client#/home.. otherwise intermittent fails
end

View File

@ -1,462 +1,509 @@
/*!
* iCheck v0.9.1, http://git.io/uhUPMA
* =================================
* Powerful jQuery plugin for checkboxes and radio buttons customization
* iCheck v1.0.2, http://git.io/arlzeA
* ===================================
* Powerful jQuery and Zepto plugin for checkboxes and radio buttons customization
*
* (c) 2013 Damir Foy, http://damirfoy.com
* (c) 2013 Damir Sultanov, http://fronteed.com
* MIT Licensed
*/
(function($) {
// Cached vars
var _iCheck = 'iCheck',
_iCheckHelper = _iCheck + '-helper',
_checkbox = 'checkbox',
_radio = 'radio',
_checked = 'checked',
_unchecked = 'un' + _checked,
_disabled = 'disabled',
_determinate = 'determinate',
_indeterminate = 'in' + _determinate,
_update = 'update',
_type = 'type',
_click = 'click',
_touch = 'touchbegin.i touchend.i',
_add = 'addClass',
_remove = 'removeClass',
_callback = 'trigger',
_label = 'label',
_cursor = 'cursor',
_mobile = /ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);
// Cached vars
var _iCheck = 'iCheck',
_iCheckHelper = _iCheck + '-helper',
_checkbox = 'checkbox',
_radio = 'radio',
_checked = 'checked',
_unchecked = 'un' + _checked,
_disabled = 'disabled',
_determinate = 'determinate',
_indeterminate = 'in' + _determinate,
_update = 'update',
_type = 'type',
_click = 'click',
_touch = 'touchbegin.i touchend.i',
_add = 'addClass',
_remove = 'removeClass',
_callback = 'trigger',
_label = 'label',
_cursor = 'cursor',
_mobile = /ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);
// Plugin init
$.fn[_iCheck] = function(options, fire) {
// Plugin init
$.fn[_iCheck] = function(options, fire) {
// Walker
var handle = ':' + _checkbox + ', :' + _radio,
stack = $(),
walker = function(object) {
object.each(function() {
var self = $(this);
// Walker
var handle = 'input[type="' + _checkbox + '"], input[type="' + _radio + '"]',
stack = $(),
walker = function(object) {
object.each(function() {
var self = $(this);
if (self.is(handle)) {
stack = stack.add(self);
} else {
stack = stack.add(self.find(handle));
};
});
};
if (self.is(handle)) {
stack = stack.add(self);
} else {
stack = stack.add(self.find(handle));
}
});
};
// Check if we should operate with some method
if (/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(options)) {
// Check if we should operate with some method
if (/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(options)) {
// Normalize method's name
options = options.toLowerCase();
// Normalize method's name
options = options.toLowerCase();
// Find checkboxes and radio buttons
walker(this);
// Find checkboxes and radio buttons
walker(this);
return stack.each(function() {
if (options == 'destroy') {
tidy(this, 'ifDestroyed');
} else {
operate($(this), true, options);
};
return stack.each(function() {
var self = $(this);
// Fire method's callback
if ($.isFunction(fire)) {
fire();
};
});
// Customization
} else if (typeof options == 'object' || !options) {
// Check if any options were passed
var settings = $.extend({
checkedClass: _checked,
disabledClass: _disabled,
indeterminateClass: _indeterminate,
labelHover: true
}, options),
selector = settings.handle,
hoverClass = settings.hoverClass || 'hover',
focusClass = settings.focusClass || 'focus',
activeClass = settings.activeClass || 'active',
labelHover = !!settings.labelHover,
labelHoverClass = settings.labelHoverClass || 'hover',
// Setup clickable area
area = ('' + settings.increaseArea).replace('%', '') | 0;
// Selector limit
if (selector == _checkbox || selector == _radio) {
handle = ':' + selector;
};
// Clickable area limit
if (area < -50) {
area = -50;
};
// Walk around the selector
walker(this);
return stack.each(function() {
// If already customized
tidy(this);
var self = $(this),
node = this,
id = node.id,
// Layer styles
offset = -area + '%',
size = 100 + (area * 2) + '%',
layer = {
position: 'absolute',
top: offset,
left: offset,
display: 'block',
width: size,
height: size,
margin: 0,
padding: 0,
background: '#fff',
border: 0,
opacity: 0
},
// Choose how to hide input
hide = _mobile ? {
position: 'absolute',
visibility: 'hidden'
} : area ? layer : {
position: 'absolute',
opacity: 0
},
// Get proper class
className = node[_type] == _checkbox ? settings.checkboxClass || 'i' + _checkbox : settings.radioClass || 'i' + _radio,
// Find assigned labels
label = $(_label + '[for="' + id + '"]').add(self.closest(_label)),
// Wrap input
parent = self.wrap('<div class="' + className + '"/>')[_callback]('ifCreated').parent().append(settings.insert),
// Layer addition
helper = $('<ins class="' + _iCheckHelper + '"/>').css(layer).appendTo(parent);
// Finalize customization
self.data(_iCheck, {o: settings, s: self.attr('style')}).css(hide);
!!settings.inheritClass && parent[_add](node.className);
!!settings.inheritID && id && parent.attr('id', _iCheck + '-' + id);
parent.css('position') == 'static' && parent.css('position', 'relative');
operate(self, true, _update);
// Label events
if (label.length) {
label.on(_click + '.i mouseenter.i mouseleave.i ' + _touch, function(event) {
var type = event[_type],
item = $(this);
// Do nothing if input is disabled
if (!node[_disabled]) {
// Click
if (type == _click) {
operate(self, false, true);
// Hover state
} else if (labelHover) {
// mouseleave|touchend
if (/ve|nd/.test(type)) {
parent[_remove](hoverClass);
item[_remove](labelHoverClass);
} else {
parent[_add](hoverClass);
item[_add](labelHoverClass);
};
};
if (_mobile) {
event.stopPropagation();
} else {
return false;
};
};
});
};
// Input events
self.on(_click + '.i focus.i blur.i keyup.i keydown.i keypress.i', function(event) {
var type = event[_type],
key = event.keyCode;
// Click
if (type == _click) {
return false;
// Keydown
} else if (type == 'keydown' && key == 32) {
if (!(node[_type] == _radio && node[_checked])) {
if (node[_checked]) {
off(self, _checked);
} else {
on(self, _checked);
};
};
return false;
// Keyup
} else if (type == 'keyup' && node[_type] == _radio) {
!node[_checked] && on(self, _checked);
// Focus/blur
} else if (/us|ur/.test(type)) {
parent[type == 'blur' ? _remove : _add](focusClass);
};
});
// Helper events
helper.on(_click + ' mousedown mouseup mouseover mouseout ' + _touch, function(event) {
var type = event[_type],
// mousedown|mouseup
toggle = /wn|up/.test(type) ? activeClass : hoverClass;
// Do nothing if input is disabled
if (!node[_disabled]) {
// Click
if (type == _click) {
operate(self, false, true);
// Active and hover states
} else {
// State is on
if (/wn|er|in/.test(type)) {
// mousedown|mouseover|touchbegin
parent[_add](toggle);
// State is off
} else {
parent[_remove](toggle + ' ' + activeClass);
};
// Label hover
if (label.length && labelHover && toggle == hoverClass) {
// mouseout|touchend
label[/ut|nd/.test(type) ? _remove : _add](labelHoverClass);
};
};
if (_mobile) {
event.stopPropagation();
} else {
return false;
};
};
});
});
if (options == 'destroy') {
tidy(self, 'ifDestroyed');
} else {
return this;
};
};
operate(self, true, options);
}
// Do something with inputs
function operate(input, direct, method) {
var node = input[0];
state = /er/.test(method) ? _indeterminate : /bl/.test(method) ? _disabled : _checked,
active = method == _update ? {
checked: node[_checked],
disabled: node[_disabled],
indeterminate: input.attr(_indeterminate) == 'true' || input.attr(_determinate) == 'false'
} : node[state];
// Fire method's callback
if ($.isFunction(fire)) {
fire();
}
});
// Check, disable or indeterminate
if (/^(ch|di|in)/.test(method) && !active) {
on(input, state);
// Customization
} else if (typeof options == 'object' || !options) {
// Uncheck, enable or determinate
} else if (/^(un|en|de)/.test(method) && active) {
off(input, state);
// Check if any options were passed
var settings = $.extend({
checkedClass: _checked,
disabledClass: _disabled,
indeterminateClass: _indeterminate,
labelHover: true
}, options),
// Update
} else if (method == _update) {
selector = settings.handle,
hoverClass = settings.hoverClass || 'hover',
focusClass = settings.focusClass || 'focus',
activeClass = settings.activeClass || 'active',
labelHover = !!settings.labelHover,
labelHoverClass = settings.labelHoverClass || 'hover',
// Handle states
for (var state in active) {
if (active[state]) {
on(input, state, true);
// Setup clickable area
area = ('' + settings.increaseArea).replace('%', '') | 0;
// Selector limit
if (selector == _checkbox || selector == _radio) {
handle = 'input[type="' + selector + '"]';
}
// Clickable area limit
if (area < -50) {
area = -50;
}
// Walk around the selector
walker(this);
return stack.each(function() {
var self = $(this);
// If already customized
tidy(self);
var node = this,
id = node.id,
// Layer styles
offset = -area + '%',
size = 100 + (area * 2) + '%',
layer = {
position: 'absolute',
top: offset,
left: offset,
display: 'block',
width: size,
height: size,
margin: 0,
padding: 0,
background: '#fff',
border: 0,
opacity: 0
},
// Choose how to hide input
hide = _mobile ? {
position: 'absolute',
visibility: 'hidden'
} : area ? layer : {
position: 'absolute',
opacity: 0
},
// Get proper class
className = node[_type] == _checkbox ? settings.checkboxClass || 'i' + _checkbox : settings.radioClass || 'i' + _radio,
// Find assigned labels
label = $(_label + '[for="' + id + '"]').add(self.closest(_label)),
// Check ARIA option
aria = !!settings.aria,
// Set ARIA placeholder
ariaID = _iCheck + '-' + Math.random().toString(36).substr(2,6),
// Parent & helper
parent = '<div class="' + className + '" ' + (aria ? 'role="' + node[_type] + '" ' : ''),
helper;
// Set ARIA "labelledby"
if (aria) {
label.each(function() {
parent += 'aria-labelledby="';
if (this.id) {
parent += this.id;
} else {
this.id = ariaID;
parent += ariaID;
}
parent += '"';
});
}
// Wrap input
parent = self.wrap(parent + '/>')[_callback]('ifCreated').parent().append(settings.insert);
// Layer addition
helper = $('<ins class="' + _iCheckHelper + '"/>').css(layer).appendTo(parent);
// Finalize customization
self.data(_iCheck, {o: settings, s: self.attr('style')}).css(hide);
!!settings.inheritClass && parent[_add](node.className || '');
!!settings.inheritID && id && parent.attr('id', _iCheck + '-' + id);
parent.css('position') == 'static' && parent.css('position', 'relative');
operate(self, true, _update);
// Label events
if (label.length) {
label.on(_click + '.i mouseover.i mouseout.i ' + _touch, function(event) {
var type = event[_type],
item = $(this);
// Do nothing if input is disabled
if (!node[_disabled]) {
// Click
if (type == _click) {
if ($(event.target).is('a')) {
return;
}
operate(self, false, true);
// Hover state
} else if (labelHover) {
// mouseout|touchend
if (/ut|nd/.test(type)) {
parent[_remove](hoverClass);
item[_remove](labelHoverClass);
} else {
off(input, state, true);
};
};
parent[_add](hoverClass);
item[_add](labelHoverClass);
}
}
} else if (!direct || method == 'toggle') {
if (_mobile) {
event.stopPropagation();
} else {
return false;
}
}
});
}
// Helper or label was clicked
if (!direct) {
input[_callback]('ifClicked');
};
// Input events
self.on(_click + '.i focus.i blur.i keyup.i keydown.i keypress.i', function(event) {
var type = event[_type],
key = event.keyCode;
// Toggle checked state
if (active) {
if (node[_type] !== _radio) {
off(input, state);
};
} else {
on(input, state);
};
};
};
// Click
if (type == _click) {
return false;
// Add checked, disabled or indeterminate state
function on(input, state, keep) {
var node = input[0],
parent = input.parent(),
checked = state == _checked,
indeterminate = state == _indeterminate,
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
regular = option(node, callback + capitalize(node[_type])),
specific = option(node, state + capitalize(node[_type]));
// Keydown
} else if (type == 'keydown' && key == 32) {
if (!(node[_type] == _radio && node[_checked])) {
if (node[_checked]) {
off(self, _checked);
} else {
on(self, _checked);
}
}
// Prevent unnecessary actions
if (node[state] !== true) {
return false;
// Toggle assigned radio buttons
if (!keep && state == _checked && node[_type] == _radio && node.name) {
var form = input.closest('form'),
inputs = 'input[name="' + node.name + '"]';
// Keyup
} else if (type == 'keyup' && node[_type] == _radio) {
!node[_checked] && on(self, _checked);
inputs = form.length ? form.find(inputs) : $(inputs);
// Focus/blur
} else if (/us|ur/.test(type)) {
parent[type == 'blur' ? _remove : _add](focusClass);
}
});
inputs.each(function() {
if (this !== node && $.data(this, _iCheck)) {
off($(this), state);
};
});
};
// Helper events
helper.on(_click + ' mousedown mouseup mouseover mouseout ' + _touch, function(event) {
var type = event[_type],
// Indeterminate state
if (indeterminate) {
// mousedown|mouseup
toggle = /wn|up/.test(type) ? activeClass : hoverClass;
// Add indeterminate state
node[state] = true;
// Do nothing if input is disabled
if (!node[_disabled]) {
// Remove checked state
if (node[_checked]) {
off(input, _checked, 'force');
};
// Click
if (type == _click) {
operate(self, false, true);
// Checked or disabled state
// Active and hover states
} else {
// Add checked or disabled state
if (!keep) {
node[state] = true;
};
// State is on
if (/wn|er|in/.test(type)) {
// Remove indeterminate state
if (checked && node[_indeterminate]) {
off(input, _indeterminate, false);
};
};
// mousedown|mouseover|touchbegin
parent[_add](toggle);
// Trigger callbacks
callbacks(input, checked, state, keep);
};
// State is off
} else {
parent[_remove](toggle + ' ' + activeClass);
}
// Add proper cursor
if (node[_disabled] && !!option(node, _cursor, true)) {
parent.find('.' + _iCheckHelper).css(_cursor, 'default');
};
// Label hover
if (label.length && labelHover && toggle == hoverClass) {
// Add state class
parent[_add](specific || option(node, state));
// mouseout|touchend
label[/ut|nd/.test(type) ? _remove : _add](labelHoverClass);
}
}
// Remove regular state class
parent[_remove](regular || option(node, callback) || '');
};
if (_mobile) {
event.stopPropagation();
} else {
return false;
}
}
});
});
} else {
return this;
}
};
// Remove checked, disabled or indeterminate state
function off(input, state, keep) {
var node = input[0],
parent = input.parent(),
checked = state == _checked,
indeterminate = state == _indeterminate,
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
regular = option(node, callback + capitalize(node[_type])),
specific = option(node, state + capitalize(node[_type]));
// Do something with inputs
function operate(input, direct, method) {
var node = input[0],
state = /er/.test(method) ? _indeterminate : /bl/.test(method) ? _disabled : _checked,
active = method == _update ? {
checked: node[_checked],
disabled: node[_disabled],
indeterminate: input.attr(_indeterminate) == 'true' || input.attr(_determinate) == 'false'
} : node[state];
// Prevent unnecessary actions
if (node[state] !== false) {
// Check, disable or indeterminate
if (/^(ch|di|in)/.test(method) && !active) {
on(input, state);
// Toggle state
if (indeterminate || !keep || keep == 'force') {
node[state] = false;
};
// Uncheck, enable or determinate
} else if (/^(un|en|de)/.test(method) && active) {
off(input, state);
// Trigger callbacks
callbacks(input, checked, callback, keep);
};
// Update
} else if (method == _update) {
// Add proper cursor
if (!node[_disabled] && !!option(node, _cursor, true)) {
parent.find('.' + _iCheckHelper).css(_cursor, 'pointer');
};
// Handle states
for (var each in active) {
if (active[each]) {
on(input, each, true);
} else {
off(input, each, true);
}
}
// Remove state class
parent[_remove](specific || option(node, state) || '');
} else if (!direct || method == 'toggle') {
// Add regular state class
parent[_add](regular || option(node, callback));
};
// Helper or label was clicked
if (!direct) {
input[_callback]('ifClicked');
}
// Remove all traces
function tidy(node, callback) {
if ($.data(node, _iCheck)) {
var input = $(node);
// Toggle checked state
if (active) {
if (node[_type] !== _radio) {
off(input, state);
}
} else {
on(input, state);
}
}
}
// Remove everything except input
input.parent().html(input.attr('style', $.data(node, _iCheck).s || '')[_callback](callback || ''));
// Add checked, disabled or indeterminate state
function on(input, state, keep) {
var node = input[0],
parent = input.parent(),
checked = state == _checked,
indeterminate = state == _indeterminate,
disabled = state == _disabled,
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
regular = option(input, callback + capitalize(node[_type])),
specific = option(input, state + capitalize(node[_type]));
// Unbind events
input.off('.i').unwrap();
$(_label + '[for="' + node.id + '"]').add(input.closest(_label)).off('.i');
};
};
// Prevent unnecessary actions
if (node[state] !== true) {
// Get some option
function option(node, state, regular) {
if ($.data(node, _iCheck)) {
return $.data(node, _iCheck).o[state + (regular ? '' : 'Class')];
};
};
// Toggle assigned radio buttons
if (!keep && state == _checked && node[_type] == _radio && node.name) {
var form = input.closest('form'),
inputs = 'input[name="' + node.name + '"]';
// Capitalize some string
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
inputs = form.length ? form.find(inputs) : $(inputs);
// Executable handlers
function callbacks(input, checked, callback, keep) {
inputs.each(function() {
if (this !== node && $(this).data(_iCheck)) {
off($(this), state);
}
});
}
// Indeterminate state
if (indeterminate) {
// Add indeterminate state
node[state] = true;
// Remove checked state
if (node[_checked]) {
off(input, _checked, 'force');
}
// Checked or disabled state
} else {
// Add checked or disabled state
if (!keep) {
if (checked) {
input[_callback]('ifToggled');
};
node[state] = true;
}
input[_callback]('ifChanged')[_callback]('if' + capitalize(callback));
};
};
})(jQuery);
// Remove indeterminate state
if (checked && node[_indeterminate]) {
off(input, _indeterminate, false);
}
}
// Trigger callbacks
callbacks(input, checked, state, keep);
}
// Add proper cursor
if (node[_disabled] && !!option(input, _cursor, true)) {
parent.find('.' + _iCheckHelper).css(_cursor, 'default');
}
// Add state class
parent[_add](specific || option(input, state) || '');
// Set ARIA attribute
if (!!parent.attr('role') && !indeterminate) {
parent.attr('aria-' + (disabled ? _disabled : _checked), 'true');
}
// Remove regular state class
parent[_remove](regular || option(input, callback) || '');
}
// Remove checked, disabled or indeterminate state
function off(input, state, keep) {
var node = input[0],
parent = input.parent(),
checked = state == _checked,
indeterminate = state == _indeterminate,
disabled = state == _disabled,
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
regular = option(input, callback + capitalize(node[_type])),
specific = option(input, state + capitalize(node[_type]));
// Prevent unnecessary actions
if (node[state] !== false) {
// Toggle state
if (indeterminate || !keep || keep == 'force') {
node[state] = false;
}
// Trigger callbacks
callbacks(input, checked, callback, keep);
}
// Add proper cursor
if (!node[_disabled] && !!option(input, _cursor, true)) {
parent.find('.' + _iCheckHelper).css(_cursor, 'pointer');
}
// Remove state class
parent[_remove](specific || option(input, state) || '');
// Set ARIA attribute
if (!!parent.attr('role') && !indeterminate) {
parent.attr('aria-' + (disabled ? _disabled : _checked), 'false');
}
// Add regular state class
parent[_add](regular || option(input, callback) || '');
}
// Remove all traces
function tidy(input, callback) {
if (input.data(_iCheck)) {
// Remove everything except input
input.parent().html(input.attr('style', input.data(_iCheck).s || ''));
// Callback
if (callback) {
input[_callback](callback);
}
// Unbind events
input.off('.i').unwrap();
$(_label + '[for="' + input[0].id + '"]').add(input.closest(_label)).off('.i');
}
}
// Get some option
function option(input, state, regular) {
if (input.data(_iCheck)) {
return input.data(_iCheck).o[state + (regular ? '' : 'Class')];
}
}
// Capitalize some string
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
// Executable handlers
function callbacks(input, checked, callback, keep) {
if (!keep) {
if (checked) {
input[_callback]('ifToggled');
}
input[_callback]('ifChanged')[_callback]('if' + capitalize(callback));
}
}
})(window.jQuery || window.Zepto);