VRFS-2795 filter description & spec tests; support for reset, cancel buttons; proper display and update impl

This commit is contained in:
Jonathan Kolyer 2015-04-04 14:55:41 +00:00
parent 1f432446f5
commit 4fbf3e694d
10 changed files with 316 additions and 87 deletions

View File

@ -25,5 +25,9 @@ module JamRuby
self.update_attribute(:data_blob, self.json)
end
def json_value(key)
self.json[key]
end
end
end

View File

@ -75,6 +75,12 @@ module JamRuby
INTEREST_VALS[5] => 'Co-Writing'
}
INSTRUMENT_PROFICIENCY = {
1 => 'Beginner',
2 => 'Intermediate',
3 => 'Expert',
}
JSON_SCHEMA = {
KEY_SORT_ORDER => SORT_VALS[0],
KEY_INSTRUMENTS => [],
@ -148,7 +154,7 @@ module JamRuby
arels = []
vals.each do |val|
today = Date.today
case val
case val.to_i
when 10
arels << User.where("birth_date >= ? AND birth_date < ?",
today - 20.years, today - 10.years)
@ -234,14 +240,22 @@ module JamRuby
rel
end
def search_results_page(filter={}, page=1, searcher=nil)
self.data_blob = filter
self.save
def search_results_page(filter=nil, page=1)
if filter
self.data_blob = filter
self.save
else
filter = self.data_blob
end
rel = do_search(filter)
rel, page = self.pagination(rel, filter)
rel = rel.includes([:instruments, :followings, :friends])
@page_count = rel.total_pages
musician_results_for_user(rel.all, searcher)
musician_results(rel.all)
end
def reset_search_results
search_results_page(JSON_SCHEMA)
end
RESULT_FOLLOW = :follows
@ -253,7 +267,7 @@ module JamRuby
COUNT_SESSION = :count_session
COUNTERS = [COUNT_FRIEND, COUNT_FOLLOW, COUNT_RECORD, COUNT_SESSION]
def musician_results_for_user(_results, user)
def musician_results(_results)
@results = _results
@user_counters = {} and return self unless user
@ -330,6 +344,58 @@ module JamRuby
self.class.to_s
end
def is_blank?
self.data_blob == JSON_SCHEMA
end
def description
if self.is_blank?
return 'Click search button to look for musicians with similar interests, skill levels, etc.'
end
jj = self.json
str = 'Current Search: '
str += "Sort = #{SORT_ORDERS[json_value(MusicianSearch::KEY_SORT_ORDER)]}"
if (val = jj[KEY_INTERESTS]) != INTEREST_VALS[0]
str += "; Interest = #{INTERESTS[val]}"
end
if (val = jj[KEY_SKILL].to_i) != SKILL_VALS[0]
str += "; Skill = #{SKILL_LEVELS[val]}"
end
if (val = jj[KEY_STUDIOS].to_i) != STUDIO_COUNTS[0]
str += "; Studio Sessions = #{STUDIOS_LABELS[val]}"
end
if (val = jj[KEY_GIGS].to_i) != GIG_COUNTS[0]
str += "; Concert Gigs = #{GIG_LABELS[val]}"
end
val = jj[KEY_AGES]
val.sort!
if val[0] != AGE_COUNTS[0]
str += "; Ages = "
val.each_with_index do |vv, idx|
str += "#{AGES[vv]}"
str += ', ' unless idx==(val.length-1)
end
end
if 0 < (val = jj[KEY_GENRES]).length
str += "; Genres = "
genres = Genre.where(["id IN (?)", val]).order('description').pluck(:description)
genres.each_with_index do |gg, idx|
str += "#{gg}"
str += ', ' unless idx==(genres.length-1)
end
end
if 0 < (val = jj[KEY_INSTRUMENTS]).length
str += "; Instruments = "
instr_ids = val.collect { |vv| vv['instrument_id'] }
instrs = Instrument.where(["id IN (?)", instr_ids]).order(:description)
instrs.each_with_index do |ii, idx|
proficiency = val.detect { |vv| vv['instrument_id'] == ii.id }['proficiency_level']
str += "#{ii.description} (#{INSTRUMENT_PROFICIENCY[proficiency.to_i]})"
str += ', ' unless idx==(instrs.length-1)
end
end
str
end
end
end

View File

@ -1,7 +1,9 @@
require 'spec_helper'
require 'time_difference'
describe 'Musician Search Model' do
let!(:searcher) { FactoryGirl.create(:austin_user) }
let!(:search) { MusicianSearch.create_search(searcher) }
@ -174,16 +176,6 @@ describe 'Musician Search Model' do
expect(search.do_search.count).to eq(3)
end
end
it "filters combinations" do
end
end
describe "pagination" do
it "first page results" do
end
it "second page results" do
end
end
describe "sort order by distance" do
@ -219,7 +211,7 @@ describe 'Musician Search Model' do
@musicians << @user2
@musicians << @user3
@musicians << @user4
< @musicians << @user5
@musicians << @user5
@musicians << @user6
# from these scores:
@ -245,5 +237,70 @@ describe 'Musician Search Model' do
end
end
describe "produces accurate query description" do
it 'has default description' do
expect(search.description).to match(/^Click search button to look for musicians/)
end
it 'has correct sort order description' do
search.update_json_value(MusicianSearch::KEY_SORT_ORDER, MusicianSearch::SORT_VALS[1])
str = MusicianSearch::SORT_ORDERS[search.json_value(MusicianSearch::KEY_SORT_ORDER)]
expect(search.description).to match(/^Current Search: Sort = #{str}$/)
end
it 'has correct description for single-valued selections' do
selections = [{
key: MusicianSearch::KEY_INTERESTS,
value: MusicianSearch::INTEREST_VALS[1],
lookup: MusicianSearch::INTERESTS,
description: 'Interest'
},
{
key: MusicianSearch::KEY_SKILL,
value: MusicianSearch::SKILL_VALS[1],
lookup: MusicianSearch::SKILL_LEVELS,
description: 'Skill'
},
{
key: MusicianSearch::KEY_STUDIOS,
value: MusicianSearch::STUDIO_COUNTS[1],
lookup: MusicianSearch::STUDIOS_LABELS,
description: 'Studio Sessions'
},
{
key: MusicianSearch::KEY_GIGS,
value: MusicianSearch::GIG_COUNTS[1],
lookup: MusicianSearch::GIG_LABELS,
description: 'Concert Gigs'
}]
selections.each do |hash|
search.update_json_value(hash[:key], hash[:value])
json_val = search.json_value(hash[:key])
expect(search.description).to match(/; #{hash[:description]} = #{hash[:lookup][json_val]}/)
end
end
it 'has correct description for genres' do
search.update_json_value(MusicianSearch::KEY_GENRES, [Genre.first.id, Genre.last.id])
expect(search.description).to match(/; Genres = #{Genre.first.description}, #{Genre.last.description}/)
end
it 'has correct description for ages' do
search.update_json_value(MusicianSearch::KEY_AGES, [MusicianSearch::AGE_COUNTS[1],MusicianSearch::AGE_COUNTS[2]])
expect(search.description).to match(/; Ages = #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[1]]}, #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[2]]}/)
end
it 'has correct description for instruments' do
instrs = Instrument.limit(2).order(:description)
instjson = [{ instrument_id: instrs[0].id, proficiency_level: 2 },
{ instrument_id: instrs[1].id, proficiency_level: 1 }
]
search.update_json_value(MusicianSearch::KEY_INSTRUMENTS, instjson)
instr_descrip = "#{instrs[0].description} (#{MusicianSearch::INSTRUMENT_PROFICIENCY[2]}), #{instrs[1].description} (#{MusicianSearch::INSTRUMENT_PROFICIENCY[1]})"
expect(search.description).to match(/; Instruments = #{Regexp.escape(instr_descrip)}/)
end
end
end

View File

@ -132,7 +132,7 @@ group :test, :cucumber do
# gem 'growl', '1.0.3'
gem 'poltergeist'
gem 'resque_spec'
#gem 'thin'
# gem 'puma'
end

View File

@ -1613,8 +1613,9 @@
});
}
function getMusicianSearchFilter() {
return $.get("/api/search/musicians.json");
function getMusicianSearchFilter(query) {
var qarg = query === undefined ? '' : query;
return $.get("/api/search/musicians.json?"+qarg);
}
function postMusicianSearchFilter(query) {

View File

@ -17,23 +17,36 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
init: (app) =>
@screenBindings = { 'beforeShow': this.beforeShow, 'afterShow': this.afterShow }
app.bindScreen('musicians', @screenBindings);
@results = $('#musician-search-filter-results')
@results = $('#musician-search-filter-results-list')
srchbtn = $ '#btn-perform-musician-search'
srchbtn.on 'click', =>
$('#btn-perform-musician-search').on 'click', =>
this.performSearch()
renderSearchFilter: () =>
$.when(this.rest.getMusicianSearchFilter()).done (sFilter) =>
this.loadSearchFilter(sFilter)
$('#btn-musician-search-builder').on 'click', =>
this.showBuilder()
$('#btn-musician-search-reset').on 'click', =>
this.resetFilter()
$('#btn-musician-search-cancel').on 'click', =>
this.cancelFilter()
beforeShow: (data) =>
userId = data.id
afterShow: () =>
afterShow: () =>
$('#musician-search-filter-results').show()
$('#musician-search-filter-builder').hide()
this.getUserFilterResults()
showBuilder: () =>
$('#musician-search-filter-results').hide()
$('#musician-search-filter-builder').show()
this.renderSearchFilter()
$('#musician-search-filter-results-list').empty()
renderSearchFilter: () =>
$.when(this.rest.getMusicianSearchFilter()).done (sFilter) =>
this.loadSearchFilter(sFilter)
loadSearchFilter: (sFilter) =>
@searchFilter = sFilter
@ -48,14 +61,14 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
content_root = $('#musician-search-filter-builder-form')
content_root.html template
this.populateSkill()
this.populateStudio()
this.populateGigs()
this.populateInterests()
this.populateAges()
this.populateGenres()
this.populateInstruments()
this.populateSortOrder()
this._populateSkill()
this._populateStudio()
this._populateGigs()
this._populateInterests()
this._populateAges()
this._populateGenres()
this._populateInstruments()
this._populateSortOrder()
_populateSelectWithKeys: (struct, selection, keys, element) =>
element.children().remove()
@ -79,25 +92,25 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
$.extend(struct, sourceStruct)
this._populateSelectWithKeys(struct, selection, Object.keys(struct).sort(), element)
populateSortOrder: () =>
_populateSortOrder: () =>
this._populateSelectIdentifier('sort_order')
populateInterests: () =>
_populateInterests: () =>
this._populateSelectIdentifier('interests')
populateStudio: () =>
_populateStudio: () =>
elem = $ '#musician-search-filter-builder select[name=studio_sessions]'
this._populateSelectWithInt(@profileUtils.studioMap, @searchFilter.studio_sessions.toString(), elem)
populateGigs: () =>
_populateGigs: () =>
elem = $ '#musician-search-filter-builder select[name=concert_gigs]'
this._populateSelectWithInt(@profileUtils.gigMap, @searchFilter.concert_gigs.toString(), elem)
populateSkill: () =>
_populateSkill: () =>
elem = $ '#musician-search-filter-builder select[name=skill_level]'
this._populateSelectWithInt(@profileUtils.skillLevelMap, @searchFilter.skill_level.toString(), elem)
populateAges: () =>
_populateAges: () =>
$('#search-filter-ages').empty()
ages_map = gon.musician_search_meta['ages']['map']
$.each gon.musician_search_meta['ages']['keys'], (index, key) =>
@ -117,7 +130,7 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
checked: selected)
$('#search-filter-ages').append ageHtml
populateGenres: () =>
_populateGenres: () =>
$('#search-filter-genres').empty()
@rest.getGenres().done (genres) =>
genreTemplate = $('#template-search-filter-setup-genres').html()
@ -137,7 +150,7 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
checked: selected)
$('#search-filter-genres').append genreHtml
populateInstruments: () =>
_populateInstruments: () =>
$('#search-filter-instruments').empty()
@rest.getInstruments().done (instruments) =>
$.each instruments, (index, instrument) =>
@ -161,11 +174,11 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
$(profsel).val(proficiency)
return true
_selectedValue: (identifier) =>
_builderSelectValue: (identifier) =>
elem = $ '#musician-search-filter-builder select[name='+identifier+']'
elem.val()
_selectedMultiValue: (identifier) =>
_builderSelectMultiValue: (identifier) =>
vals = []
elem = $ '#search-filter-'+identifier+' input[type=checkbox]:checked'
if 'instruments' == identifier
@ -180,34 +193,59 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
vals.push $(this).val()
vals
_formatLocation = (musician) ->
if musician.city and musician.state
musician.city + ', ' + musician.state
else if musician.city
musician.city
else if musician.regionname
musician.regionname
else
'Location Unavailable'
didSearch: (response) =>
# console.log("response = ", JSON.stringify(response))
willSearch: () =>
$('#musician-search-filter-spinner').show()
$('#musician-search-filter-results-list').empty()
$('#musician-search-filter-builder').hide()
$('#musician-search-filter-results').show()
didSearch: (response) =>
this.loadSearchFilter(response.filter_json)
@searchResults = response
$('#musician-search-filter-spinner').hide()
this.renderMusicians()
resetFilter: () =>
this.willSearch()
@rest.postMusicianSearchFilter({ filter: 'reset' }).done(this.didSearch)
cancelFilter: () =>
this.resetFilter()
getUserFilterResults: () =>
this.willSearch()
@rest.getMusicianSearchFilter('results=true').done(this.didSearch)
performSearch: () =>
this.willSearch()
$.each gon.musician_search_meta.filter_keys.single, (index, key) =>
@searchFilter[key] = this._selectedValue(key)
@searchFilter[key] = this._builderSelectValue(key)
$.each gon.musician_search_meta.filter_keys.multi, (index, key) =>
@searchFilter[key] = this._selectedMultiValue(key)
@searchFilter[key] = this._builderSelectMultiValue(key)
@rest.postMusicianSearchFilter({ filter: JSON.stringify(@searchFilter), page: 1 }).done(this.didSearch)
renderResultsHeader: () =>
$('#musician-search-filter-description').html(@searchResults.description)
if @searchResults.is_blank_filter
$('#btn-perform-musician-search-reset').show()
else
$('#btn-perform-musician-search-reset').hide()
renderMusicians: () =>
ii = undefined
this.renderResultsHeader()
musicians = @searchResults.musicians
len = musicians.length
if 0 == len
$('#musician-search-filter-results-list-blank').show()
$('#musician-search-filter-results-list-blank').html('No results found')
return
else
$('#musician-search-filter-results-list-blank').hide()
ii = 0
mTemplate = $('#template-search-musician-row').html()
aTemplate = $('#template-search-musician-action-btns').html()
mVals = undefined
@ -218,9 +256,6 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
followVals = undefined
aFollow = undefined
myAudioLatency = @searchResults.my_audio_latency
ii = 0
musicians = @searchResults.musicians
len = musicians.length
while ii < len
musician = musicians[ii]
if context.JK.currentUserId == musician.id
@ -249,7 +284,7 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
avatar_url: context.JK.resolveAvatarUrl(musician.photo_url)
profile_url: '/client#/profile/' + musician.id
musician_name: musician.name
musician_location: _formatLocation(musician)
musician_location: this._formatLocation(musician)
instruments: instr_logos
biography: musician['biography']
follow_count: musician['follow_count']
@ -287,8 +322,7 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
return
friendMusician: (evt) =>
# if the musician is already a friend, remove the button-orange class, and prevent
# the link from working
# if the musician is already a friend, remove the button-orange class, and prevent the link from working
if 0 == $(this).closest('.button-orange').size()
return false
$(this).click (ee) ->
@ -300,8 +334,7 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
return
followMusician: (evt) =>
# if the musician is already followed, remove the button-orange class, and prevent
# the link from working
# if the musician is already followed, remove the button-orange class, and prevent the link from working
if 0 == $(this).closest('.button-orange').size()
return false
$(this).click (ee) ->
@ -319,9 +352,7 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
data: JSON.stringify(newFollowing)
processData: false
success: (response) ->
# remove the orange look to indicate it's not selectable
# @FIXME -- this will need to be tweaked when we allow unfollowing
$('div[data-musician-id=' + newFollowing.user_id + '] .search-m-follow').removeClass('button-orange').addClass 'button-grey'
# remove the orange look to indicate it's not selectable # @FIXME -- this will need to be tweaked when we allow unfollowing $('div[data-musician-id=' + newFollowing.user_id + '] .search-m-follow').removeClass('button-orange').addClass 'button-grey'
return
error: app.ajaxError
return
@ -330,4 +361,14 @@ context.JK.MusicianSearchFilter = class MusicianSearchFilter
userId = $(this).parent().data('musician-id')
app.layout.showDialog 'text-message', d1: userId
false
_formatLocation: (musician) ->
if musician.city and musician.state
musician.city + ', ' + musician.state
else if musician.city
musician.city
else if musician.regionname
musician.regionname
else
'Location Unavailable'

View File

@ -24,13 +24,22 @@ class ApiSearchController < ApiController
def musicians
if request.get?
render :json => MusicianSearch.search_filter_json(current_user), :status => 200
if params[:results]
@search = MusicianSearch.user_search_filter(current_user).search_results_page
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index'
else
render :json => MusicianSearch.search_filter_json(current_user), :status => 200
end
elsif request.post?
logger.debug("*** params = #{params.inspect}")
ms = MusicianSearch.user_search_filter(current_user)
json = JSON.parse(params[:filter], :create_additions => false)
@search = ms.search_results_page(json, [params[:page].to_i, 1].max, current_user)
filter = params[:filter]
if filter == 'reset'
@search = ms.reset_search_results
else
json = JSON.parse(filter, :create_additions => false)
@search = ms.search_results_page(json, [params[:page].to_i, 1].max)
end
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index'
end
end

View File

@ -12,6 +12,18 @@ if @search.is_a?(MusicianSearch)
current_user.last_jam_audio_latency.round if current_user.last_jam_audio_latency
end
node :description do |foo|
@search.description
end
node :is_blank_filter do |foo|
@search.is_blank?
end
node :filter_json do |foo|
@search.json
end
child(:results => :musicians) {
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score
@ -92,6 +104,18 @@ if @search.musicians_filter_search?
node :my_audio_latency do |user|
current_user.last_jam_audio_latency.round if current_user.last_jam_audio_latency
end
node :description do |foo|
@search.description
end
node :is_blank_filter do |foo|
@search.is_blank?
end
node :filter_json do |foo|
@search.json
end
child(:results => :musicians) {
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score

View File

@ -2,37 +2,44 @@
div#musician-search-filter-builder.content-wrapper
h2 search musicians
div#musician-search-filter-header
a#btn-perform-musician-search.button-grey href="#" SEARCH
a#btn-musician-search-cancel.button-grey href="#" CANCEL
a#btn-perform-musician-search.button-orange href="#" SEARCH
div#musician-search-filter-builder-form
div#musician-search-filter-results.content-wrapper
h2 musician search results
div#musician-search-filter-results
div#musician-search-filter-results-header
a#btn-musician-search-builder.button-orange href="#" SEARCH
div#musician-search-filter-description
a#btn-musician-search-reset.button-grey href="#" RESET
div#musician-search-filter-spinner.spinner-large
div#musician-search-filter-results-list-blank
div#musician-search-filter-results-list.content-wrapper
script#template-musician-search-filter type="text/template"
.field
label Sort Results By:
select.w80 name="sort_order"
option selected="selected" value="{sort_order}" {sort_order)
option selected="selected" value="{sort_order}" {sort_order}
.field
label Interests:
select.w80 name="interests"
option selected="selected" value="{interests}" {interests)
option selected="selected" value="{interests}" {interests}
.field
label Skill:
select.w80 name="skill_level"
option selected="selected" value="{skill_level}" {skill_label)
option selected="selected" value="{skill_level}" {skill_label}
.field
label Studio Sessions Played:
select.w80 name="studio_sessions"
option selected="selected" value="{studio_sessions}" {studio_sessions)
option selected="selected" value="{studio_sessions}" {studio_sessions}
.field
label Concert Gigs Played:
select.w80 name="concert_gigs"
option selected="selected" value="{concert_gigs}" {concert_gigs)
option selected="selected" value="{concert_gigs}" {concert_gigs}
.field
label for="search-filter-ages" Ages:
@ -118,5 +125,5 @@ script#template-search-musician-action-btns type="text/template"
- if current_user && current_user.musician?
a.smallbutton.search-m-friend class="{friend_class}" href="#" {friend_caption}
a.smallbutton.search-m-follow class="{follow_class}" href="#" {follow_caption}
a.smallbutton.search-m-message class="{message_class}" href="#" {message_caption"
a.smallbutton.search-m-message class="{message_class}" href="#" {message_caption}
.clearall

View File

@ -183,6 +183,7 @@ def make_users(num=99)
admin.toggle!(:admin)
instruments = Instrument.all
genres = Genre.all
num.times do |n|
password = "password"
uu = User.create!(first_name: Faker::Name.name,
@ -195,6 +196,7 @@ def make_users(num=99)
country: 'US',
password_confirmation: password)
uu.musician = true
uu.birth_date = Time.now - 13.years - rand(65).years
num_instrument = rand(4) + 1
user_instruments = instruments.sample(num_instrument)
num_instrument.times do |mm|
@ -205,6 +207,24 @@ def make_users(num=99)
musician_instrument.priority = rand(num_instrument)
uu.musician_instruments << musician_instrument
end
num_genre = rand(4) + 1
user_genres = genres.sample(num_genre)
num_genre.times do |mm|
genre_player = GenrePlayer.new
genre_player.player_id = uu.id
genre_player.player_type = uu.class.name
genre_player.genre_id = user_genres[mm].id
genre_player.genre_type = GenrePlayer::PROFILE
uu.genre_players << genre_player
end
uu.skill_level = rand(2) + 1
uu.studio_session_count = rand(100)
uu.concert_count = rand(40)
uu.virtual_band = 0==rand(2)
uu.traditional_band = 0==rand(2)
uu.paid_sessions = 0==rand(2)
uu.free_sessions = 0==rand(2)
uu.cowriting = 0==rand(2)
uu.save!
yn = true
while yn