From bab8d14798f20c2bd20bf904f340a02a697adf23 Mon Sep 17 00:00:00 2001 From: Nuwan Chathuranga Date: Mon, 9 Aug 2021 19:48:10 +0530 Subject: [PATCH] wip - filter musician records --- ruby/lib/jam_ruby/models/base_search.rb | 25 ++- ruby/lib/jam_ruby/models/musician_search.rb | 6 +- web/app/controllers/api_search_controller.rb | 129 +++++++++++++ web/app/controllers/api_users_controller.rb | 4 + web/config/routes.rb | 4 + .../controllers/api_search_controller_spec.rb | 30 +++ web/spec/fixtures/latency_users.json | 88 +++++++++ web/spec/requests/musician_filter_api_spec.rb | 179 ++++++++++++++++++ 8 files changed, 461 insertions(+), 4 deletions(-) create mode 100644 web/spec/controllers/api_search_controller_spec.rb create mode 100644 web/spec/fixtures/latency_users.json create mode 100644 web/spec/requests/musician_filter_api_spec.rb diff --git a/ruby/lib/jam_ruby/models/base_search.rb b/ruby/lib/jam_ruby/models/base_search.rb index 685133be8..ee0b71c9b 100644 --- a/ruby/lib/jam_ruby/models/base_search.rb +++ b/ruby/lib/jam_ruby/models/base_search.rb @@ -17,6 +17,7 @@ module JamRuby KEY_INSTRUMENTS = 'instruments' KEY_GIGS = 'concert_gigs' KEY_SORT_ORDER = 'sort_order' + KEY_JOINED_WITHIN = 'joined_within_days' SORT_VALS = %W{ latency distance } SORT_ORDERS = { @@ -31,6 +32,15 @@ module JamRuby SKILL_VALS[2] => 'Pro', } + JOINED_WITHIN_VALS = [ANY_VAL_INT, 1, 7, 30, 90] + JOINED_WITHIN_LABELS = { + JOINED_WITHIN_VALS[0] => ANY_VAL_STR, + JOINED_WITHIN_VALS[1] => 'Within Last 1 Day', + JOINED_WITHIN_VALS[2] => 'Within Last 7 Day', + JOINED_WITHIN_VALS[3] => 'Within Last 30 Day', + JOINED_WITHIN_VALS[4] => 'Within Last 90 Day', + } + GIG_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4] GIG_LABELS = { GIG_COUNTS[0] => 'Any', @@ -52,6 +62,7 @@ module JamRuby KEY_INSTRUMENTS => [], KEY_GENRES => [], KEY_GIGS => self::GIG_COUNTS[0].to_s, + KEY_JOINED_WITHIN => self::JOINED_WITHIN_VALS[0], } end @@ -129,7 +140,7 @@ module JamRuby unless (instruments = query_data[KEY_INSTRUMENTS]).blank? instrids = self.class.instrument_ids instruments = instruments.select { |ii| instrids.has_key?(ii['instrument_id']) } - + #debugger unless instruments.blank? instsql = "SELECT player_id FROM musicians_instruments WHERE ((" instsql += instruments.collect do |inst| @@ -159,6 +170,14 @@ module JamRuby rel end + def _joined_within(rel) + #debugger + if 0 < (val = json[KEY_JOINED_WITHIN].to_i) + rel = rel.where("created_at >= ?", val.days.ago.at_beginning_of_day) + end + rel + end + def _sort_order(rel) end @@ -172,7 +191,7 @@ module JamRuby rel end - def search_results_page(filter=nil, page=1) + def search_results_page(filter=nil, page=1, user_ids = []) if filter self.data_blob = filter self.save @@ -180,7 +199,7 @@ module JamRuby filter = self.data_blob end - rel = do_search(filter) + rel = do_search(filter, user_ids) @page_number = [page.to_i, 1].max rel = rel.paginate(:page => @page_number, :per_page => self.class::PER_PAGE) diff --git a/ruby/lib/jam_ruby/models/musician_search.rb b/ruby/lib/jam_ruby/models/musician_search.rb index 81639cd1c..c8d9bbd9d 100644 --- a/ruby/lib/jam_ruby/models/musician_search.rb +++ b/ruby/lib/jam_ruby/models/musician_search.rb @@ -143,8 +143,11 @@ module JamRuby rel end - def do_search(params={}) + def do_search(params={}, user_ids = []) rel = User.musicians.where('users.id <> ?', self.user.id) + if user_ids.any? + rel = rel.where(id: user_ids).where('users.id <> ?', self.user.id) + end rel = Search.scope_schools_together(rel, self.user) rel = self._genres(rel) rel = self._ages(rel) @@ -154,6 +157,7 @@ module JamRuby rel = self._instruments(rel) rel = self._interests(rel) rel = self._sort_order(rel) + rel = self._joined_within(rel) rel end diff --git a/web/app/controllers/api_search_controller.rb b/web/app/controllers/api_search_controller.rb index fd4c9d5cc..447f8b42c 100644 --- a/web/app/controllers/api_search_controller.rb +++ b/web/app/controllers/api_search_controller.rb @@ -93,4 +93,133 @@ class ApiSearchController < ApiController end end + #filter users by first fetching users from latency graph database + #for the latency filter options and then quering the relational + #database for other filter options + def filter + latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) + latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair]) + latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high]) + + begin + user_ids = user_ids_by_latency(latency_good, latency_fair, latency_high) + + filter_params = { + "sort_order"=>"latency", + "instruments"=>[], + "genres"=> [], + "concert_gigs"=>"-1", + "interests"=>"any", + "studio_sessions"=>"-1", + "ages"=>[], + "skill_level"=>"-1", + "joined_within_days"=>"any" + } + + filter_params.merge!(genres: params[:genres]) unless params[:genres].blank? + + beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner]) + intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate]) + expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert]) + + proficiency_levels = [] + proficiency_levels.push(1) if beginner + proficiency_levels.push(2) if intermediate + proficiency_levels.push(3) if expert + + instruments = params[:instruments] + + if instruments && instruments.any? + inst = [] + instruments.each do |ii| + proficiency_levels.each do |pl| + inst << { instrument_id: ii, proficiency_level: pl} + end + end + filter_params.merge!(instruments: inst) + end + + filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank? + + sobj = MusicianSearch.user_search_filter(current_user) + @search = sobj.search_results_page(filter_params, [params[:page].to_i, 1].max, user_ids) + + respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index' + + rescue => exception + logger.debug("Latency exception: #{exception.message}") + Bugsnag.notify(exception) do |report| + report.severity = "error" + report.add_tab(:latency, { + params: params, + user_id: current_user.id, + name: current_user.name, + url: filter_latency_url, + }) + end + render json: {}, status: 500 + end + + end + +private + + def filter_latency_url + "#{Rails.application.config.latency_data_host}/search_users" + end + + def user_ids_by_latency(latency_good, latency_fair, latency_high) + user_ids = [] + if latency_good || latency_fair || latency_high + uri = URI(filter_latency_url) + begin + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://") + req = Net::HTTP::Post.new(uri) + req["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}" + req["Content-Type"] = "application/json" + req.body = { + my_user_id: current_user.id, + my_public_ip: request.remote_ip, + my_device_id: nil, + my_client_id: nil + }.to_json + + response = http.request(req) + + if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess) + graph_db_users = JSON.parse(response.body)["users"] + if latency_good || latency_fair || latency_high + graph_db_users.select! do |user| + total_latency = user["ars"]["total_latency"].to_f + (total_latency <= 40 && latency_good) || + (total_latency > 40 && total_latency <= 80 && latency_fair) || + (total_latency > 80 && latency_high) + end + end + + user_ids = graph_db_users.map { | user | user["user_id"] }.uniq + + return user_ids + else + logger.debug("Latency response failed: #{response}") + Bugsnag.notify("LatencyResponseFailed") do |report| + report.severity = "faliure" + report.add_tab(:latency, { + user_id: current_user.id, + name: current_user.name, + params: params, + url: filter_latency_url, + code: response.code, + body: response.body, + }) + end + end + rescue => exception + raise exception + end + end + user_ids + end + end diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 522c1d529..97977d805 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -33,6 +33,10 @@ class ApiUsersController < ApiController render :json => {}, :status => 200 end + def me + render json: { first_name: current_user.first_name, last_name: current_user.last_name, name: current_user.name, photo_url: current_user.photo_url }, status: 200 + end + def show @user=lookup_user diff --git a/web/config/routes.rb b/web/config/routes.rb index 22f70acc7..ac601cb3d 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -378,6 +378,9 @@ Rails.application.routes.draw do # validation match '/data_validation' => 'api_users#validate_data', :via => :get + #current user data + match '/me' => 'api_users#me', :via => :get + match '/users' => 'api_users#index', :via => :get match '/users' => 'api_users#create', :via => :post match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail' @@ -629,6 +632,7 @@ Rails.application.routes.draw do match '/search/musicians' => 'api_search#musicians', :via => [:get, :post] match '/search/bands' => 'api_search#bands', :via => [:get, :post] match '/search/jam_tracks' => 'api_search#jam_tracks', :via => [:get, :post] + match '/filter' => 'api_search#filter', :via => [:post] # join requests match '/join_requests/:id' => 'api_join_requests#show', :via => :get, :as => 'api_join_request_detail' diff --git a/web/spec/controllers/api_search_controller_spec.rb b/web/spec/controllers/api_search_controller_spec.rb new file mode 100644 index 000000000..7eecffa2c --- /dev/null +++ b/web/spec/controllers/api_search_controller_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require 'webmock/rspec' + +describe ApiSearchController, type: :controller do + let (:user) { FactoryGirl.create(:user) } + let(:user1) { FactoryGirl.create(:user) } + + before(:each) do + controller.current_user = user + end + + describe "GET filter" do + let(:latency_data_uri) { /\S+\/search_users/ } + let(:response_body) { mock_latency_response([ + { user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 } + ])} + + it "success" do + stub_request(:post, latency_data_uri) + .to_return(body: response_body, status: 200) + + get :filter, { latency_good: true, latency_fair: true, latency_high: true, format: 'json' } + + response.should be_success + expect(response.content_type).to eq("application/json") + expect(response).to render_template(:index) + expect(response).to have_http_status(:created) + end + end +end \ No newline at end of file diff --git a/web/spec/fixtures/latency_users.json b/web/spec/fixtures/latency_users.json new file mode 100644 index 000000000..754332a87 --- /dev/null +++ b/web/spec/fixtures/latency_users.json @@ -0,0 +1,88 @@ +{ + "users": [ + { + "user_id": "963d5268-66b6-463a-a3ee-c97f274fc23f", + "first_name": "Peter", + "last_name": "Walker", + "audio_latency": 0.5, + "audio_latency_unknown": false, + "ars": { + "internet_latency": 0.5, + "total_latency": 1.0 + }, + "p2p": { "internet_latency": 7.0, "total_latency": 11.586167812347412 }, + "wifi": null + }, + { + "user_id": "6337dc99-5023-477f-8781-3a810bb35b61", + "first_name": "Kasun", + "last_name": "Kalhara", + "audio_latency": 15.0, + "audio_latency_unknown": false, + "ars": { + "internet_latency": 16.0, + "total_latency": 31.0 + }, + "p2p": { "internet_latency": null, "total_latency": null }, + "wifi": null + }, + { + "user_id": "feb671a3-1821-48f0-bc14-aa26cf98bb25", + "first_name": "David", + "last_name": "Wilson", + "audio_latency": 10.0, + "audio_latency_unknown": false, + "ars": { + "internet_latency": 30.0, + "total_latency": 40.0 + }, + "p2p": { + "internet_latency": 10.31944465637207, + "total_latency": 14.905612468719482 + }, + "wifi": null + }, + { + "user_id": "fd467978-da75-421f-8e97-6406a784e87e", + "first_name": "Indrashapa", + "last_name": "Liyanage", + "audio_latency": 20.0, + "audio_latency_unknown": false, + "ars": { + "internet_latency": 21.0, + "total_latency": 41.0 + }, + "p2p": { "internet_latency": null, "total_latency": null }, + "wifi": null + }, + { + "user_id": "1", + "first_name": "Test", + "last_name": "User", + "audio_latency": 50.0, + "audio_latency_unknown": false, + "ars": { + "internet_latency": 50.0, + "total_latency": 100.0 + }, + "p2p": { "internet_latency": null, "total_latency": null }, + "wifi": null + }, + { + "user_id": "a09f9a7e-afb7-489d-870d-e13a336e0b97", + "first_name": "Seth", + "last_name": "Call", + "audio_latency": 75.0, + "audio_latency_unknown": false, + "ars": { + "internet_latency": 75.0, + "total_latency": 150.0 + }, + "p2p": { "internet_latency": 7.0, "total_latency": 11.586167812347412 }, + "wifi": null + } + ], + + "my_audio_latency": 5.0, + "my_audio_latency_unknown": false +} diff --git a/web/spec/requests/musician_filter_api_spec.rb b/web/spec/requests/musician_filter_api_spec.rb new file mode 100644 index 000000000..d569dba1a --- /dev/null +++ b/web/spec/requests/musician_filter_api_spec.rb @@ -0,0 +1,179 @@ +require 'spec_helper' +require 'webmock/rspec' + +describe "Musician Filter API", type: :request do + let(:user) { FactoryGirl.create(:user) } + + let(:user1) { FactoryGirl.create(:user) } + let(:user2) { FactoryGirl.create(:user) } + let(:user3) { FactoryGirl.create(:user) } + let(:user4) { FactoryGirl.create(:user) } + let(:user5) { FactoryGirl.create(:user) } + let(:user6) { FactoryGirl.create(:user) } + + let(:latency_data_uri) { /\S+\/search_users/ } + + let(:response_body) { mock_latency_response([ + { user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD + { user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, #GOOD + { user: user3, ars_total_latency: 41.0, ars_internet_latency: 25, audio_latency: 16 }, #FAIR + { user: user4, ars_total_latency: 80.0, ars_internet_latency: 40, audio_latency: 40.0 }, #FAIR + { user: user5, ars_total_latency: 81.0, ars_internet_latency: 41, audio_latency: 40 }, #HIGH + { user: user6, ars_total_latency: 100.0, ars_internet_latency: 50.0, audio_latency: 50.0 } #HIGH + ]) + } + + let(:pop) { Genre.find_by_id('pop') } + let(:rap) { Genre.find_by_id('rap') } + let(:rock) { Genre.find_by_id('rock') } + + before(:each) do + #ActiveMusicSession.delete_all + User.delete_all + stub_request(:post, latency_data_uri) + .with(:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}) + .to_return( body: response_body, status: 200) + end + + def login(user) + post '/sessions', "session[email]" => user.email, "session[password]" => user.password + end + + before do + login(user) + end + + it "get all musicians" do + get '/api/search/musicians.json?results=true' + expect(JSON.parse(response.body)["musicians"].size).to eq(6) + end + + it "filter all musicians for all latency types" do + post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: false } + expect(JSON.parse(response.body)["musicians"].size).to eq(6) + end + + it "filter GOOD latency users" do + post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: false } + expect(response.content_type).to eq("application/json") + expect(response).to render_template(:index) + expect(response).to have_http_status(:created) + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + end + + it "filter FAIR latency musicians" do + post '/api/filter.json', { latency_good: false, latency_fair: true, latency_high: false } + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + end + + it "filter HIGH latency musicians" do + post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: true } + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + end + + it "filter GOOD and FAIR latency musicians" do + post '/api/filter.json', { latency_good: true, latency_fair: true, latency_high: false } + expect(JSON.parse(response.body)["musicians"].size).to eq(4) + end + + it "filter GOOD and HIGH latency musicians" do + post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: true } + expect(JSON.parse(response.body)["musicians"].size).to eq(4) + end + + it "filter GOOD, FAIR and HIGH latency musicians" do + post '/api/filter.json', { latency_good: true, latency_fair: true, latency_high: true } + expect(JSON.parse(response.body)["musicians"].size).to eq(6) + end + + it "filter musicians by genres" do + #------ user1 and user2 are good latency ------ + user1.genres = [pop, rap, rock] + user1.save! + user2.genres = [pop] + user2.save! + + #-------- user3 and user4 are fair latency ----- + user3.genres = [rock] + user3.save! + user4.genres = [pop, rock] + user4.save! + + #-------- user5 and user6 are high latency ----- + user5.genres = [rap, rock] + user5.save! + user6.genres = [rock] + user6.save! + + #debugger + post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: false, genres: ['pop'] } + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + + post '/api/filter.json', { latency_good: false, latency_fair: true, latency_high: true, genres: ['rap'] } + expect(JSON.parse(response.body)["musicians"].size).to eq(1) + + post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: true, genres: ['rock'] } + expect(JSON.parse(response.body)["musicians"].size).to eq(3) + + post '/api/filter.json', { latency_good: false, latency_fair: true, latency_high: false, genres: ['pop', 'rock'] } + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + + end + + it "filter musicians by instruments they play", focus: true do + user1.musician_instruments << FactoryGirl.create(:musician_instrument, player: user1, instrument: JamRuby::Instrument.find('drums'), proficiency_level: 1 ) + user1.musician_instruments << FactoryGirl.create(:musician_instrument, player: user1, instrument: JamRuby::Instrument.find('violin'), proficiency_level: 2 ) + user1.save! + + user2.musician_instruments << FactoryGirl.create(:musician_instrument, player: user2, instrument: JamRuby::Instrument.find('violin'), proficiency_level: 1 ) + user2.save! + + user3.musician_instruments << FactoryGirl.create(:musician_instrument, player: user3, instrument: JamRuby::Instrument.find('drums'), proficiency_level: 2 ) + user3.save! + + post '/api/filter.json', { latency_good: true, latency_fair: true, latency_high: true, instruments: ['drums'], proficiency_intermediate: true } + expect(JSON.parse(response.body)["musicians"].size).to eq(1) + + post '/api/filter.json', { latency_good: false, latency_fair: true, latency_high: true, instruments: ['drums'], proficiency_beginner: true } + expect(JSON.parse(response.body)["musicians"].size).to eq(0) + + post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: false, instruments: ['drums, violin'], proficiency_beginner: true } + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + + post '/api/filter.json', { latency_good: false, latency_fair: true, latency_high: false, instruments: ['drums'], proficiency_beginner: true } + expect(JSON.parse(response.body)["musicians"].size).to eq(0) + end + + it "filter musicians by joined within day" do + user1.created_at = 1.day.ago + user1.save! + + user2.created_at = 6.days.ago + user2.save! + + user3.created_at = 7.days.ago + user3.save! + + user4.created_at = 29.days.ago + user4.save! + + user5.created_at = 30.days.ago + user5.save! + + user6.created_at = 31.days.ago + user6.save! + + post '/api/filter.json', { latency_good: true, latency_fair: true, latency_high: true, joined_within_days: 1 } + expect(JSON.parse(response.body)["musicians"].size).to eq(1) + + post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: false, joined_within_days: 7 } + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + + post '/api/filter.json', { latency_good: true, latency_fair: true, latency_high: false, joined_within_days: 7 } + expect(JSON.parse(response.body)["musicians"].size).to eq(3) + + post '/api/filter.json', { latency_good: false, latency_fair: true, latency_high: false, joined_within_days: 29 } + expect(JSON.parse(response.body)["musicians"].size).to eq(2) + end + +end