diff --git a/db/manifest b/db/manifest index c185d0563..8e23fc021 100755 --- a/db/manifest +++ b/db/manifest @@ -150,4 +150,5 @@ diagnostics.sql user_mods.sql connection_stale_expire.sql rename_chat_messages.sql -fix_connection_fields.sql \ No newline at end of file +fix_connection_fields.sql +session_ratings.sql \ No newline at end of file diff --git a/db/up/session_ratings.sql b/db/up/session_ratings.sql new file mode 100644 index 000000000..68223a849 --- /dev/null +++ b/db/up/session_ratings.sql @@ -0,0 +1 @@ +ALTER TABLE music_sessions_user_history ADD COLUMN rating_comment TEXT; diff --git a/ruby/lib/jam_ruby/app/mailers/batch_mailer.rb b/ruby/lib/jam_ruby/app/mailers/batch_mailer.rb index 7b200eb3b..17f238090 100644 --- a/ruby/lib/jam_ruby/app/mailers/batch_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/batch_mailer.rb @@ -15,7 +15,7 @@ module JamRuby batch.did_send(emails) - mail(:to => emails, + mail(:to => emails, :from => batch.from_email, :subject => batch.subject) do |format| format.text diff --git a/ruby/lib/jam_ruby/models/email_batch.rb b/ruby/lib/jam_ruby/models/email_batch.rb index 74dff06ec..02c729dda 100644 --- a/ruby/lib/jam_ruby/models/email_batch.rb +++ b/ruby/lib/jam_ruby/models/email_batch.rb @@ -13,7 +13,7 @@ module JamRuby VAR_LAST_NAME = '@LASTNAME' DEFAULT_SENDER = "noreply@jamkazam.com" - BATCH_SIZE = 1000 + BATCH_SIZE = 5 BODY_TEMPLATE =< -1..1, :allow_nil => true belongs_to(:user, :class_name => "JamRuby::User", @@ -16,8 +17,13 @@ module JamRuby :class_name => "MusicSessionHistory", :foreign_key => "music_session_id") - validates_inclusion_of :rating, :in => 0..2, :allow_nil => true - after_save :track_user_progression + def self.latest_history(client_id) + self.where(:client_id => client_id) + .order('created_at DESC') + .limit(1) + .includes(:user) + .first + end def music_session_history @msh ||= JamRuby::MusicSessionHistory.find_by_music_session_id(self.music_session_id) @@ -104,10 +110,23 @@ module JamRuby self.perf_data.try(:uri) end - def track_user_progression - if self.rating == 0 - user.update_progression_field(:first_good_music_session_at) - end + def add_rating(rval, comment='') + rval = rval.to_i + self.rating = rval if 0 != rval + self.rating_comment = comment end + + MIN_SESSION_DURATION_RATING = 60 + + def should_rate_session? + (2 <= music_session_history.unique_users.all.count && + MIN_SESSION_DURATION_RATING < (Time.now - music_session_history.created_at).seconds) || + Rails.env.development? + end + + def good_rating? + 0 < self.rating.to_i + end + end end diff --git a/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb b/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb index 3d7713b86..be947eb67 100644 --- a/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb +++ b/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb @@ -8,6 +8,7 @@ describe MusicSessionUserHistory do let(:user_history2) { FactoryGirl.create(:music_session_user_history, :history => music_session.music_session_history, :user => some_user) } describe "create" do + pending it {user_history1.music_session_id.should == music_session.id } it {user_history1.created_at.should_not be_nil } it {user_history1.session_removed_at.should be_nil } @@ -15,28 +16,35 @@ describe MusicSessionUserHistory do describe "rating" do - describe "success" do - - before(:each) do - user_history1.update_attribute(:rating ,0) - end - - it { user_history1.errors.any?.should be_false} + it "success" do + user_history1.update_attribute(:rating, 1) + expect( user_history1.errors.any? ).to eq(false) end - describe "out of range" do - before(:each) do - user_history1.update_attribute(:rating, 3) - user_history1.save - end + it "out of range" do + user_history1.rating = 2 + user_history1.save + expect( user_history1.errors.any? ).to eq(true) + end - it { user_history1.errors.any?.should be_true} + it 'should rate success' do + users = [user_history1, user_history2] + Timecop.travel(Time.now + (MusicSessionUserHistory::MIN_SESSION_DURATION_RATING * 1.5).seconds) + expect( user_history1.should_rate_session? ).to eq(true) + Timecop.return + end + + it 'should rate fails' do + users = [user_history1] + expect( user_history1.should_rate_session? ).to eq(false) + users = [user_history1, user_history2] + expect( user_history2.should_rate_session? ).to eq(false) end end describe "end_history" do - + pending it "histories created at the same time" do user_history1.reload user_history2.reload diff --git a/update b/update index a5495fcb3..969b747c0 100755 --- a/update +++ b/update @@ -17,7 +17,7 @@ pushd pb popd echo "" -echo "updating database" +echo "updating ruby" echo "" pushd ruby bundle update diff --git a/web/app/assets/images/content/icon_thumbsdown_big_off.png b/web/app/assets/images/content/icon_thumbsdown_big_off.png new file mode 100644 index 000000000..bd7e0261b Binary files /dev/null and b/web/app/assets/images/content/icon_thumbsdown_big_off.png differ diff --git a/web/app/assets/images/content/icon_thumbsdown_big_on.png b/web/app/assets/images/content/icon_thumbsdown_big_on.png new file mode 100644 index 000000000..69438d595 Binary files /dev/null and b/web/app/assets/images/content/icon_thumbsdown_big_on.png differ diff --git a/web/app/assets/images/content/icon_thumbsup_big_off.png b/web/app/assets/images/content/icon_thumbsup_big_off.png new file mode 100644 index 000000000..00ecd4f00 Binary files /dev/null and b/web/app/assets/images/content/icon_thumbsup_big_off.png differ diff --git a/web/app/assets/images/content/icon_thumbsup_big_on.png b/web/app/assets/images/content/icon_thumbsup_big_on.png new file mode 100644 index 000000000..39bcc1a8a Binary files /dev/null and b/web/app/assets/images/content/icon_thumbsup_big_on.png differ diff --git a/web/app/assets/javascripts/ga.js b/web/app/assets/javascripts/ga.js index 11a96eed6..a2d30f72b 100644 --- a/web/app/assets/javascripts/ga.js +++ b/web/app/assets/javascripts/ga.js @@ -19,6 +19,11 @@ join : "Join" }; + var sessionQualityTypes = { + good : "Good", + poor : "Poor" + }; + var invitationTypes = { email : "Email", facebook : "Facebook", @@ -83,6 +88,7 @@ audioTest : "AudioTest", sessionCount : "SessionCount", sessionMusicians : "SessionMusicians", + sessionQuality : "SessionQuality", invite : "Invite", findSession : "FindSession", friendConnect : "Connect", @@ -174,6 +180,11 @@ context.ga('send', 'event', categories.sessionMusicians, joinOrCreate); } + function trackSessionQuality(goodOrPoor) { + assertOneOf(goodOrPoor, sessionQualityTypes); + context.ga('send', 'event', categories.sessionQuality, goodOrPoor); + } + function trackServiceInvitations(invitationType, numInvited) { assertOneOf(invitationType, invitationTypes); assertNumber(numInvited); @@ -271,6 +282,7 @@ var GA = {}; GA.Categories = categories; GA.SessionCreationTypes = sessionCreationTypes; + GA.SessionQualityTypes = sessionQualityTypes; GA.InvitationTypes = invitationTypes; GA.FriendConnectTypes = friendConnectTypes; GA.RecordingActions = recordingActions; @@ -281,6 +293,7 @@ GA.trackFTUECompletion = trackFTUECompletion; GA.trackSessionCount = trackSessionCount; GA.trackSessionMusicians = trackSessionMusicians; + GA.trackSessionQuality = trackSessionQuality; GA.trackServiceInvitations = trackServiceInvitations; GA.trackFindSessions = trackFindSessions; GA.virtualPageView = virtualPageView; diff --git a/web/app/assets/javascripts/hoverBand.js b/web/app/assets/javascripts/hoverBand.js index 7f85e0dbc..3d5115341 100644 --- a/web/app/assets/javascripts/hoverBand.js +++ b/web/app/assets/javascripts/hoverBand.js @@ -32,8 +32,8 @@ $.each(response.musicians, function(index, val) { var instrumentHtml = ''; - musicianHtml += ''; - musicianHtml += '' + val.name + ''; + musicianHtml += ''; + musicianHtml += '<' + val.name + ''; instrumentHtml = '
'; if (val.instruments) { // @FIXME: edge case for Test user that has no instruments? @@ -75,6 +75,8 @@ }); $(hoverSelector).append('

Band Detail

' + bandHtml); + + context.JK.bindProfileClickEvents(hoverSelector); configureActionButtons(response); }) .fail(function(xhr) { diff --git a/web/app/assets/javascripts/hoverFan.js b/web/app/assets/javascripts/hoverFan.js index a68f65204..7bf13ec1c 100644 --- a/web/app/assets/javascripts/hoverFan.js +++ b/web/app/assets/javascripts/hoverFan.js @@ -36,19 +36,21 @@ followingHtml += ''; } - var avatarUrl, profilePath; + var avatarUrl, attrId, type; if (val.type === "band") { avatarUrl = context.JK.resolveBandAvatarUrl(val.photo_url); - profilePath = "bandProfile"; + attrId = "band-id"; + type = "band"; } else { avatarUrl = context.JK.resolveAvatarUrl(val.photo_url); - profilePath = "profile"; + attrId = "user-id"; + type = "musician"; } - followingHtml += ''; - followingHtml += '' + val.name + ''; + followingHtml += ''; + followingHtml += '' + val.name + ''; if (index % 2 > 0) { followingHtml += ''; @@ -76,6 +78,8 @@ }); $(hoverSelector).append('

Fan Detail

' + fanHtml); + + context.JK.bindProfileClickEvents(hoverSelector); configureActionButtons(response); }) .fail(function(xhr) { diff --git a/web/app/assets/javascripts/hoverMusician.js b/web/app/assets/javascripts/hoverMusician.js index 360bc664d..985da502b 100644 --- a/web/app/assets/javascripts/hoverMusician.js +++ b/web/app/assets/javascripts/hoverMusician.js @@ -42,19 +42,21 @@ followingHtml += ''; } - var avatarUrl, profilePath; + var avatarUrl, attrId, type; if (val.type === "band") { avatarUrl = context.JK.resolveBandAvatarUrl(val.photo_url); - profilePath = "bandProfile"; + attrId = "band-id"; + type = "band"; } else { avatarUrl = context.JK.resolveAvatarUrl(val.photo_url); - profilePath = "profile"; + attrId = "user-id"; + type = "musician"; } - followingHtml += ''; - followingHtml += '' + val.name + ''; + followingHtml += ''; + followingHtml += '' + val.name + ''; if (index % 2 > 0) { followingHtml += ''; @@ -101,6 +103,8 @@ }); $(hoverSelector).append('

Musician Detail

' + musicianHtml); + + context.JK.bindProfileClickEvents(hoverSelector); configureActionButtons(response); }) .fail(function(xhr) { diff --git a/web/app/assets/javascripts/rateSessionDialog.js b/web/app/assets/javascripts/rateSessionDialog.js new file mode 100644 index 000000000..644585bb5 --- /dev/null +++ b/web/app/assets/javascripts/rateSessionDialog.js @@ -0,0 +1,123 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.RateSessionDialog = function(app) { + var logger = context.JK.logger; + var dialogId = 'rate-session-dialog'; + var $scopeSelector = "[layout-id='rate-session-dialog']"; + var clientId = context.JK.JamServer.clientID; + + function reset() { + clientId = context.JK.JamServer.clientID; + $('#btn-rate-session-up', $scopeSelector).removeClass('selected'); + $('#btn-rate-session-down', $scopeSelector).removeClass('selected'); + $('#txt-rate-session-comment',"[layout-id='rate-session-dialog']").val(''); + } + + function showDialog() { + if (clientId) { + reset(); + $.ajax({ + type: "GET", + url: "/api/participant_histories/"+clientId + }).done(function (response) { + if (response && + response.hasOwnProperty('should_rate_session') && + true==response['should_rate_session']) { + app.layout.showDialog(dialogId); + } + }); + return true; + } + return false; + } + + function closeDialog() { + app.layout.closeDialog(dialogId); + } + + function getRating() { + if ($('#btn-rate-session-down', $scopeSelector).hasClass('selected')) { + return -1; + } else if ($('#btn-rate-session-up', $scopeSelector).hasClass('selected')) { + return 1; + } + return 0; + } + + function getComment() { + return $('#txt-rate-session-comment',"[layout-id='rate-session-dialog']").val(); + } + + function events() { + $('#btn-rate-session-cancel', $scopeSelector).click(function(evt) { + closeDialog(); + }); + $('#btn-rate-session-up', $scopeSelector).click(function(evt) { + if ($(this).hasClass('selected')) { + $(this).removeClass('selected') + } else { + $(this).addClass('selected'); + } + if ($('#btn-rate-session-down').hasClass('selected')) { + $('#btn-rate-session-down').removeClass('selected') + } + return false; + }); + $('#btn-rate-session-down', $scopeSelector).click(function(evt) { + if ($(this).hasClass('selected')) { + $(this).removeClass('selected') + } else { + $(this).addClass('selected'); + } + if ($('#btn-rate-session-up').hasClass('selected')) { + $('#btn-rate-session-up').removeClass('selected') + } + return false; + }); + $('#btn-rate-session-send', $scopeSelector).click(function(evt) { + var rr = getRating(), cc = getComment(); + if (0 == rr && 0 == cc.length) { + closeDialog(); + return false; + } + var url = "/api/participant_histories/"+clientId+"/rating"; + $.ajax({ + type: "POST", + url: url, + data: { rating: getRating(), comment: getComment() } + }).done(function (response) { + var qq = getRating(); + if (0 < qq) { + context.JK.GA.trackSessionQuality(context.JK.GA.SessionQualityTypes.good); + } else if (0 > qq){ + context.JK.GA.trackSessionQuality(context.JK.GA.SessionQualityTypes.poor); + } + closeDialog(); + }); + return false; + }); + } + + function beforeShow(data) { + // confirm user should see dialog + } + + function afterShow(data) { + } + + function initialize() { + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterShow' : afterShow + }; + app.bindDialog(dialogId, dialogBindings); + events(); + } + + this.initialize = initialize; + this.showDialog = showDialog; + }; +})(window,jQuery); diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index e667e27c0..8f9da6756 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -31,6 +31,7 @@ var playbackControls = null; var promptLeave = false; var backendMixerAlertThrottleTimer = null; + var rateSessionDialog = null; var rest = context.JK.Rest(); @@ -1305,15 +1306,27 @@ } } + function bailOut() { + promptLeave = false; + context.window.location = '/client#/home'; + } + function sessionLeave(evt) { evt.preventDefault(); - - promptLeave = false; - context.window.location = '/client#/home'; - + rateSession(); + bailOut(); return false; } + function rateSession() { + if (rateSessionDialog === null) { + rateSessionDialog = new context.JK.RateSessionDialog(context.JK.app); + rateSessionDialog.initialize(); + } + rateSessionDialog.showDialog(); + return true; + } + function sessionResync(evt) { evt.preventDefault(); var response = context.jamClient.SessionAudioResync(); diff --git a/web/app/assets/javascripts/web/recordings.js b/web/app/assets/javascripts/web/recordings.js index 1fecac65b..c83b42007 100644 --- a/web/app/assets/javascripts/web/recordings.js +++ b/web/app/assets/javascripts/web/recordings.js @@ -94,6 +94,7 @@ $(".landing-comment-scroller").prepend(commentHtml); } + context.JK.bindProfileClickEvents(); context.JK.bindHoverEvents(); } diff --git a/web/app/assets/javascripts/web/sessions.js b/web/app/assets/javascripts/web/sessions.js index c31bd62c7..66fadcbfb 100644 --- a/web/app/assets/javascripts/web/sessions.js +++ b/web/app/assets/javascripts/web/sessions.js @@ -49,6 +49,7 @@ $(".landing-comment-scroller").prepend(commentHtml); } + context.JK.bindProfileClickEvents(); context.JK.bindHoverEvents(); } @@ -129,7 +130,6 @@ $("#btnLike").click(like); $playButton.trigger('click'); - pollForUpdates(musicSessionId); } diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index cd389c60f..387352b0d 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -727,4 +727,26 @@ table.vu td { #update-session-invite-musicians { margin: 10px; -} \ No newline at end of file +} + +.rate-thumbsup { + width:64px; + height:64px; + display:inline-block; + background-image:url('/assets/content/icon_thumbsup_big_off.png'); +} + +.rate-thumbsup.selected { + background-image:url('/assets/content/icon_thumbsup_big_on.png'); +} + +.rate-thumbsdown { + width:64px; + height:64px; + display:inline-block; + background-image:url('/assets/content/icon_thumbsdown_big_off.png'); +} + +.rate-thumbsdown.selected { + background-image:url('/assets/content/icon_thumbsdown_big_on.png'); +} diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index a7d96b3d1..d832b68a0 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -151,16 +151,27 @@ class ApiMusicSessionsController < ApiController end def participant_rating - @history = MusicSessionUserHistory.find(params[:id]) - @history.rating = params[:rating] - @history.save + if @history = MusicSessionUserHistory.latest_history(params[:client_id]) + if request.post? + @history.add_rating(params[:rating], params[:comment]) + @history.save - if @history.errors.any? - response.status = :unprocessable_entity - respond_with @history - else - render :json => {}, :status => :ok + if @history.errors.any? + response.status = :unprocessable_entity + respond_with @history + else + if @history.good_rating? && @history.user.first_good_music_session_at.nil? + @history.user.first_good_music_session_at = Time.now + @history.user.save + end + render :json => {}, :status => :ok + end + elsif request.get? + render :json => { :should_rate_session => @history.should_rate_session? }, :status => :ok end + else + render :json => { :message => ValidationMessages::SESSION_NOT_FOUND }, :status => 404 + end end def track_index diff --git a/web/app/views/clients/_rateSession.html.erb b/web/app/views/clients/_rateSession.html.erb new file mode 100644 index 000000000..fa87832f5 --- /dev/null +++ b/web/app/views/clients/_rateSession.html.erb @@ -0,0 +1,17 @@ +
+ +
+ <%= image_tag "shared/icon_session.png", {:height => 19, :width => 19, :class => "content-icon"} %> +

please rate your session

+
+
+
+       +

+ +

+ SEND FEEDBACK   NOT NOW, THANKS +
+
+ +
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index f5352a70e..28836cda8 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -21,6 +21,7 @@ <%= render "clients/gear/gear_wizard" %> <%= render "terms" %> <%= render "leaveSessionWarning" %> +<%= render "rateSession" %> <%= render "alert" %> <%= render "sidebar" %> <%= render "createSession" %> diff --git a/web/app/views/shared/_comments.html.erb b/web/app/views/shared/_comments.html.erb index 03f348550..a0057319f 100644 --- a/web/app/views/shared/_comments.html.erb +++ b/web/app/views/shared/_comments.html.erb @@ -17,11 +17,13 @@