diff --git a/ruby/lib/jam_ruby/models/band.rb b/ruby/lib/jam_ruby/models/band.rb
index 7cdd0a0e8..5f8da60e1 100644
--- a/ruby/lib/jam_ruby/models/band.rb
+++ b/ruby/lib/jam_ruby/models/band.rb
@@ -70,6 +70,14 @@ module JamRuby
self.music_sessions.size
end
+ def latitude
+ lat
+ end
+
+ def longitude
+ lng
+ end
+
def recent_history
recordings = Recording
.joins(:claimed_recordings)
diff --git a/ruby/lib/jam_ruby/models/geo_ip_locations.rb b/ruby/lib/jam_ruby/models/geo_ip_locations.rb
index 1f8caeb50..215cd5e26 100644
--- a/ruby/lib/jam_ruby/models/geo_ip_locations.rb
+++ b/ruby/lib/jam_ruby/models/geo_ip_locations.rb
@@ -54,6 +54,19 @@ module JamRuby
{city: city, state: state, country: country, addr: addr, locidispid: (locid.nil? || ispid.nil?) ? nil : Score.compute_locidispid(locid, ispid) }
end
+ # returns a display- friendly bit of info about this location
+ def info
+ country_model = Country.where(countrycode: countrycode).first
+ region_model = Region.where(region: region, countrycode: countrycode).first
+ {
+ countrycode: countrycode,
+ country: country_model ? country_model.countryname : nil,
+ regioncode: region,
+ region: region_model ? region_model.regionname : nil,
+ city: city
+ }
+ end
+
def self.createx(locid, countrycode, region, city, postalcode, latitude, longitude, metrocode, areacode)
c = connection.raw_connection
c.exec_params("insert into #{self.table_name} (locid, countrycode, region, city, postalcode, latitude, longitude, metrocode, areacode, geog) values($1, $2, $3, $4, $5, $6, $7, $8, $9, ST_SetSRID(ST_MakePoint($7, $6), 4326)::geography)",
@@ -82,8 +95,8 @@ module JamRuby
# it isn't reasonable for both to be 0...
latlng = [geo.latitude, geo.longitude]
end
- elsif current_user and current_user.locidispid and current_user.locidispid != 0
- location = GeoIpLocations.find_by_locid(current_user.locidispid/1000000)
+ elsif current_user and current_user.last_jam_locidispid and current_user.last_jam_locidispid != 0
+ location = GeoIpLocations.find_by_locid(current_user.last_jam_locidispid/1000000)
if location and location.latitude and location.longitude and (location.latitude != 0 or location.longitude != 0)
# it isn't reasonable for both to be 0...
latlng = [location.latitude, location.longitude]
@@ -99,7 +112,7 @@ module JamRuby
end
if latlng
- relation = relation.where(['latitude IS NOT NULL AND longitude IS NOT NULL']).within(distance, origin: latlng)
+ relation = relation.where(['lat IS NOT NULL AND lng IS NOT NULL']).within(distance, origin: latlng)
end
end
relation
diff --git a/ruby/lib/jam_ruby/models/search.rb b/ruby/lib/jam_ruby/models/search.rb
index 1575c9b20..a5efa82cf 100644
--- a/ruby/lib/jam_ruby/models/search.rb
+++ b/ruby/lib/jam_ruby/models/search.rb
@@ -99,12 +99,13 @@ module JamRuby
M_ORDER_PLAYS = ['Most Plays', :plays]
M_ORDER_PLAYING = ['Playing Now', :playing]
M_ORDER_LATENCY = ['Latency To Me', :latency]
- M_ORDERINGS = [M_ORDER_LATENCY, M_ORDER_FOLLOWS, M_ORDER_PLAYS]
+ M_ORDER_DISTANCE = ['Distance To Me', :distance]
+ M_ORDERINGS = [M_ORDER_LATENCY, M_ORDER_DISTANCE, 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]]
+ DISTANCE_OPTS = B_DISTANCE_OPTS = M_DISTANCE_OPTS = [[25.to_s, 25], [50.to_s, 50], [100.to_s, 100], [250.to_s, 250], [500.to_s, 500], [1000.to_s, 1000] ]
# the values for score ranges are raw roundtrip scores. david often talks of one way scores (<= 20 is good), but
# the client reports scores as roundtrip and the server uses those values throughout
@@ -117,6 +118,7 @@ module JamRuby
ANY_SCORE = ''
M_SCORE_OPTS = [['Any', ANY_SCORE], ['Good', GOOD_SCORE], ['Moderate', MODERATE_SCORE], ['Poor', POOR_SCORE], ['Unacceptable', UNACCEPTABLE_SCORE]]
M_SCORE_DEFAULT = ANY_SCORE
+ M_DISTANCE_DEFAULT = 500
F_SORT_RECENT = ['Most Recent', :date]
F_SORT_OLDEST = ['Most Liked', :likes]
@@ -148,10 +150,7 @@ module JamRuby
# distance - defunct!
# city - defunct!
# remote_ip - defunct!
- def self.musician_filter(params={}, user=nil, conn=nil)
- # puts "================ params #{params.inspect}"
- # puts "================ user #{user.inspect}"
- # puts "================ conn #{conn.inspect}"
+ def self.musician_filter(params={}, user=nil)
rel = User.musicians # not musicians_geocoded on purpose; we allow 'unknowns' to surface in the search page
rel = rel.select('users.*')
@@ -173,11 +172,23 @@ module JamRuby
score_limit = l
end
- # puts "================ score_limit #{score_limit}"
+ locidispid = user.nil? ? 0 : (user.last_jam_locidispid || 0)
- locidispid = ((conn and conn.client_type == 'client') ? conn.locidispid : ((user and user.musician) ? user.last_jam_locidispid : nil))
+ # user can override their location with these 3 values
+ country = params[:country]
+ region = params[:region]
+ city = params[:city]
- # puts "================ locidispid #{locidispid}"
+ my_locid = nil # this is used for distance searches only
+
+ if country && region && city
+ geoiplocation = GeoIpLocations.where(countrycode: country, region: region, city: city).first
+ my_locid = geoiplocation.locid
+ end
+
+ unless my_locid
+ my_locid = locidispid/1000000 # if the user didn't specify a location to search on, user their account locidispid
+ end
unless locidispid.nil?
# score_join of left allows for null scores, whereas score_join of inner requires a score however good or bad
@@ -229,10 +240,18 @@ module JamRuby
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 :distance
+ # convert miles to meters for PostGIS functions
+ miles = params[:distance].blank? ? 500 : params[:distance].to_i
+ meters = miles * 1609.34
+ rel = rel.joins("INNER JOIN geoiplocations AS my_geo ON #{my_locid} = my_geo.locid")
+ rel = rel.joins("INNER JOIN geoiplocations AS other_geo ON users.last_jam_locidispid/1000000 = other_geo.locid")
+ rel = rel.where("users.last_jam_locidispid/1000000 IN (SELECT locid FROM geoiplocations WHERE geog && st_buffer((SELECT geog FROM geoiplocations WHERE locid = #{my_locid}), #{meters}))")
+ rel = rel.group("my_geo.geog, other_geo.geog")
+ rel = rel.order('st_distance(my_geo.geog, other_geo.geog)')
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')
@@ -254,17 +273,10 @@ module JamRuby
rel = rel.order('users.created_at DESC')
- # rel = rel.select(sel_str)
rel, page = self.relation_pagination(rel, params)
rel = rel.includes([:instruments, :followings, :friends])
- # puts "======================== sql #{rel.to_sql}"
objs = rel.all
- # puts "======================== objs #{objs.inspect}"
- # if objs.length > 0
- # puts "======================== attributes #{objs[0].attributes}"
- # puts "======================== score #{objs[0].score}"
- # end
srch = Search.new
srch.search_type = :musicians_filter
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index dde31b7ad..b2cf8f678 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -1003,6 +1003,11 @@ module JamRuby
self.save
end
+ # gets the GeoIpLocation for the user's last_jam_locidispid (where are they REALLY, vs profile info)
+ def geoiplocation
+ GeoIpLocations.find_by_locid(last_jam_locidispid / 1000000) if last_jam_locidispid
+ end
+
def update_last_jam(remote_ip, reason)
location = GeoIpLocations.lookup(remote_ip)
self.last_jam_addr = location[:addr]
diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb
index a348d1a1e..1e5faf4c4 100644
--- a/ruby/spec/factories.rb
+++ b/ruby/spec/factories.rb
@@ -39,6 +39,50 @@ FactoryGirl.define do
admin true
end
+ factory :austin_user do
+ first_name 'Austin'
+ sequence(:last_name) { |n| "#{n}" }
+ state 'TX'
+ city 'Austin'
+ last_jam_locidispid { austin_geoip[:locidispid] }
+ last_jam_addr { austin_ip }
+ end
+
+ factory :dallas_user do
+ first_name 'Dallas'
+ sequence(:last_name) { |n| "#{n}" }
+ state 'TX'
+ city 'Dallas'
+ last_jam_locidispid { dallas_geoip[:locidispid] }
+ last_jam_addr { dallas_ip }
+ end
+
+ factory :houston_user do
+ first_name 'Houston'
+ sequence(:last_name) { |n| "#{n}" }
+ state 'TX'
+ city 'Houston'
+ last_jam_locidispid { houston_geoip[:locidispid] }
+ last_jam_addr { houston_ip }
+ end
+
+ factory :miami_user do
+ first_name 'Miami'
+ sequence(:last_name) { |n| "#{n}" }
+ state 'FL'
+ city 'Miami'
+ last_jam_locidispid { miami_geoip[:locidispid] }
+ last_jam_addr { miami_ip }
+ end
+
+ factory :seattle_user do
+ first_name 'Seattle'
+ sequence(:last_name) { |n| "#{n}" }
+ state 'WA'
+ city 'Seattle'
+ last_jam_locidispid { seattle_geoip[:locidispid] }
+ last_jam_addr { seattle_ip }
+ end
factory :single_user_session do
after(:create) do |user, evaluator|
active_music_session = FactoryGirl.create(:active_music_session, :creator => user)
diff --git a/ruby/spec/jam_ruby/models/musician_search_spec.rb b/ruby/spec/jam_ruby/models/musician_search_spec.rb
index 0482e8ba1..9765e9785 100644
--- a/ruby/spec/jam_ruby/models/musician_search_spec.rb
+++ b/ruby/spec/jam_ruby/models/musician_search_spec.rb
@@ -2,356 +2,423 @@ require 'spec_helper'
describe 'Musician search' do
- before(:each) do
- # @geocode1 = FactoryGirl.create(:geocoder)
- # @geocode2 = FactoryGirl.create(:geocoder)
- t = Time.now - 10.minute
+ # need a data set with actual distances
+ describe "test set A" do
- @user1 = FactoryGirl.create(:user, created_at: t+1.minute, last_jam_locidispid: 1)
- @user2 = FactoryGirl.create(:user, created_at: t+2.minute, last_jam_locidispid: 2)
- @user3 = FactoryGirl.create(:user, created_at: t+3.minute, last_jam_locidispid: 3)
- @user4 = FactoryGirl.create(:user, created_at: t+4.minute, last_jam_locidispid: 4)
- @user5 = FactoryGirl.create(:user, created_at: t+5.minute, last_jam_locidispid: 5)
- @user6 = FactoryGirl.create(:user, created_at: t+6.minute) # not geocoded
- @user7 = FactoryGirl.create(:user, created_at: t+7.minute, musician: false) # not musician
-
- @musicians = []
- @musicians << @user1
- @musicians << @user2
- @musicians << @user3
- @musicians << @user4
- @musicians << @user5
- @musicians << @user6
-
- @geomusicians = []
- @geomusicians << @user1
- @geomusicians << @user2
- @geomusicians << @user3
- @geomusicians << @user4
- @geomusicians << @user5
-
- # 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)
- Score.createx(1, 'a', 1, 4, 'd', 4, 40)
- Score.createx(2, 'b', 2, 3, 'c', 3, 15)
- Score.createx(2, 'b', 2, 4, 'd', 4, 70)
- end
-
- context 'default filter settings' do
-
- it "finds all musicians" do
- # expects all the musicians (geocoded)
- results = Search.musician_filter({score_limit: Search::TEST_SCORE})
- results.search_type.should == :musicians_filter
- results.results.count.should == @musicians.length
- results.results.should eq @musicians.reverse
+ before(:each) do
+ create_phony_database
end
- it "finds all musicians page 1" do
- # expects all the musicians
- results = Search.musician_filter({page: 1, score_limit: Search::TEST_SCORE})
- results.search_type.should == :musicians_filter
- results.results.count.should == @musicians.length
- results.results.should eq @musicians.reverse
- end
+ let!(:austin_user) { FactoryGirl.create(:austin_user) }
+ let!(:dallas_user) { FactoryGirl.create(:dallas_user) }
+ let!(:miami_user) { FactoryGirl.create(:miami_user) }
+ let!(:seattle_user) { FactoryGirl.create(:seattle_user) }
- it "finds all musicians page 2" do
- # expects no musicians (all fit on page 1)
- results = Search.musician_filter({page: 2, score_limit: Search::TEST_SCORE})
- results.search_type.should == :musicians_filter
- results.results.count.should == 0
- end
+ describe "search on distance" do
- it "finds all musicians page 1 per_page 3" do
- # expects three of the musicians
- 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 @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, score_limit: Search::TEST_SCORE})
- results.search_type.should == :musicians_filter
- 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, score_limit: Search::TEST_SCORE})
- results.search_type.should == :musicians_filter
- results.results.count.should == 0
- end
-
- it "sorts musicians by followers" do
- # establish sorting order
-
- # @user4
- f1 = Follow.new
- f1.user = @user2
- f1.followable = @user4
- f1.save
-
- f2 = Follow.new
- f2.user = @user3
- f2.followable = @user4
- f2.save
-
- f3 = Follow.new
- f3.user = @user4
- f3.followable = @user4
- f3.save
-
- # @user3
- f4 = Follow.new
- f4.user = @user3
- f4.followable = @user3
- f4.save
-
- f5 = Follow.new
- f5.user = @user4
- f5.followable = @user3
- f5.save
-
- # @user2
- f6 = Follow.new
- f6.user = @user1
- f6.followable = @user2
- f6.save
-
- # @user4.followers.concat([@user2, @user3, @user4])
- # @user3.followers.concat([@user3, @user4])
- # @user2.followers.concat([@user1])
- expect(@user4.followers.count).to be 3
- expect(Follow.count).to be 6
-
- # refresh the order to ensure it works right
- f1 = Follow.new
- f1.user = @user3
- f1.followable = @user2
- f1.save
-
- f2 = Follow.new
- f2.user = @user4
- f2.followable = @user2
- f2.save
-
- f3 = Follow.new
- f3.user = @user2
- f3.followable = @user2
- f3.save
-
- # @user2.followers.concat([@user3, @user4, @user2])
- 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
- expect(results.results[0].search_follow_count.to_i).not_to eq(0)
- # check the follow relationship between current_user and result
- expect(results.is_follower?(@user2)).to be true
- end
-
- it 'paginates properly' do
- # make sure pagination works right
- params = { :per_page => 2, :page => 1 , score_limit: Search::TEST_SCORE}
- results = Search.musician_filter(params)
- expect(results.results.count).to be 2
- end
-
- end
-
- def make_recording(usr)
- connection = FactoryGirl.create(:connection, :user => usr, locidispid: usr.last_jam_locidispid)
- instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
- track = FactoryGirl.create(:track, :connection => connection, :instrument => instrument)
- music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true)
- # music_session.connections << connection
- # music_session.save
- connection.join_the_session(music_session, true, nil, usr, 10)
- recording = Recording.start(music_session, usr)
- recording.stop
- recording.reload
- genre = FactoryGirl.create(:genre)
- recording.claim(usr, "name", "description", genre, true)
- recording.reload
- recording
- end
-
- def make_session(usr)
- connection = FactoryGirl.create(:connection, :user => usr, locidispid: usr.last_jam_locidispid)
- music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true)
- # music_session.connections << connection
- # music_session.save
- connection.join_the_session(music_session, true, nil, usr, 10)
- end
-
- context 'musician stat counters' do
-
- it "displays musicians top followings" do
- f1 = Follow.new
- f1.user = @user4
- f1.followable = @user4
- f1.save
-
- f2 = Follow.new
- f2.user = @user4
- f2.followable = @user3
- f2.save
-
- f3 = Follow.new
- f3.user = @user4
- f3.followable = @user2
- f3.save
-
- # @user4.followers.concat([@user4])
- # @user3.followers.concat([@user4])
- # @user2.followers.concat([@user4])
- expect(@user4.top_followings.count).to eq 3
- expect(@user4.top_followings.map(&:id)).to match_array((@musicians - [@user1, @user5, @user6]).map(&:id))
- end
-
- it "friends stat shows friend count" do
- # create friendship record
- Friendship.save(@user1.id, @user2.id)
- # search on 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
- @user1.reload
- expect(friend.friends?(@user2)).to be true
- expect(results.is_friend?(@user1)).to be true
- end
-
- it "recording stat shows recording count" do
- Recording.delete_all
-
- recording = make_recording(@user1)
- expect(recording.users.length).to be 1
- expect(recording.users.first).to eq(@user1)
- @user1.reload
- expect(@user1.recordings.length).to be 1
- expect(@user1.recordings.first).to eq(recording)
- expect(recording.claimed_recordings.length).to be 1
- expect(@user1.recordings.detect { |rr| rr == recording }).to_not be_nil
-
- 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
-
- expect(results.record_count(uu)).to be 1
- expect(results.session_count(uu)).to be 1
- end
-
- end
-
- context 'musician sorting' do
-
- it "by plays" do
- Recording.delete_all
-
- make_recording(@user1)
- # order results by num recordings
- 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)
- expect(results.results[1].id).to eq(@user3.id)
-
- # add more data and make sure order still correct
- make_recording(@user3)
- make_recording(@user3)
- 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', 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', 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)
- end
-
- end
-
- context 'filter settings' do
- it "searches musicisns for an instrument" do
- minst = FactoryGirl.create(:musician_instrument, {
- :user => @user1,
- :instrument => Instrument.find('tuba') })
- @user1.musician_instruments << minst
- @user1.reload
- ii = @user1.instruments.detect { |inst| inst.id == 'tuba' }
- expect(ii).to_not be_nil
- 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)
+ it "finds self when very local search" do
+ Search.musician_filter({distance: 1, orderby: 'distance'}, austin_user).results.should == [austin_user] # just to see that distance is 0 to self
+ Search.musician_filter({distance: 1, orderby: 'distance'}, dallas_user).results.should == [dallas_user] # just to see that distance is 0 to self
+ Search.musician_filter({distance: 1, orderby: 'distance'}, miami_user).results.should == [miami_user] # just to see that distance is 0 to self
+ Search.musician_filter({distance: 1, orderby: 'distance'}, seattle_user).results.should == [seattle_user] # just to see that distance is 0 to self
+ end
+
+ it "finds dallas when in range of austin" do
+ expected_results = [austin_user, dallas_user]
+
+ Search.musician_filter({distance: 500, orderby: 'distance'}, austin_user).results.should == expected_results
+ Search.musician_filter({distance: 100, orderby: 'distance'}, austin_user).results.should == [austin_user]
+ end
+
+ it "finds miami when in range of austin" do
+ expected_results = [austin_user, dallas_user, miami_user]
+
+ Search.musician_filter({distance: 1500, orderby: 'distance'}, austin_user).results.should == expected_results
+ Search.musician_filter({distance: 300, orderby: 'distance'}, austin_user).results.should == [austin_user, dallas_user]
+ Search.musician_filter({distance: 100, orderby: 'distance'}, austin_user).results.should == [austin_user]
+ end
+
+ it "finds seattle when in range of austin" do
+ expected_results = [austin_user, dallas_user, miami_user, seattle_user]
+
+ Search.musician_filter({distance: 2000, orderby: 'distance'}, austin_user).results.should == expected_results
+ Search.musician_filter({distance: 1500, orderby: 'distance'}, austin_user).results.should == [austin_user, dallas_user, miami_user]
+ Search.musician_filter({distance: 300, orderby: 'distance'}, austin_user).results.should == [austin_user, dallas_user]
+ Search.musician_filter({distance: 100, orderby: 'distance'}, austin_user).results.should == [austin_user]
+ end
+
+ it "finds austin & dallas by user-specified location when in range" do
+ Search.musician_filter({distance: 500, orderby: 'distance', city: 'Austin', region: 'TX', country: 'US'}, austin_user).results.should == [austin_user, dallas_user]
+ end
+
+ it "finds dallas & austin by user-specified location when in range" do
+ Search.musician_filter({distance: 500, orderby: 'distance', city: 'Dallas', region: 'TX', country: 'US'}, austin_user).results.should == [dallas_user, austin_user]
+ end
+
+ it "finds miami user-specified location when in range" do
+ Search.musician_filter({distance: 300, orderby: 'distance', city: 'Tampa', region: 'FL', country: 'US'}, austin_user).results.should == [miami_user]
+ end
+
+ it "finds all users with user-specified location when in range" do
+ Search.musician_filter({distance: 2500, orderby: 'distance', city: 'Tampa', region: 'FL', country: 'US'}, austin_user).results.should == [miami_user, dallas_user, austin_user, seattle_user]
end
- expect(results.results.count).to be 1
end
end
- context 'new users' do
+ describe "test set B" do
- it "find three for user1" do
- # user2..4 are scored against user1
- ms = Search.new_musicians(@user1, Time.now - 1.week)
- ms.should_not be_nil
- ms.length.should == 3
- ms.should eq [@user2, @user3, @user4]
+ before(:each) do
+ # @geocode1 = FactoryGirl.create(:geocoder)
+ # @geocode2 = FactoryGirl.create(:geocoder)
+ t = Time.now - 10.minute
+
+ @user1 = FactoryGirl.create(:user, created_at: t+1.minute, last_jam_locidispid: 1)
+ @user2 = FactoryGirl.create(:user, created_at: t+2.minute, last_jam_locidispid: 2)
+ @user3 = FactoryGirl.create(:user, created_at: t+3.minute, last_jam_locidispid: 3)
+ @user4 = FactoryGirl.create(:user, created_at: t+4.minute, last_jam_locidispid: 4)
+ @user5 = FactoryGirl.create(:user, created_at: t+5.minute, last_jam_locidispid: 5)
+ @user6 = FactoryGirl.create(:user, created_at: t+6.minute) # not geocoded
+ @user7 = FactoryGirl.create(:user, created_at: t+7.minute, musician: false) # not musician
+
+ @musicians = []
+ @musicians << @user1
+ @musicians << @user2
+ @musicians << @user3
+ @musicians << @user4
+ @musicians << @user5
+ @musicians << @user6
+
+ @geomusicians = []
+ @geomusicians << @user1
+ @geomusicians << @user2
+ @geomusicians << @user3
+ @geomusicians << @user4
+ @geomusicians << @user5
+
+ # 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)
+ Score.createx(1, 'a', 1, 4, 'd', 4, 40)
+ Score.createx(2, 'b', 2, 3, 'c', 3, 15)
+ Score.createx(2, 'b', 2, 4, 'd', 4, 70)
end
- it "find two for user2" do
- # user1,3,4 are scored against user1, but user4 is bad
- ms = Search.new_musicians(@user2, Time.now - 1.week)
- ms.should_not be_nil
- ms.length.should == 2
- ms.should eq [@user3, @user1]
+
+ context 'default filter settings' do
+
+ it "finds all musicians" do
+ # expects all the musicians (geocoded)
+ results = Search.musician_filter({score_limit: Search::TEST_SCORE})
+ results.search_type.should == :musicians_filter
+ 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, score_limit: Search::TEST_SCORE})
+ results.search_type.should == :musicians_filter
+ 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, 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, score_limit: Search::TEST_SCORE})
+ results.search_type.should == :musicians_filter
+ results.results.count.should == 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, score_limit: Search::TEST_SCORE})
+ results.search_type.should == :musicians_filter
+ 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, score_limit: Search::TEST_SCORE})
+ results.search_type.should == :musicians_filter
+ results.results.count.should == 0
+ end
+
+ it "sorts musicians by followers" do
+ # establish sorting order
+
+ # @user4
+ f1 = Follow.new
+ f1.user = @user2
+ f1.followable = @user4
+ f1.save
+
+ f2 = Follow.new
+ f2.user = @user3
+ f2.followable = @user4
+ f2.save
+
+ f3 = Follow.new
+ f3.user = @user4
+ f3.followable = @user4
+ f3.save
+
+ # @user3
+ f4 = Follow.new
+ f4.user = @user3
+ f4.followable = @user3
+ f4.save
+
+ f5 = Follow.new
+ f5.user = @user4
+ f5.followable = @user3
+ f5.save
+
+ # @user2
+ f6 = Follow.new
+ f6.user = @user1
+ f6.followable = @user2
+ f6.save
+
+ # @user4.followers.concat([@user2, @user3, @user4])
+ # @user3.followers.concat([@user3, @user4])
+ # @user2.followers.concat([@user1])
+ expect(@user4.followers.count).to be 3
+ expect(Follow.count).to be 6
+
+ # refresh the order to ensure it works right
+ f1 = Follow.new
+ f1.user = @user3
+ f1.followable = @user2
+ f1.save
+
+ f2 = Follow.new
+ f2.user = @user4
+ f2.followable = @user2
+ f2.save
+
+ f3 = Follow.new
+ f3.user = @user2
+ f3.followable = @user2
+ f3.save
+
+ # @user2.followers.concat([@user3, @user4, @user2])
+ 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
+ expect(results.results[0].search_follow_count.to_i).not_to eq(0)
+ # check the follow relationship between current_user and result
+ expect(results.is_follower?(@user2)).to be true
+ end
+
+ it 'paginates properly' do
+ # make sure pagination works right
+ params = {:per_page => 2, :page => 1, score_limit: Search::TEST_SCORE}
+ results = Search.musician_filter(params)
+ expect(results.results.count).to be 2
+ end
+
end
- it "find two for user3" do
- # user1..2 are scored against user3
- ms = Search.new_musicians(@user3, Time.now - 1.week)
- ms.should_not be_nil
- ms.length.should == 2
- ms.should eq [@user2, @user1]
+ def make_recording(usr)
+ connection = FactoryGirl.create(:connection, :user => usr, locidispid: usr.last_jam_locidispid)
+ instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
+ track = FactoryGirl.create(:track, :connection => connection, :instrument => instrument)
+ music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true)
+ # music_session.connections << connection
+ # music_session.save
+ connection.join_the_session(music_session, true, nil, usr, 10)
+ recording = Recording.start(music_session, usr)
+ recording.stop
+ recording.reload
+ genre = FactoryGirl.create(:genre)
+ recording.claim(usr, "name", "description", genre, true)
+ recording.reload
+ recording
end
- it "find one for user4" do
- # user1..2 are scored against user4, but user2 is bad
- ms = Search.new_musicians(@user4, Time.now - 1.week)
- ms.should_not be_nil
- ms.length.should == 1
- ms.should eq [@user1]
+ def make_session(usr)
+ connection = FactoryGirl.create(:connection, :user => usr, locidispid: usr.last_jam_locidispid)
+ music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true)
+ # music_session.connections << connection
+ # music_session.save
+ connection.join_the_session(music_session, true, nil, usr, 10)
end
- it "find none for user5" do
- # user1..4 are not scored against user5
- ms = Search.new_musicians(@user5, Time.now - 1.week)
- ms.should_not be_nil
- ms.length.should == 0
+ context 'musician stat counters' do
+
+ it "displays musicians top followings" do
+ f1 = Follow.new
+ f1.user = @user4
+ f1.followable = @user4
+ f1.save
+
+ f2 = Follow.new
+ f2.user = @user4
+ f2.followable = @user3
+ f2.save
+
+ f3 = Follow.new
+ f3.user = @user4
+ f3.followable = @user2
+ f3.save
+
+ # @user4.followers.concat([@user4])
+ # @user3.followers.concat([@user4])
+ # @user2.followers.concat([@user4])
+ expect(@user4.top_followings.count).to eq 3
+ expect(@user4.top_followings.map(&:id)).to match_array((@musicians - [@user1, @user5, @user6]).map(&:id))
+ end
+
+ it "friends stat shows friend count" do
+ # create friendship record
+ Friendship.save(@user1.id, @user2.id)
+ # search on 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
+ @user1.reload
+ expect(friend.friends?(@user2)).to be true
+ expect(results.is_friend?(@user1)).to be true
+ end
+
+ it "recording stat shows recording count" do
+ Recording.delete_all
+
+ recording = make_recording(@user1)
+ expect(recording.users.length).to be 1
+ expect(recording.users.first).to eq(@user1)
+ @user1.reload
+ expect(@user1.recordings.length).to be 1
+ expect(@user1.recordings.first).to eq(recording)
+ expect(recording.claimed_recordings.length).to be 1
+ expect(@user1.recordings.detect { |rr| rr == recording }).to_not be_nil
+
+ 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
+
+ expect(results.record_count(uu)).to be 1
+ expect(results.session_count(uu)).to be 1
+ end
+
end
+ context 'musician sorting' do
+
+ it "by plays" do
+ Recording.delete_all
+
+ make_recording(@user1)
+ # order results by num recordings
+ 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)
+ expect(results.results[1].id).to eq(@user3.id)
+
+ # add more data and make sure order still correct
+ make_recording(@user3)
+ make_recording(@user3)
+ 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', 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', 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)
+ end
+
+ end
+
+ context 'filter settings' do
+ it "searches musicisns for an instrument" do
+ minst = FactoryGirl.create(:musician_instrument, {
+ :user => @user1,
+ :instrument => Instrument.find('tuba')})
+ @user1.musician_instruments << minst
+ @user1.reload
+ ii = @user1.instruments.detect { |inst| inst.id == 'tuba' }
+ expect(ii).to_not be_nil
+ 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
+ expect(results.results.count).to be 1
+ end
+ end
+
+ context 'new users' do
+
+ it "find three for user1" do
+ # user2..4 are scored against user1
+ ms = Search.new_musicians(@user1, Time.now - 1.week)
+ ms.should_not be_nil
+ ms.length.should == 3
+ ms.should eq [@user2, @user3, @user4]
+ end
+
+ it "find two for user2" do
+ # user1,3,4 are scored against user1, but user4 is bad
+ ms = Search.new_musicians(@user2, Time.now - 1.week)
+ ms.should_not be_nil
+ ms.length.should == 2
+ ms.should eq [@user3, @user1]
+ end
+
+ it "find two for user3" do
+ # user1..2 are scored against user3
+ ms = Search.new_musicians(@user3, Time.now - 1.week)
+ ms.should_not be_nil
+ ms.length.should == 2
+ ms.should eq [@user2, @user1]
+ end
+
+ it "find one for user4" do
+ # user1..2 are scored against user4, but user2 is bad
+ ms = Search.new_musicians(@user4, Time.now - 1.week)
+ ms.should_not be_nil
+ ms.length.should == 1
+ ms.should eq [@user1]
+ end
+
+ it "find none for user5" do
+ # user1..4 are not scored against user5
+ ms = Search.new_musicians(@user5, Time.now - 1.week)
+ ms.should_not be_nil
+ ms.length.should == 0
+ end
+
+ end
end
end
diff --git a/ruby/spec/support/maxmind.rb b/ruby/spec/support/maxmind.rb
index 73b78d663..60a257bcc 100644
--- a/ruby/spec/support/maxmind.rb
+++ b/ruby/spec/support/maxmind.rb
@@ -169,7 +169,7 @@ def ip_from_num(num)
end
def austin_ip
- IPAddr.new(0x0FFFFFFF, Socket::AF_INET).to_s
+ IPAddr.new(austin_ip_as_num, Socket::AF_INET).to_s
end
def austin_ip_as_num
@@ -177,35 +177,64 @@ def austin_ip_as_num
end
def dallas_ip
- IPAddr.new(0x1FFFFFFF, Socket::AF_INET).to_s
+ IPAddr.new(dallas_ip_as_num, Socket::AF_INET).to_s
end
def dallas_ip_as_num
0x1FFFFFFF
end
-# gets related models for an IP in the 1st block from the scores_better_test_data.sql
-def austin_geoip
- geoiplocation = GeoIpLocations.find_by_locid(17192)
- geoipblock = GeoIpBlocks.find_by_locid(17192)
+def houston_ip
+ IPAddr.new(houston_ip_as_num, Socket::AF_INET).to_s
+end
+
+def houston_ip_as_num
+ 0x2FFFFFFF
+end
+
+def miami_ip
+ IPAddr.new(miami_ip_as_num, Socket::AF_INET).to_s
+end
+
+def miami_ip_as_num
+ 0x5FFFFFFF
+end
+
+def seattle_ip
+ IPAddr.new(seattle_ip_as_num, Socket::AF_INET).to_s
+end
+
+def seattle_ip_as_num
+ 0xAFFFFFFF
+end
+
+def create_geoip(locid)
+ geoiplocation = GeoIpLocations.find_by_locid(locid)
+ geoipblock = GeoIpBlocks.find_by_locid(locid)
jamisp = JamIsp.find_by_beginip(geoipblock.beginip)
{jamisp: jamisp, geoiplocation: geoiplocation, geoipblock: geoipblock, locidispid: Score.compute_locidispid(geoiplocation.locid, jamisp.coid)}
end
+# gets related models for an IP in the 1st block from the scores_better_test_data.sql
+def austin_geoip
+ create_geoip(17192)
+end
# gets related models for an IP in the 1st block from the scores_better_test_data.sql
def dallas_geoip
- geoiplocation = GeoIpLocations.find_by_locid(667)
- geoipblock = GeoIpBlocks.find_by_locid(667)
- jamisp = JamIsp.find_by_beginip(geoipblock.beginip)
- {jamisp: jamisp, geoiplocation: geoiplocation, geoipblock: geoipblock, locidispid: Score.compute_locidispid(geoiplocation.locid, jamisp.coid)}
+ create_geoip(667)
end
# gets related models for an IP in the 1st block from the scores_better_test_data.sql
def houston_geoip
- geoiplocation = GeoIpLocations.find_by_locid(30350)
- geoipblock = GeoIpBlocks.find_by_locid(30350)
- jamisp = JamIsp.find_by_beginip(geoipblock.beginip)
- {jamisp: jamisp, geoiplocation: geoiplocation, geoipblock: geoipblock, locidispid: Score.compute_locidispid(geoiplocation.locid, jamisp.coid)}
+ create_geoip(30350)
+end
+
+def miami_geoip
+ create_geoip(23565)
+end
+
+def seattle_geoip
+ create_geoip(1539)
end
# attempts to make the creation of a score more straightforward.
diff --git a/web/Gemfile b/web/Gemfile
index 06aa29212..ad080e413 100644
--- a/web/Gemfile
+++ b/web/Gemfile
@@ -84,11 +84,12 @@ group :development, :test do
gem 'rspec-rails', '2.14.2'
gem "activerecord-import", "~> 0.4.1"
gem 'guard-rspec', '0.5.5'
- gem 'jasmine', '1.3.1'
+# gem 'jasmine', '1.3.1'
gem 'pry'
gem 'execjs', '1.4.0'
gem 'factory_girl_rails', '4.1.0' # in dev because in use by rake task
gem 'database_cleaner', '1.3.0' #in dev because in use by rake task
+ gem 'teaspoon'
end
group :unix do
gem 'therubyracer' #, '0.11.0beta8'
diff --git a/web/app/assets/javascripts/accounts_audio_profile.js b/web/app/assets/javascripts/accounts_audio_profile.js
index b9be0128e..d35a28c5b 100644
--- a/web/app/assets/javascripts/accounts_audio_profile.js
+++ b/web/app/assets/javascripts/accounts_audio_profile.js
@@ -13,6 +13,8 @@
var self = this;
var reloadAudioTimeout = null;
var $root;
+ var $dialog;
+ var $addNewGearBtn;
var userId;
var showingGearWizard = false;
var cancelRescanFunc = null;
@@ -88,7 +90,13 @@
context.JK.guardAgainstBrowser(app, {d1: 'gear'});
}
else {
- populateAccountAudio()
+ var profiles = populateAccountAudio();
+ if(profiles.length == 1) {
+ setTimeout(function() {
+ context.JK.prodBubble($addNewGearBtn, 'no-audio-profiles', {}, {positions: ['bottom'], offsetParent: $addNewGearBtn.closest('.screen')});
+ }, 1000);
+
+ }
}
}
@@ -115,6 +123,8 @@
var template = context._.template($('#template-account-audio').html(), {is_admin: context.JK.currentUserAdmin, profiles: cleansedProfiles}, {variable: 'data'});
appendAudio(template);
+
+ return profiles;
}
function appendAudio(template) {
@@ -202,16 +212,16 @@
function handleStartAudioQualification() {
- if(true) {
- app.afterFtue = function() { showingGearWizard = false; populateAccountAudio() };
- app.cancelFtue = function() { showingGearWizard = false; populateAccountAudio() };
- showingGearWizard = true;
- app.layout.startNewFtue();
- }
- else {
- app.setWizardStep(1);
- app.layout.showDialog('ftue');
- }
+ app.afterFtue = function() {
+ showingGearWizard = false;
+ if(populateAccountAudio().length == 1) {
+ app.layout.showDialog('join-test-session');
+ }
+ };
+ app.cancelFtue = function() { showingGearWizard = false; populateAccountAudio() };
+ showingGearWizard = true;
+ app.layout.startNewFtue();
+
}
function reloadAudio() {
@@ -309,8 +319,11 @@
'beforeShow': beforeShow,
'afterShow': afterShow
};
+
app.bindScreen('account/audio', screenBindings);
+ $dialog = $('#account-audio-profile')
+ $addNewGearBtn = $dialog.find('a[data-purpose=add-profile]');
events();
}
diff --git a/web/app/assets/javascripts/accounts_profile.js b/web/app/assets/javascripts/accounts_profile.js
index d904f5f0e..91e7f2665 100644
--- a/web/app/assets/javascripts/accounts_profile.js
+++ b/web/app/assets/javascripts/accounts_profile.js
@@ -10,6 +10,7 @@
var api = context.JK.Rest();
var userId;
var user = {};
+ var selectLocation = null;
var recentUserDetail = null;
var loadingCitiesData = false;
var loadingRegionsData = false;
@@ -252,28 +253,8 @@
$('#account-profile-content-scroller').on('click', '#account-edit-profile-cancel', function(evt) { evt.stopPropagation(); navToAccount(); return false; } );
$('#account-profile-content-scroller').on('click', '#account-edit-profile-submit', function(evt) { evt.stopPropagation(); handleUpdateProfile(); return false; } );
$('#account-profile-content-scroller').on('submit', '#account-edit-email-form', function(evt) { evt.stopPropagation(); handleUpdateProfile(); return false; } );
- $('#account-profile-content-scroller').on('change', 'select[name=country]', function(evt) { evt.stopPropagation(); handleCountryChanged(); return false; } );
- $('#account-profile-content-scroller').on('change', 'select[name=region]', function(evt) { evt.stopPropagation(); handleRegionChanged(); return false; } );
$('#account-profile-content-scroller').on('click', '#account-change-avatar', function(evt) { evt.stopPropagation(); navToAvatar(); return false; } );
}
-
- function regionListFailure(jqXHR, textStatus, errorThrown) {
- if(jqXHR.status == 422) {
- logger.debug("no regions found for country: " + recentUserDetail.country);
- }
- else {
- app.ajaxError(arguments);
- }
- }
-
- function cityListFailure(jqXHR, textStatus, errorThrown) {
- if(jqXHR.status == 422) {
- logger.debug("no cities found for country/region: " + recentUserDetail.country + "/" + recentUserDetail.state);
- }
- else {
- app.ajaxError(arguments);
- }
- }
function renderAccountProfile() {
$.when( api.getUserDetail(),
@@ -285,43 +266,8 @@
// show page; which can be done quickly at this point
populateAccountProfile(userDetail, instruments);
- var country = userDetail.country;
-
- if(!country) {
- // this case shouldn't happen because sign up makes you pick a location. This is just 'in case', so that the UI is more error-resilient
- country = 'US';
- }
-
- loadingCountriesData = true;
- loadingRegionsData = true;
- loadingCitiesData = true;
-
- // make the 3 slower requests, which only matter if the user wants to affect their ISP or location
-
- api.getCountries()
- .done(function(countriesx) { populateCountriesx(countriesx["countriesx"], userDetail.country); } )
- .fail(app.ajaxError)
- .always(function() { loadingCountriesData = false; })
-
- var country = userDetail.country;
- var state = userDetail.state;
-
- if(country) {
- api.getRegions( { country: country } )
- .done(function(regions) { populateRegions(regions["regions"], userDetail.state); } )
- .fail(regionListFailure)
- .always(function() { loadingRegionsData = false; })
-
- if(state) {
- api.getCities( { country: country, region: state })
- .done(function(cities) {
- populateCities(cities["cities"], userDetail.city)
- })
- .fail(cityListFailure)
- .always(function() { loadingCitiesData = false;})
- }
- }
-
+ selectLocation = new context.JK.SelectLocation(getCountryElement(), getRegionElement(), getCityElement(), app);
+ selectLocation.load(userDetail.country, userDetail.state, userDetail.city)
})
context.JK.dropdown($('select'));
}
diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js
index 61b264f38..ec05b467c 100644
--- a/web/app/assets/javascripts/application.js
+++ b/web/app/assets/javascripts/application.js
@@ -10,6 +10,7 @@
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
+//= require bind-polyfill
//= require jquery
//= require jquery.monkeypatch
//= require jquery_ujs
@@ -34,6 +35,7 @@
//= require jquery.browser
//= require jquery.custom-protocol
//= require jstz
+//= require class
//= require AAC_underscore
//= require AAA_Log
//= require globals
diff --git a/web/app/assets/javascripts/dialog/audioProfileInvalidDialog.js b/web/app/assets/javascripts/dialog/audioProfileInvalidDialog.js
index 11397f7a3..6fb4a5ec4 100644
--- a/web/app/assets/javascripts/dialog/audioProfileInvalidDialog.js
+++ b/web/app/assets/javascripts/dialog/audioProfileInvalidDialog.js
@@ -77,6 +77,13 @@
context.JK.onBackendEvent(ALERT_NAMES.USB_DISCONNECTED, 'audio-profile-invalid-dialog', onUsbDeviceDisconnected);
}
+ function onCancel() {
+ if($btnCancel.is('.disabled')) return false;
+
+ $dialog.data('result', 'cancel');
+ return true;
+ }
+
function beforeHide() {
context.JK.offBackendEvent(ALERT_NAMES.USB_CONNECTED, 'audio-profile-invalid-dialog', onUsbDeviceConnected);
context.JK.offBackendEvent(ALERT_NAMES.USB_DISCONNECTED, 'audio-profile-invalid-dialog', onUsbDeviceDisconnected);
@@ -166,14 +173,6 @@
app.layout.closeDialog('audio-profile-invalid-dialog');
return false;
})
-
- $btnCancel.click(function() {
- if($(this).is('.disabled')) return false;
-
- $dialog.data('result', 'cancel');
- app.layout.closeDialog('audio-profile-invalid-dialog');
- return false;
- })
}
function initialize() {
@@ -182,7 +181,8 @@
'beforeShow': beforeShow,
'afterShow' : afterShow,
'beforeHide' : beforeHide,
- 'afterHide': afterHide
+ 'afterHide': afterHide,
+ 'onCancel' : onCancel
};
app.bindDialog('audio-profile-invalid-dialog', dialogBindings);
diff --git a/web/app/assets/javascripts/dialog/changeSearchLocationDialog.js b/web/app/assets/javascripts/dialog/changeSearchLocationDialog.js
new file mode 100644
index 000000000..f28786ad7
--- /dev/null
+++ b/web/app/assets/javascripts/dialog/changeSearchLocationDialog.js
@@ -0,0 +1,116 @@
+(function (context, $) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.ChangeSearchLocationDialog = function (app) {
+ var logger = context.JK.logger;
+ var rest = context.JK.Rest();
+ var $dialog = null;
+ var initialized = false;
+ var $countries = null;
+ var $regions = null;
+ var $cities = null;
+ var selectLocation = null;
+ var $resetLocation = null;
+ var $btnSave = null;
+
+ function getSelection() {
+ var countryVal = $countries.val();
+ var regionVal = $regions.val();
+ var cityVal = $cities.val();
+
+ if(countryVal && regionVal && cityVal) {
+ // if any bit of info is not set, then null out info; user didn't pick a full set of data
+ var $country = $($countries).find('option:selected');
+ var $region = $($regions).find('option:selected');
+
+ return {
+ countrycode : countryVal,
+ regioncode : regionVal,
+ city: cityVal,
+ country: $country.text(),
+ region: $region.text()
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ function events() {
+ $btnSave.click(function() {
+
+ var selection = getSelection();
+
+ app.user().done(function(user) {
+ if(selection != null &&
+ selection.countrycode == user.country &&
+ selection.regioncode == user.state &&
+ selection.city == user.city) {
+ // if this is the same location on the user's account, suppress selection
+ selection = null;
+ }
+
+ $dialog.data('result', selection);
+ app.layout.closeDialog('change-search-location')
+ })
+
+ return false;
+ })
+
+ $resetLocation.click(function() {
+ app.user().done(function(user) {
+ selectLocation.load(user.country, user.state, user.city);
+ })
+ return false;
+ })
+ }
+
+ function beforeShow() {
+ $dialog.data('result', null);
+
+ if(!initialized) {
+ initialized = true;
+ app.user().done(function(user) {
+ selectLocation = new context.JK.SelectLocation($countries, $regions, $cities, app);
+ selectLocation.load(user.country, user.state, user.city);
+ })
+ }
+ else {
+
+ }
+ }
+
+ function beforeHide() {
+
+ }
+
+ function getSelectedLocation() {
+ return searchLocation;
+ }
+
+ function initialize() {
+ var dialogBindings = {
+ 'beforeShow': beforeShow,
+ 'beforeHide': beforeHide
+ };
+
+ app.bindDialog('change-search-location', dialogBindings);
+
+ $dialog = $('#change-search-location-dialog');
+ $countries = $dialog.find('select[name="country"]')
+ $regions = $dialog.find('select[name="region"]')
+ $cities = $dialog.find('select[name="city"]')
+ $resetLocation = $dialog.find('.reset-location');
+ $btnSave = $dialog.find('.btnSave')
+
+ events();
+ };
+
+ this.initialize = initialize;
+ this.getSelectedLocation = getSelectedLocation;
+ }
+
+ return this;
+})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js
index d7943734f..f89efe96b 100644
--- a/web/app/assets/javascripts/dialog/configureTrackDialog.js
+++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js
@@ -119,13 +119,10 @@
});
$btnCancel.click(function() {
- if(voiceChatHelper.cancel()) {
- app.layout.closeDialog('configure-tracks')
- }
+ app.layout.cancelDialog('configure-tracks');
return false;
});
-
//$btnAddNewGear.click(function() {
// return false;
@@ -206,10 +203,14 @@
function afterShow() {
sessionUtils.SessionPageEnter();
}
+
+ function onCancel() {
+ return voiceChatHelper.cancel();
+ }
+
function afterHide() {
voiceChatHelper.beforeHide();
sessionUtils.SessionPageLeave();
-
}
function initialize() {
@@ -217,7 +218,8 @@
var dialogBindings = {
'beforeShow' : beforeShow,
'afterShow' : afterShow,
- 'afterHide': afterHide
+ 'afterHide': afterHide,
+ 'onCancel' : onCancel
};
app.bindDialog('configure-tracks', dialogBindings);
diff --git a/web/app/assets/javascripts/dialog/editRecordingDialog.js b/web/app/assets/javascripts/dialog/editRecordingDialog.js
index 23c1d4919..9aac9d33a 100644
--- a/web/app/assets/javascripts/dialog/editRecordingDialog.js
+++ b/web/app/assets/javascripts/dialog/editRecordingDialog.js
@@ -12,7 +12,6 @@
var $description = null;
var $genre = null;
var $isPublic = null;
- var $cancelBtn = null;
var $saveBtn = null;
var $deleteBtn = null;
@@ -129,14 +128,9 @@
})
}
- function cancel() {
- app.layout.closeDialog('edit-recording');
- }
-
function events() {
$saveBtn.click(attemptUpdate);
$deleteBtn.click(attemptDelete);
- $cancelBtn.click(cancel)
$form.submit(false);
}
@@ -151,7 +145,6 @@
$dialog = $('#edit-recording-dialog');
$form = $dialog.find('form');
- $cancelBtn = $dialog.find('.cancel-btn');
$saveBtn = $dialog.find('.save-btn');
$deleteBtn = $dialog.find('.delete-btn');
$name = $dialog.find('input[name="name"]');
diff --git a/web/app/assets/javascripts/dialog/gettingStartedDialog.js b/web/app/assets/javascripts/dialog/gettingStartedDialog.js
new file mode 100644
index 000000000..ff93b8fc7
--- /dev/null
+++ b/web/app/assets/javascripts/dialog/gettingStartedDialog.js
@@ -0,0 +1,79 @@
+(function (context, $) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.GettingStartedDialog = function (app) {
+ var logger = context.JK.logger;
+ var rest = context.JK.Rest();
+ var invitationDialog = null;
+ var $dialog = null;
+ var $dontShowAgain = null;
+ var $setupGearBtn = null;
+
+
+ function registerEvents() {
+
+ $setupGearBtn.click(function() {
+ if (gon.isNativeClient) {
+ app.layout.closeDialog('getting-started');
+ window.location = '/client#/account/audio'
+ }
+ else {
+ context.JK.guardAgainstBrowser(app, {d1: 'gear'});
+ }
+ return false;
+ })
+
+ $('#getting-started-dialog a.facebook-invite').on('click', function (e) {
+ invitationDialog.showFacebookDialog(e);
+ });
+
+ $('#getting-started-dialog a.google-invite').on('click', function (e) {
+ invitationDialog.showGoogleDialog();
+ });
+
+ $('#getting-started-dialog a.email-invite').on('click', function (e) {
+ invitationDialog.showEmailDialog();
+ });
+ }
+
+ function beforeShow() {
+ }
+
+ function beforeHide() {
+
+ if ($dontShowAgain.is(':checked')) {
+ app.updateUserModel({show_whats_next: false})
+ }
+ }
+
+ function initializeButtons() {
+
+ context.JK.checkbox($dontShowAgain);
+ }
+
+ function initialize(invitationDialogInstance) {
+ var dialogBindings = {
+ 'beforeShow': beforeShow,
+ 'beforeHide': beforeHide
+ };
+
+ app.bindDialog('getting-started', dialogBindings);
+
+ $dialog = $('#getting-started-dialog');
+ $dontShowAgain = $dialog.find('#show_getting_started');
+ $setupGearBtn = $dialog.find('.setup-gear-btn')
+
+ registerEvents();
+
+ invitationDialog = invitationDialogInstance;
+
+ initializeButtons();
+ };
+
+
+ this.initialize = initialize;
+ }
+
+ return this;
+})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/dialog/joinTestSession.js b/web/app/assets/javascripts/dialog/joinTestSession.js
new file mode 100644
index 000000000..89d0b0df6
--- /dev/null
+++ b/web/app/assets/javascripts/dialog/joinTestSession.js
@@ -0,0 +1,113 @@
+(function (context, $) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.JoinTestSessionDialog = function (app) {
+ var logger = context.JK.logger;
+ var rest = context.JK.Rest();
+ var $dialog = null;
+ var $joinTestSessionBtn = null;
+
+ function joinSession(sessionId) {
+ app.layout.closeDialog('join-test-session')
+ $joinTestSessionBtn.removeClass('disabled');
+ context.JK.GA.trackSessionCount(true, true, 0);
+
+ // we redirect to the session screen, which handles the REST call to POST /participants.
+ logger.debug("joining session screen: " + sessionId)
+ context.location = '/client#/session/' + sessionId;
+ };
+
+ function createSession(sessionId) {
+ var data = {};
+
+ data.name = 'First Session';
+ data.description = 'This is my first ever session on JamKazam, so please be gentle. :)';
+ data.genres = ['other']
+ data.musician_access = true;
+ data.approval_required = false;
+ data.fan_access = true;
+ data.fan_chat = true;
+ data.legal_policy = 'Standard'
+ data.legal_terms = true;
+ data.language = 'eng';
+ data.start = new Date().toDateString() + ' ' + context.JK.formatUtcTime(new Date(), false);
+ data.duration = "60";
+ data.recurring_mode = 'once'
+ data.music_notations = [];
+ data.timezone = 'Central Time (US & Canada),America/Chicago'
+ data.open_rsvps = true
+ data.rsvp_slots = [];
+
+ // auto pick an 'other' instrument
+ var otherId = context.JK.server_to_client_instrument_map.Other.server_id; // get server ID
+ var otherInstrumentInfo = context.JK.instrument_id_to_instrument[otherId]; // get display name
+ var beginnerLevel = 1; // default to beginner
+ var instruments = [ {id: otherId, name: otherInstrumentInfo.display, level: beginnerLevel} ];
+ $.each(instruments, function(index, instrument) {
+ var slot = {};
+ slot.instrument_id = instrument.id;
+ slot.proficiency_level = instrument.level;
+ slot.approve = true;
+ data.rsvp_slots.push(slot);
+ });
+
+ data.isUnstructuredRsvp = true;
+
+ rest.createScheduledSession(data)
+ .done(function(response) {
+ logger.debug("created test session on server");
+ //$('#create-session-buttons .btn-next').off('click');
+ var newSessionId = response.id;
+
+ joinSession(newSessionId);
+ })
+ .fail(function(jqXHR){
+ $joinTestSessionBtn.removeClass('disabled');
+ logger.debug("unable to schedule a test session")
+ app.notifyServerError(jqXHR, "Unable to schedule a test session");
+ })
+ }
+
+ function registerEvents() {
+
+ $joinTestSessionBtn.click(function() {
+
+ if($joinTestSessionBtn.is('.disabled')) return false;
+
+ $joinTestSessionBtn.addClass('disabled');
+
+ createSession();
+
+ return false;
+ })
+ }
+
+ function beforeShow() {
+ $joinTestSessionBtn.removeClass('disabled');
+ }
+
+ function beforeHide() {
+
+ }
+
+ function initialize() {
+ var dialogBindings = {
+ 'beforeShow': beforeShow,
+ 'beforeHide': beforeHide
+ };
+
+ app.bindDialog('join-test-session', dialogBindings);
+
+ $dialog = $('#join-test-session-dialog');
+ $joinTestSessionBtn = $dialog.find('.join-test-session')
+
+ registerEvents();
+ };
+
+
+ this.initialize = initialize;
+ }
+
+ return this;
+})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/dialog/networkTestDialog.js b/web/app/assets/javascripts/dialog/networkTestDialog.js
index ad00388fb..5d64ceb64 100644
--- a/web/app/assets/javascripts/dialog/networkTestDialog.js
+++ b/web/app/assets/javascripts/dialog/networkTestDialog.js
@@ -7,7 +7,6 @@
var logger = context.JK.logger;
var $dialog = null;
- var $btnCancel = null;
var $btnClose = null;
var $btnHelp = null;
var networkTest = new context.JK.NetworkTest(app);
@@ -37,13 +36,10 @@
$btnClose.removeClass('button-grey').addClass('button-orange');
}
+ function onCancel() {
+ // should we stop this if the test is going?
+ }
function events() {
- $btnCancel.on('click', function() {
- // should we stop this if the test is going?
- app.layout.closeDialog('network-test')
- return false;
- })
-
$btnClose.on('click', function(e) {
if(!networkTest.isScoring()) {
app.layout.closeDialog('network-test');
@@ -77,7 +73,6 @@
app.bindDialog('network-test', dialogBindings);
$dialog = $('#network-test-dialog');
- $btnCancel = $dialog.find('.btn-cancel');
$btnHelp = $dialog.find('.btn-help');
$btnClose = $dialog.find('.btn-close');
diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js
index b3272d0c6..e90049966 100644
--- a/web/app/assets/javascripts/everywhere/everywhere.js
+++ b/web/app/assets/javascripts/everywhere/everywhere.js
@@ -46,6 +46,8 @@
initializeStun(app);
operationalEvents(app);
+
+ handleGettingStarted(app);
});
function watchPreferencesEvent(app) {
@@ -107,8 +109,11 @@
JK.UserDropdown = userDropdown;
userDropdown.initialize(invitationDialog);
- var whatsNextDialog = new JK.WhatsNextDialog(app);
- whatsNextDialog.initialize(invitationDialog);
+ var gettingStartedDialog = new JK.GettingStartedDialog(app);
+ gettingStartedDialog.initialize(invitationDialog);
+
+ var joinTestSessionDialog = new JK.JoinTestSessionDialog(app);
+ joinTestSessionDialog.initialize();
var videoDialog = new JK.VideoDialog(app);
videoDialog.initialize();
@@ -167,7 +172,18 @@
JK.JamServer.registerMessageCallback(JK.MessageType.STOP_APPLICATION, function(header, payload) {
context.jamClient.ShutdownApplication();
});
+ }
+ function handleGettingStarted(app) {
+ app.user()
+ .done(function(userProfile) {
+ if (userProfile.show_whats_next &&
+ window.location.pathname.indexOf(gon.client_path) == 0 &&
+ !app.layout.isDialogShowing('getting-started'))
+ {
+ app.layout.showDialog('getting-started');
+ }
+ })
}
})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/findMusician.js b/web/app/assets/javascripts/findMusician.js
index b46e1aaaa..c900cbf2a 100644
--- a/web/app/assets/javascripts/findMusician.js
+++ b/web/app/assets/javascripts/findMusician.js
@@ -6,6 +6,7 @@
var logger = context.JK.logger;
var rest = context.JK.Rest();
+ var EVENTS = context.JK.EVENTS;
var musicians = {};
var musicianList;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
@@ -16,60 +17,139 @@
var $results = null;
var $spinner = null;
var $endMusicianList = null;
+ var $templateAccountSessionLatency = null;
var helpBubble = context.JK.HelpBubbleHelper;
var sessionUtils = context.JK.SessionUtils;
-
+ var $musicianSearchCity = null;
+ var $musicianFilterCity = null;
+ var $musicianChangeFilterCity = null;
+ var $musicianQueryScore = null;
+ var $musicianDistance = null;
+ var $musicianLatencyOrDistanceLabel = null;
+ var $refreshBtn = null;
+ var searchLocationOverride = null;
+ var currentRequest = null;
function search() {
- $spinner.show();
- did_show_musician_page = true;
- var queryString = 'srch_m=1&page=' + page_num + '&';
+ var options = {
+ srch_m: 1,
+ page: page_num
+ }
// order by
var orderby = $('#musician_order_by').val();
if (typeof orderby != 'undefined' && orderby.length > 0) {
- queryString += "orderby=" + orderby + '&';
+ options['orderby'] = orderby;
}
// instrument filter
var instrument = $('#musician_instrument').val();
if (typeof instrument != 'undefined' && !(instrument === '')) {
- queryString += "instrument=" + instrument + '&';
+ options['instrument'] = instrument;
}
// score filter
- var query_param = $('#musician_query_score').val();
+ var query_param = $musicianQueryScore.val();
if (query_param !== null) {
- queryString += "score_limit=" + query_param + '&';
+ options['score_limit'] = query_param;
}
- rest.searchMusicians(queryString)
+
+ var distance = $musicianDistance.val();
+ if (distance !== null) {
+ options['distance'] = distance;
+ }
+
+ if(searchLocationOverride) {
+ options['country'] = searchLocationOverride.countrycode;
+ options['region'] = searchLocationOverride.regioncode;
+ options['city'] = searchLocationOverride.city;
+ }
+
+ $spinner.show();
+ $refreshBtn.addClass('disabled')
+ currentRequest = rest.searchMusicians(options)
.done(afterLoadMusicians)
- .fail(app.ajaxError)
- .always(function() {$spinner.hide()})
+ .fail(function(jqXHR) {
+ if(jqXHR.status === 0 && jqXHR.statusText === 'abort') {
+ // do nothing
+ }
+ else {
+ app.ajaxError(arguments);
+ }
+ })
+ .always(function() {currentRequest = null; $spinner.hide(); $refreshBtn.removeClass('disabled')})
+ }
+
+ function abortCurrentRequest() {
+ if(currentRequest) {
+ currentRequest.abort()
+ currentRequest = null;
+ $spinner.hide();
+ $refreshBtn.removeClass('disabled')
+ }
}
function refreshDisplay() {
+ abortCurrentRequest();
clearResults();
+ setupSearch();
search();
}
+ // user clicked refresh
+ function manualRefresh() {
+ if(!$refreshBtn.is('.disabled')) {
+ refreshDisplay();
+ }
+ return false;
+ }
+
+ function changeSearchLocation() {
+ app.layout.showDialog('change-search-location').one(EVENTS.DIALOG_CLOSED, function(e, data) {
+ if(data.canceled) {
+ // do nothing
+ }
+ else {
+ searchLocationOverride = data.result;
+ displayUserCity();
+ refreshDisplay();
+ }
+ })
+
+ return false;
+ }
+
+ function displayUserCity() {
+ app.user().done(function(user) {
+ var location = searchLocationOverride || user.location;
+ var unknown = 'unknown location';
+ var result = unknown;
+ if(location) {
+ var region = location['region'] ? location['region'] : location['regioncode']
+ if(!region) { region = 'n/a'; }
+ var city = location['city'];
+ result = city + ', ' + region;
+ }
+ $musicianFilterCity.text(result);
+ })
+ }
+
function afterLoadMusicians(mList) {
+ did_show_musician_page = true;
+
// display the 'no musicians' banner if appropriate
var $noMusiciansFound = $('#musicians-none-found');
musicianList = mList;
-
- // @FIXME: This needs to pivot on musicianList.musicians.length
- if (musicianList.length == 0) {
+ if (musicianList.musicians.length == 0) {
$noMusiciansFound.show();
musicians = [];
}
else {
$noMusiciansFound.hide();
- musicians = musicianList['musicians'];
+ musicians = musicianList.musicians;
if (!(typeof musicians === 'undefined')) {
- $('#musician-filter-city').text(musicianList['city']);
- if (0 == page_count) {
+ if (-1 == page_count) {
page_count = musicianList['page_count'];
}
renderMusicians();
@@ -102,7 +182,6 @@
function renderMusicians() {
var ii, len;
var mTemplate = $('#template-find-musician-row').html();
- var fTemplate = $('#template-musician-follow-info').html();
var aTemplate = $('#template-musician-action-btns').html();
var mVals, musician, renderings = '';
var instr_logos, instr;
@@ -122,19 +201,6 @@
}
instr_logos += '';
}
- follows = '';
- followVals = {};
- for (var jj = 0, ilen = musician['followings'].length; jj < ilen; jj++) {
- aFollow = musician['followings'][jj];
- followVals = {
- user_id: aFollow.user_id,
- musician_name: aFollow.name,
- profile_url: '/client#/profile/' + aFollow.user_id,
- avatar_url: context.JK.resolveAvatarUrl(aFollow.photo_url)
- };
- follows += context.JK.fillTemplate(fTemplate, followVals);
- if (2 == jj) break;
- }
var actionVals = {
profile_url: "/client#/profile/" + musician.id,
friend_class: 'button-' + (musician['is_friend'] ? 'grey' : 'orange'),
@@ -147,9 +213,12 @@
};
var musician_actions = context.JK.fillTemplate(aTemplate, actionVals);
- var full_score = musician['full_score'];
+ var latencyBadge = context._.template(
+ $templateAccountSessionLatency.html(),
+ $.extend(sessionUtils.createLatency(musician), musician),
+ {variable: 'data'}
+ );
- var scoreInfo = sessionUtils.scoreInfo(full_score, false)
mVals = {
avatar_url: context.JK.resolveAvatarUrl(musician.photo_url),
profile_url: "/client#/profile/" + musician.id,
@@ -162,12 +231,9 @@
recording_count: musician['recording_count'],
session_count: musician['session_count'],
musician_id: musician['id'],
- musician_follow_template: follows,
musician_action_template: musician_actions,
- musician_one_way_score: score_to_text(full_score),
- musician_score_color: scoreInfo.icon_name,
- musician_score_color_alt: scoreInfo.description,
- latency_style: scoreInfo.latency_style
+ latency_badge: latencyBadge,
+ musician_first_name: musician['first_name']
};
var $rendering = $(context.JK.fillTemplate(mTemplate, mVals))
@@ -178,9 +244,10 @@
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);
- helpBubble.scoreBreakdown($('.score-count', $rendering), false, full_score, myAudioLatency, musician['audio_latency'], musician['score'], scoreOptions);
+ helpBubble.scoreBreakdown($('.latency', $rendering), false, musician['full_score'], myAudioLatency, musician['audio_latency'], musician['score'], scoreOptions);
$results.append($rendering);
+ $rendering.find('.biography').dotdotdot();
}
@@ -195,7 +262,10 @@
}
function afterShow(data) {
- refreshDisplay();
+ if(!did_show_musician_page) {
+ // cache page because query is slow, and user may have paginated a bunch
+ refreshDisplay();
+ }
}
function clearResults() {
@@ -203,7 +273,23 @@
$('#musician-filter-results .musician-list-result').remove();
$endMusicianList.hide();
page_num = 1;
- page_count = 0;
+ page_count = -1;
+ }
+
+ function setupSearch() {
+ var orderby = $('#musician_order_by').val();
+ if(orderby == 'distance') {
+ $musicianSearchCity.show();
+ $musicianDistance.closest('.easydropdown-wrapper').show();
+ $musicianQueryScore.closest('.easydropdown-wrapper').hide();
+ $musicianLatencyOrDistanceLabel.text('Distance:')
+ }
+ else {
+ $musicianSearchCity.hide();
+ $musicianDistance.closest('.easydropdown-wrapper').hide();
+ $musicianQueryScore.closest('.easydropdown-wrapper').show();
+ $musicianLatencyOrDistanceLabel.text('Latency:')
+ }
}
function friendMusician(evt) {
@@ -267,11 +353,18 @@
function events() {
- $('#musician_query_score').change(refreshDisplay);
+ $musicianQueryScore.change(refreshDisplay);
$('#musician_instrument').change(refreshDisplay);
$('#musician_order_by').change(refreshDisplay);
+ $musicianDistance.change(refreshDisplay);
+ $musicianChangeFilterCity.click(changeSearchLocation);
+ $refreshBtn.click(manualRefresh)
$('#musician-filter-results').closest('.content-body-scroller').bind('scroll', function () {
+
+ // do not check scroll when filling out initial results, which we can tell if page_count == -1
+ if(page_count == -1) {return};
+
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
if (page_num < page_count) {
page_num += 1;
@@ -298,8 +391,17 @@
$results = $screen.find('#musician-filter-results');
$spinner = $screen.find('.paginate-wait')
$endMusicianList = $screen.find('#end-of-musician-list')
-
+ $templateAccountSessionLatency = $("#template-account-session-latency")
+ $musicianSearchCity = $('#musician-search-city');
+ $musicianFilterCity = $('#musician-filter-city');
+ $musicianChangeFilterCity = $('#musician-change-filter-city');
+ $musicianQueryScore = $('#musician_query_score');
+ $musicianDistance = $('#musician_distance');
+ $musicianLatencyOrDistanceLabel = $screen.find('.latency-or-distance')
+ $refreshBtn = $screen.find('.btn-refresh-entries');
events();
+ setupSearch();
+ displayUserCity();
}
this.initialize = initialize;
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 936ecd116..08c153eb8 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -1189,13 +1189,10 @@
});
}
- function searchMusicians(queryString) {
- // squelch nulls and undefines
- queryString = !!queryString ? queryString : "";
-
+ function searchMusicians(query) {
return $.ajax({
type: "GET",
- url: "/api/search.json?" + queryString
+ url: "/api/search.json?" + $.param(query)
});
}
diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js
index 046fffa2a..08d79d34a 100644
--- a/web/app/assets/javascripts/jamkazam.js
+++ b/web/app/assets/javascripts/jamkazam.js
@@ -294,6 +294,16 @@
context.location = url;
}
+ this.updateUserModel = function(userUpdateData) {
+
+ var update = rest.updateUser(userUpdateData)
+ update.done(function() {
+ logger.debug("updating user info")
+ userDeferred = update; // update the global user object if this succeeded
+ })
+ return update;
+ }
+
// call .done/.fail on this to wait for safe user data
this.user = function() {
return userDeferred;
diff --git a/web/app/assets/javascripts/landing/landing.js b/web/app/assets/javascripts/landing/landing.js
index caf518ddb..fe9765444 100644
--- a/web/app/assets/javascripts/landing/landing.js
+++ b/web/app/assets/javascripts/landing/landing.js
@@ -1,3 +1,4 @@
+//= require bind-polyfill
//= require jquery
//= require jquery.monkeypatch
//= require jquery_ujs
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index ea161f7e3..6abce7e89 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -427,15 +427,40 @@
return false;
}
- function closeDialog(dialog) {
+ function cancel(evt) {
+ var $target = $(evt.currentTarget).closest('[layout]');
+ var layoutId = $target.attr('layout-id');
+ var isDialog = ($target.attr('layout') === 'dialog');
+ if (isDialog) {
+ cancelDialog(layoutId);
+ } else {
+ // ?
+ logger.warn("unable to handle cancel layout-action for %o", $target)
+ }
+ return false;
+ }
+
+ function cancelDialog(dialog) {
+ logger.debug("cancelling dialog: " + dialog);
+ var $dialog = $('[layout-id="' + dialog + '"]');
+ var result = dialogEvent(dialog, 'onCancel');
+ if(result !== false) {
+ closeDialog(dialog, true);
+ }
+ else {
+ logger.debug("dialog refused cancel");
+ }
+ }
+
+ function closeDialog(dialog, canceled) {
logger.debug("closing dialog: " + dialog);
var $dialog = $('[layout-id="' + dialog + '"]');
dialogEvent(dialog, 'beforeHide');
var $overlay = $('.dialog-overlay');
unstackDialogs($overlay);
$dialog.hide();
- $dialog.triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length, result: $dialog.data('result') });
- $(context).triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length, result: $dialog.data('result') });
+ $dialog.triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length, result: $dialog.data('result'), canceled: canceled });
+ $(context).triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length, result: $dialog.data('result'), canceled: canceled });
dialogEvent(dialog, 'afterHide');
}
@@ -550,8 +575,23 @@
}
}
+ // if no arguments passed, then see if any dialog is showing
+ // if string passed, see if dialog is showing (even if buried) of a given name
function isDialogShowing() {
- return openDialogs.length > 0;
+ if(arguments.length == 1) {
+ // user passed in dialog id
+ var dialogId = arguments[0];
+ context._.each(openDialogs, function(dialog) {
+ if($(dialog).attr('layout-id') == dialogId) {
+ return true;
+ }
+ })
+ return false;
+ }
+ else {
+ // user passed in nothing
+ return openDialogs.length > 0;
+ }
}
function currentDialog() {
@@ -690,7 +730,7 @@
// var step = 0;
//setWizardStep(step);
//wizardShowFunctions[step]();
- showDialog('gear-wizard');
+ return showDialog('gear-wizard');
}
function setWizardStep(targetStepId) {
@@ -718,6 +758,22 @@
context.JK.GA.virtualPageView(location.pathname + location.search + location.hash);
}
+ function onHandleKey(e) {
+
+ if (e.keyCode == 27 /** esc */)
+ {
+ if(isDialogShowing()) {
+ var $dialog = currentDialog();
+ if(!$dialog) {
+ logger.error("unable to find current dialog on ESC");
+ return;
+ }
+
+ cancelDialog($dialog.attr('layout-id'));
+ }
+ }
+ }
+
function handleDialogState() {
var rawDialogState = $.cookie('dialog_state');
try {
@@ -756,11 +812,13 @@
});
$('body').on('click', '[layout-link]', linkClicked);
$('[layout-action="close"]').on('click', close);
+ $('[layout-action="cancel"]').on('click', cancel);
$('[layout-sidebar-expander]').on('click', toggleSidebar);
$('[layout-panel="expanded"] [layout-panel="header"]').on('click', panelHeaderClicked);
$('[layout-wizard-link]').on('click', wizardLinkClicked);
$('[tab-target]').on('click', tabClicked);
$(context).on('hashchange', trackLocationChange);
+ $(document).keyup(onHandleKey);
}
// public functions
@@ -960,6 +1018,7 @@
}
this.closeDialog = closeDialog;
+ this.cancelDialog = cancelDialog;
this.handleDialogState = handleDialogState;
this.queueDialog = queueDialog;
diff --git a/web/app/assets/javascripts/selectLocation.js b/web/app/assets/javascripts/selectLocation.js
new file mode 100644
index 000000000..4d5b36711
--- /dev/null
+++ b/web/app/assets/javascripts/selectLocation.js
@@ -0,0 +1,271 @@
+(function (context, $) {
+
+ "use strict";
+
+ context.JK = context.JK || {};
+
+ context.JK.SelectLocation = Class.extend({
+
+ init: function ($countries, $regions, $cities, app) {
+ this.api = context.JK.Rest();
+ this.logger = context.JK.logger;
+ this.loadingCitiesData = false;
+ this.loadingRegionsData = false;
+ this.loadingCountriesData = false;
+ this.nilOptionStr = '';
+ this.nilOptionText = 'n/a';
+ this.$countries = $countries;
+ this.$regions = $regions;
+ this.$cities = $cities;
+ this.app = app;
+
+ $countries.on('change', function (evt) {
+ evt.stopPropagation();
+ this.handleCountryChanged();
+ return false;
+ }.bind(this));
+ $regions.on('change', function (evt) {
+ evt.stopPropagation();
+ this.handleRegionChanged();
+ return false;
+ }.bind(this));
+ },
+ load: function (country, region, city) {
+
+ this.country = country;
+ this.region = region;
+ this.city = city;
+
+ if (!country) {
+ // this case shouldn't happen because sign up makes you pick a location. This is just 'in case', so that the UI is more error-resilient
+ this.logger.debug("user has no specified country: " + country)
+ country = 'US';
+ }
+
+ this.loadingCountriesData = true;
+ this.loadingRegionsData = true;
+ this.loadingCitiesData = true;
+
+ // make the 3 slower requests, which only matter if the user wants to affect their ISP or location
+
+ this.api.getCountries()
+ .done(function (countriesx) {
+ this.populateCountriesx(countriesx["countriesx"], country);
+ }.bind(this))
+ .fail(this.app.ajaxError)
+ .always(function () {
+ this.loadingCountriesData = false;
+ }.bind(this))
+
+ if (country) {
+ this.api.getRegions({ country: country })
+ .done(function (regions) {
+ this.populateRegions(regions["regions"], region);
+ }.bind(this))
+ .fail(this.regionListFailure.bind(this))
+ .always(function () {
+ this.loadingRegionsData = false;
+ }.bind(this))
+
+ if (region) {
+ this.api.getCities({ country: country, region: region })
+ .done(function (cities) {
+ this.populateCities(cities["cities"], this.city)
+ }.bind(this))
+ .fail(this.cityListFailure.bind(this))
+ .always(function () {
+ this.loadingCitiesData = false;
+ }.bind(this))
+ }
+ }
+ },
+ handleCountryChanged: function () {
+ var selectedCountry = this.$countries.val()
+ var selectedRegion = this.$regions.val()
+ var cityElement = this.$cities
+
+ this.updateRegionList(selectedCountry, this.$regions);
+ this.updateCityList(selectedCountry, null, cityElement);
+ },
+ handleRegionChanged: function () {
+ var selectedCountry = this.$countries.val()
+ var selectedRegion = this.$regions.val()
+ var cityElement = this.$cities;
+
+ this.updateCityList(selectedCountry, selectedRegion, cityElement);
+ },
+ updateRegionList: function (selectedCountry, regionElement) {
+ // only update region
+ if (selectedCountry) {
+ // set city disabled while updating
+ regionElement.attr('disabled', true).easyDropDown('disable');
+ this.loadingRegionsData = true;
+
+ regionElement.children().remove()
+ regionElement.append($(this.nilOptionStr).text('loading...'))
+
+ this.api.getRegions({ country: selectedCountry })
+ .done(this.getRegionsDone.bind(this))
+ .error(function (err) {
+ regionElement.children().remove()
+ regionElement.append($(this.nilOptionStr).text(this.nilOptionText))
+ }.bind(this))
+ .always(function () {
+ console.log("regions load: this.loadingRegionsData; " + this.loadingRegionsData)
+ this.loadingRegionsData = false;
+ }.bind(this))
+ }
+ else {
+ regionElement.children().remove()
+ regionElement.append($(this.nilOptionStr).text(this.nilOptionText))
+ }
+ },
+ updateCityList: function (selectedCountry, selectedRegion, cityElement) {
+
+ // only update cities
+ if (selectedCountry && selectedRegion) {
+ // set city disabled while updating
+ cityElement.attr('disabled', true).easyDropDown('disable');
+ this.loadingCitiesData = true;
+
+ cityElement.children().remove()
+ cityElement.append($(this.nilOptionStr).text('loading...'))
+
+ this.api.getCities({ country: selectedCountry, region: selectedRegion })
+ .done(this.getCitiesDone.bind(this))
+ .error(function (err) {
+ cityElement.children().remove()
+ cityElement.append($(this.nilOptionStr).text(this.nilOptionText))
+ }.bind(this))
+ .always(function () {
+ this.loadingCitiesData = false;
+ }.bind(this))
+ }
+ else {
+ cityElement.children().remove();
+ cityElement.append($(this.nilOptionStr).text(this.nilOptionText));
+ context.JK.dropdown(cityElement);
+ }
+ },
+
+ getCitiesDone: function (data) {
+ this.populateCities(data['cities'], this.city);
+ },
+ getRegionsDone: function (data) {
+ this.populateRegions(data['regions'], this.region);
+ this.updateCityList(this.$countries.val(), this.$regions.val(), this.$cities);
+ },
+ writeCountry: function (index, countryx) {
+ if (!countryx.countrycode) return;
+
+ var option = $(this.nilOptionStr);
+ option.text(countryx.countryname);
+ option.attr("value", countryx.countrycode);
+
+ if (countryx.countrycode == this.country) {
+ this.foundCountry = true;
+ }
+
+ this.$countries.append(option);
+ },
+ populateCountriesx: function (countriesx) {
+
+ // countriesx has the format [{countrycode: "US", countryname: "United States"}, ...]
+
+ this.foundCountry = false;
+ this.$countries.children().remove();
+
+ var nilOption = $(this.nilOptionStr);
+ nilOption.text(this.nilOptionText);
+ this.$countries.append(nilOption);
+
+ $.each(countriesx, this.writeCountry.bind(this));
+
+ if (!this.foundCountry) {
+ this.logger.warn("user has no country in the database. user's country:" + this.country)
+ // in this case, the user has a country that is not in the database
+ // this can happen in a development/test scenario, but let's assume it can
+ // happen in production too.
+ var option = $(this.nilOptionStr);
+ option.text(this.country);
+ option.attr("value", this.country);
+ this.$countries.append(option);
+ }
+
+ this.$countries.val(this.country);
+ this.$countries.attr("disabled", null).easyDropDown('enable');
+
+ context.JK.dropdown(this.$countries);
+ },
+
+ writeRegion: function (index, region) {
+ if (!region) return;
+
+ var option = $(this.nilOptionStr)
+ option.text(region['name'])
+ option.attr("value", region['region'])
+
+ this.$regions.append(option)
+ },
+
+ populateRegions: function (regions, userRegion) {
+ this.$regions.children().remove()
+
+ var nilOption = $(this.nilOptionStr);
+ nilOption.text(this.nilOptionText);
+ this.$regions.append(nilOption);
+
+ $.each(regions, this.writeRegion.bind(this))
+
+ this.$regions.val(userRegion)
+ this.$regions.attr("disabled", null).easyDropDown('enable');
+
+ context.JK.dropdown(this.$regions);
+ },
+
+ writeCity: function (index, city) {
+ if (!city) return;
+
+ var option = $(this.nilOptionStr)
+ option.text(city)
+ option.attr("value", city)
+
+ this.$cities.append(option)
+ },
+
+ populateCities: function (cities, userCity) {
+ this.$cities.children().remove();
+
+ var nilOption = $(this.nilOptionStr);
+ nilOption.text(this.nilOptionText);
+ this.$cities.append(nilOption);
+
+ $.each(cities, this.writeCity.bind(this))
+
+ this.$cities.val(userCity)
+ this.$cities.attr("disabled", null).easyDropDown('enable');
+
+ context.JK.dropdown(this.$cities);
+ },
+
+ regionListFailure: function (jqXHR, textStatus, errorThrown) {
+ if (jqXHR.status == 422) {
+ this.logger.debug("no regions found for country: " + this.country);
+ }
+ else {
+ this.app.ajaxError(arguments);
+ }
+ },
+
+ cityListFailure: function (jqXHR, textStatus, errorThrown) {
+ if (jqXHR.status == 422) {
+ this.logger.debug("no cities found for country/region: " + this.country + "/" + this.region);
+ }
+ else {
+ this.app.ajaxError(arguments);
+ }
+ }
+ });
+
+})(window, jQuery);
+
diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js
index a8fc38a9d..aa17755ae 100644
--- a/web/app/assets/javascripts/sessionList.js
+++ b/web/app/assets/javascripts/sessionList.js
@@ -90,13 +90,16 @@
var $row = $(context.JK.fillTemplate($activeSessionTemplate.html(), sessionVals));
var $offsetParent = $(tbGroup).closest('.content');
- var $latencyBadge = $row.find('.latency-value');
- var full_score = $latencyBadge.attr('data-full-score') || null;
- var internet_score = $latencyBadge.attr('data-internet-score') || null;
- var audio_latency = $latencyBadge.attr('data-audio-latency') || null;
- var latencyBadgeUserId = $latencyBadge.attr('data-user-id');
- var scoreOptions = {offsetParent: $offsetParent};
- helpBubble.scoreBreakdown($latencyBadge, context.JK.currentUserId == latencyBadgeUserId, full_score, myAudioLatency, audio_latency, internet_score, scoreOptions);
+ var $latencyBadges = $row.find('.latency-value');
+ context._.each($latencyBadges, function(latencyBadge) {
+ var $latencyBadge = $(latencyBadge);
+ var full_score = $latencyBadge.attr('data-full-score') || null;
+ var internet_score = $latencyBadge.attr('data-internet-score') || null;
+ var audio_latency = $latencyBadge.attr('data-audio-latency') || null;
+ var latencyBadgeUserId = $latencyBadge.attr('data-user-id');
+ var scoreOptions = {offsetParent: $offsetParent};
+ helpBubble.scoreBreakdown($latencyBadge, context.JK.currentUserId == latencyBadgeUserId, full_score, myAudioLatency, audio_latency, internet_score, scoreOptions);
+ });
$(tbGroup).append($row);
@@ -200,13 +203,16 @@
var $row = $(context.JK.fillTemplate($inactiveSessionTemplate.html(), sessionVals));
var $offsetParent = $(tbGroup).closest('.content');
- var $latencyBadge = $row.find('.latency-value');
- var full_score = $latencyBadge.attr('data-full-score') || null;
- var internet_score = $latencyBadge.attr('data-internet-score') || null;
- var audio_latency = $latencyBadge.attr('data-audio-latency') || null;
- var latencyBadgeUserId = $latencyBadge.attr('data-user-id');
- var scoreOptions = {offsetParent: $offsetParent};
- helpBubble.scoreBreakdown($latencyBadge, context.JK.currentUserId == latencyBadgeUserId, full_score, myAudioLatency, audio_latency, internet_score, scoreOptions);
+ var $latencyBadges = $row.find('.latency-value');
+ context._.each($latencyBadges, function(latencyBadge) {
+ var $latencyBadge = $(latencyBadge);
+ var full_score = $latencyBadge.attr('data-full-score') || null;
+ var internet_score = $latencyBadge.attr('data-internet-score') || null;
+ var audio_latency = $latencyBadge.attr('data-audio-latency') || null;
+ var latencyBadgeUserId = $latencyBadge.attr('data-user-id');
+ var scoreOptions = {offsetParent: $offsetParent};
+ helpBubble.scoreBreakdown($latencyBadge, context.JK.currentUserId == latencyBadgeUserId, full_score, myAudioLatency, audio_latency, internet_score, scoreOptions);
+ })
// initial page load
diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js
index 054f1953e..82a8724b0 100644
--- a/web/app/assets/javascripts/sessionModel.js
+++ b/web/app/assets/javascripts/sessionModel.js
@@ -515,6 +515,15 @@
}
function onWindowBackgrounded(type, text) {
+ app.user()
+ .done(function(userProfile) {
+ if(userProfile.show_whats_next &&
+ window.location.pathname.indexOf(gon.client_path) == 0 &&
+ !app.layout.isDialogShowing('getting-started')) {
+ app.layout.showDialog('getting-started');
+ }
+ })
+
if(!inSession()) return;
// the window was closed; just attempt to nav to home, which will cause all the right REST calls to happen
diff --git a/web/app/assets/javascripts/user_dropdown.js b/web/app/assets/javascripts/user_dropdown.js
index 3242ec824..342e3dc7e 100644
--- a/web/app/assets/javascripts/user_dropdown.js
+++ b/web/app/assets/javascripts/user_dropdown.js
@@ -11,7 +11,7 @@
var rest = new JK.Rest();
var userMe = null;
var invitationDialog = null;
- var notYetShownWhatsNext = true;
+ var nowYetShownGettingStarted = true;
function menuHoverIn() {
$('ul.shortcuts', this).show();
@@ -77,7 +77,6 @@
// TODO - Setting global variable for local user.
context.JK.userMe = r;
updateHeader();
- handleWhatsNext(userMe);
});
}
@@ -86,17 +85,6 @@
showAvatar();
}
- function handleWhatsNext(userProfile) {
- if (notYetShownWhatsNext && gon.isNativeClient && userProfile.show_whats_next) {
- notYetShownWhatsNext = false;
- console.log("window.location.pathname", window.location.pathname, gon.client_path, window.location.pathname.indexOf(gon.client_url));
- if(window.location.pathname.indexOf(gon.client_path) == 0) {
- app.layout.showDialog('whatsNext');
- }
-
- }
- }
-
// initially show avatar
function showAvatar() {
var photoUrl = context.JK.resolveAvatarUrl(userMe.photo_url);
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index f6a12d05a..904b12084 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -115,7 +115,9 @@
})
- var helpText = context._.template($('#template-help-' + templateName).html(), data, { variable: 'data' });
+ var $template = $('#template-help-' + templateName);
+ if($template.length == 0) throw "no template by the name " + templateName;
+ var helpText = context._.template($template.html(), data, { variable: 'data' });
var holder = $('