* VRFS-734 - adding all user progression tracking that is possible currently
This commit is contained in:
parent
6518e3a009
commit
f38ca2721a
|
|
@ -72,3 +72,4 @@ isp_score_batch.sql
|
|||
crash_dumps.sql
|
||||
crash_dumps_idx.sql
|
||||
music_sessions_user_history_add_session_removed_at.sql
|
||||
user_progress_tracking.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
-- tracks how users are progessing through the site: https://jamkazam.atlassian.net/wiki/pages/viewpage.action?pageId=3375145
|
||||
|
||||
alter table music_sessions_user_history add column max_concurrent_connections integer;
|
||||
alter table music_sessions_user_history add column rating integer;
|
||||
alter table users add column last_failed_certified_gear_at TIMESTAMP;
|
||||
alter table users add column last_failed_certified_gear_reason varchar(256);
|
||||
alter table users add column first_downloaded_client_at TIMESTAMP;
|
||||
alter table users add column first_ran_client_at TIMESTAMP;
|
||||
alter table users add column first_certified_gear_at TIMESTAMP;
|
||||
alter table users add column first_music_session_at TIMESTAMP;
|
||||
alter table users add column first_real_music_session_at TIMESTAMP;
|
||||
alter table users add column first_good_music_session_at TIMESTAMP;
|
||||
alter table users add column first_invited_at TIMESTAMP;
|
||||
alter table users add column first_friended_at TIMESTAMP;
|
||||
alter table users add column first_social_promoted_at TIMESTAMP;
|
||||
|
|
@ -263,8 +263,7 @@ SQL
|
|||
if result.cmd_tuples == 1
|
||||
# music session deleted!
|
||||
@log.debug("deleted music session #{previous_music_session_id}")
|
||||
JamRuby::MusicSessionHistory.removed_music_session(user_id,
|
||||
previous_music_session_id)
|
||||
JamRuby::MusicSessionHistory.removed_music_session(previous_music_session_id)
|
||||
elsif 1 < result.cmd_tuples
|
||||
msg = "music_sessions table data integrity violation; multiple rows found with music_session_id=#{previous_music_session_id}"
|
||||
@log.error(msg)
|
||||
|
|
@ -296,6 +295,7 @@ SQL
|
|||
raise ActiveRecord::Rollback
|
||||
else
|
||||
blk.call(db_conn, connection) unless blk.nil?
|
||||
user.update_progression_field(:first_music_session_at)
|
||||
MusicSessionUserHistory.save(music_session_id, user_id, client_id)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ module JamRuby
|
|||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id", :inverse_of => :inverse_friendships
|
||||
belongs_to :friend, :class_name => "JamRuby::User", :foreign_key => "friend_id", :inverse_of => :friendships
|
||||
|
||||
after_save :track_user_progression
|
||||
|
||||
def self.save(user_id, friend_id)
|
||||
friendship = Friendship.where("user_id='#{user_id}' AND friend_id='#{friend_id}'")
|
||||
|
||||
|
|
@ -60,5 +62,9 @@ module JamRuby
|
|||
friends = friends.limit(options[:limit])
|
||||
return friends
|
||||
end
|
||||
|
||||
def track_user_progression
|
||||
self.user.update_progression_field(:first_friended_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -22,12 +22,18 @@ module JamRuby
|
|||
validate :not_accepted_twice
|
||||
validate :can_invite?
|
||||
|
||||
after_save :track_user_progression
|
||||
|
||||
# ensure invitation code is always created
|
||||
before_validation(:on => :create) do
|
||||
self.invitation_code = SecureRandom.urlsafe_base64 if self.invitation_code.nil?
|
||||
self.sender_id = nil if self.sender_id.blank? # this coercion was done just to make activeadmin work
|
||||
end
|
||||
|
||||
def track_user_progression
|
||||
self.sender.update_progression_field(:first_invited_at) unless self.sender.nil?
|
||||
end
|
||||
|
||||
def self.index(user)
|
||||
return InvitedUser.where(:sender_id => user).order(:updated_at)
|
||||
end
|
||||
|
|
@ -60,7 +66,5 @@ module JamRuby
|
|||
def not_accepted_twice
|
||||
errors.add(:accepted, "you can only accept an invitation once") if accepted_twice
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ module JamRuby
|
|||
after_save :require_at_least_one_genre, :limit_max_genres
|
||||
|
||||
after_destroy do |obj|
|
||||
JamRuby::MusicSessionHistory.removed_music_session(obj.user_id, obj.id)
|
||||
JamRuby::MusicSessionHistory.removed_music_session(obj.id)
|
||||
end
|
||||
|
||||
validates :description, :presence => true, :no_profanity => true
|
||||
|
|
|
|||
|
|
@ -67,14 +67,29 @@ module JamRuby
|
|||
session_history.save!
|
||||
end
|
||||
|
||||
def self.removed_music_session(user_id, session_id)
|
||||
def end_history
|
||||
self.update_attribute(:session_removed_at, Time.now)
|
||||
|
||||
|
||||
# ensure all user histories are closed
|
||||
music_session_user_histories.each do |music_session_user_history|
|
||||
music_session_user_history.end_history
|
||||
|
||||
# then update any users that need their user progress updated
|
||||
if music_session_user_history.duration_minutes > 15 && music_session_user_history.max_concurrent_connections >= 3
|
||||
music_session_user_history.user.update_progression_field(:first_real_music_session_at)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def self.removed_music_session(session_id)
|
||||
hist = self
|
||||
.where(:user_id => user_id)
|
||||
.where(:music_session_id => session_id)
|
||||
.limit(1)
|
||||
.first
|
||||
hist.update_attribute(:session_removed_at, Time.now) if hist
|
||||
JamRuby::MusicSessionUserHistory.removed_music_session(user_id, session_id)
|
||||
|
||||
hist.end_history if hist
|
||||
end
|
||||
|
||||
def duration_minutes
|
||||
|
|
|
|||
|
|
@ -5,11 +5,16 @@ module JamRuby
|
|||
|
||||
self.primary_key = 'id'
|
||||
|
||||
attr_accessible :max_concurrent_connections, :session_removed_at, :rating
|
||||
|
||||
belongs_to(:user,
|
||||
:class_name => "JamRuby::User",
|
||||
:foreign_key => "user_id",
|
||||
:inverse_of => :music_session_user_histories)
|
||||
|
||||
validates_inclusion_of :rating, :in => 0..2, :allow_nil => true
|
||||
after_save :track_user_progression
|
||||
|
||||
def music_session_history
|
||||
@msh ||= JamRuby::MusicSessionHistory.find_by_music_session_id(self.music_session_id)
|
||||
end
|
||||
|
|
@ -41,12 +46,60 @@ module JamRuby
|
|||
.where(:music_session_id => session_id)
|
||||
.limit(1)
|
||||
.first
|
||||
hist.update_attribute(:session_removed_at, Time.now) if hist
|
||||
hist.end_history if hist
|
||||
end
|
||||
|
||||
def end_history
|
||||
self.session_removed_at = Time.now if self.session_removed_at.nil?
|
||||
|
||||
self.update_attributes(:session_removed_at => self.session_removed_at, :max_concurrent_connections => determine_max_concurrent)
|
||||
end
|
||||
|
||||
# figures out what the peak amount of other clients the user saw while playing at any given time.
|
||||
# we use the database to get all other connections that occurred while their this user was connected,
|
||||
# and then sort through them using custom logic to increase/decrease the count as people come and go
|
||||
def determine_max_concurrent
|
||||
overlapping_connections = MusicSessionUserHistory.where("music_session_id = ? AND
|
||||
((created_at >= ? AND (session_removed_at is NULL OR session_removed_at <= ?)) OR
|
||||
(created_at <= ? AND (session_removed_at is NULL OR session_removed_at >= ?)))",
|
||||
self.music_session_id, self.created_at, self.session_removed_at, self.created_at, self.created_at).select('id, created_at, session_removed_at').order(:created_at)
|
||||
in_out_times = []
|
||||
overlapping_connections.each do |connection|
|
||||
in_out_times.push([connection.created_at.nil? ? Time.at(0) : connection.created_at, true])
|
||||
in_out_times.push([connection.session_removed_at.nil? ? Time.new(3000) : connection.session_removed_at, false]) #helpful to get rid of nulls for sorting
|
||||
end
|
||||
|
||||
count_concurrent(in_out_times)
|
||||
end
|
||||
|
||||
def count_concurrent(in_out_times)
|
||||
in_out_times.sort!
|
||||
|
||||
max_concurrent = 0
|
||||
concurrent = 0
|
||||
|
||||
in_out_times.each do |hit|
|
||||
if hit[1]
|
||||
concurrent = concurrent + 1
|
||||
else
|
||||
concurrent = concurrent - 1
|
||||
end
|
||||
|
||||
if concurrent > max_concurrent
|
||||
max_concurrent = concurrent
|
||||
end
|
||||
end
|
||||
max_concurrent
|
||||
end
|
||||
|
||||
def perf_uri
|
||||
self.perf_data.try(:uri)
|
||||
end
|
||||
|
||||
|
||||
def track_user_progression
|
||||
if self.rating == 0
|
||||
user.update_progression_field(:first_good_music_session_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module JamRuby
|
|||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_s3_path, :photo_url, :crop_selection
|
||||
|
||||
# updating_password corresponds to a lost_password
|
||||
attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar
|
||||
attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field
|
||||
|
||||
# authorizations (for facebook, etc -- omniauth)
|
||||
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
|
||||
|
|
@ -133,6 +133,24 @@ module JamRuby
|
|||
validate :email_case_insensitive_uniqueness
|
||||
validate :update_email_case_insensitive_uniqueness, :if => :updating_email
|
||||
|
||||
def user_progression_fields
|
||||
@user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_social_promoted_at" ]
|
||||
end
|
||||
|
||||
def update_progression_field(field_name, time = DateTime.now)
|
||||
@updating_progression_field = true
|
||||
if self[field_name].nil?
|
||||
self[field_name] = time
|
||||
self.save
|
||||
end
|
||||
end
|
||||
|
||||
def failed_qualification(reason)
|
||||
self.last_failed_certified_gear_at = DateTime.now
|
||||
self.last_failed_certified_gear_reason = reason
|
||||
self.save
|
||||
end
|
||||
|
||||
def validate_musician_instruments
|
||||
errors.add(:musician_instruments, ValidationMessages::INSTRUMENT_MINIMUM_NOT_MET) if !administratively_created && musician && musician_instruments.length == 0
|
||||
errors.add(:musician_instruments, ValidationMessages::INSTRUMENT_LIMIT_EXCEEDED) if !administratively_created && musician && musician_instruments.length > 5
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Friendship do
|
||||
|
||||
let(:user1) { FactoryGirl.create(:user) }
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
Friendship.save_using_models(user1, user2)
|
||||
end
|
||||
|
||||
it "can create two-way friendship" do
|
||||
user1.friends?(user2).should be_true
|
||||
user2.friends?(user1).should be_true
|
||||
end
|
||||
|
||||
it "should track user progression" do
|
||||
user1.first_friended_at.should_not be_nil
|
||||
user2.first_friended_at.should_not be_nil
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -18,6 +18,8 @@ describe InvitedUser do
|
|||
invited_user.sender.should_not be_nil
|
||||
invited_user.note.should be_nil
|
||||
invited_user.invited_by_administrator?.should be_false
|
||||
#invited_user.sender.reload
|
||||
invited_user.sender.first_invited_at.should_not be_nil
|
||||
end
|
||||
|
||||
it 'create an invitation from admin-user' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe MusicSessionUserHistory do
|
||||
|
||||
let(:some_user) { FactoryGirl.create(:user) }
|
||||
let(:music_session) { FactoryGirl.create(:music_session) }
|
||||
let(:history) { FactoryGirl.create(:music_session_history, :music_session => music_session) }
|
||||
let(:user_history1) { FactoryGirl.create(:music_session_user_history, :history => history, :user => music_session.creator) }
|
||||
let(:user_history2) { FactoryGirl.create(:music_session_user_history, :history => history, :user => some_user) }
|
||||
|
||||
describe "create" do
|
||||
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 }
|
||||
end
|
||||
|
||||
describe "rating" do
|
||||
|
||||
describe "success" do
|
||||
|
||||
before(:each) do
|
||||
user_history1.update_attribute(:rating ,0)
|
||||
end
|
||||
|
||||
it { user_history1.errors.any?.should be_false}
|
||||
end
|
||||
|
||||
describe "out of range" do
|
||||
before(:each) do
|
||||
user_history1.update_attribute(:rating, 3)
|
||||
user_history1.save
|
||||
end
|
||||
|
||||
it { user_history1.errors.any?.should be_true}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "end_history" do
|
||||
|
||||
it "histories created at the same time" do
|
||||
user_history1.reload
|
||||
user_history2.reload
|
||||
user_history1.end_history
|
||||
user_history1.max_concurrent_connections.should == 2
|
||||
end
|
||||
|
||||
it "history2 ends before history1 starts" do
|
||||
user_history2.created_at = user_history1.created_at - 2
|
||||
user_history2.session_removed_at = user_history1.created_at - 1
|
||||
user_history2.save.should be_true
|
||||
user_history1.reload
|
||||
user_history2.reload
|
||||
user_history1.end_history
|
||||
user_history1.max_concurrent_connections.should == 1
|
||||
end
|
||||
|
||||
it "history2 starts after history1 starts" do
|
||||
user_history2.created_at = user_history1.created_at + 1
|
||||
user_history2.session_removed_at = user_history1.created_at + 2
|
||||
user_history2.save.should be_true
|
||||
user_history1.reload
|
||||
user_history2.reload
|
||||
user_history1.end_history
|
||||
user_history1.max_concurrent_connections.should == 1
|
||||
end
|
||||
|
||||
it "history2 is within bounds of history1 " do
|
||||
user_history1.session_removed_at = user_history1.created_at + 3
|
||||
user_history2.created_at = user_history1.created_at + 1
|
||||
user_history2.session_removed_at = user_history1.created_at + 2
|
||||
user_history1.save.should be_true
|
||||
user_history2.save.should be_true
|
||||
user_history1.reload
|
||||
user_history2.reload
|
||||
user_history1.end_history
|
||||
user_history1.max_concurrent_connections.should == 2
|
||||
end
|
||||
|
||||
it "two histories with same user within bounds of history1" do
|
||||
user_history3 = FactoryGirl.create(:music_session_user_history, :history => history, :user => some_user)
|
||||
|
||||
# if user2 comes and goes 2 times while user one is there, it shouldn't be a false 3
|
||||
user_history1.session_removed_at = user_history1.created_at + 5
|
||||
user_history2.created_at = user_history1.created_at + 1
|
||||
user_history2.session_removed_at = user_history1.created_at + 2
|
||||
user_history3.created_at = user_history1.created_at + 3
|
||||
user_history3.session_removed_at = user_history1.created_at + 4
|
||||
user_history1.save.should be_true
|
||||
user_history2.save.should be_true
|
||||
user_history3.save.should be_true
|
||||
user_history1.reload
|
||||
user_history2.reload
|
||||
user_history3.reload
|
||||
user_history1.end_history
|
||||
user_history1.max_concurrent_connections.should == 2
|
||||
end
|
||||
|
||||
it "two histories with different user within bounds of history1" do
|
||||
third_user = FactoryGirl.create(:user);
|
||||
|
||||
user_history3 = FactoryGirl.create(:music_session_user_history, :history => history, :user => third_user)
|
||||
|
||||
# if user2 comes and goes 2 times while user one is there, it shouldn't be a false 3
|
||||
user_history1.session_removed_at = user_history1.created_at + 5
|
||||
user_history2.created_at = user_history1.created_at + 1
|
||||
user_history2.session_removed_at = user_history1.created_at + 2
|
||||
user_history3.created_at = user_history1.created_at + 3
|
||||
user_history3.session_removed_at = user_history1.created_at + 4
|
||||
user_history1.save.should be_true
|
||||
user_history2.save.should be_true
|
||||
user_history3.save.should be_true
|
||||
user_history1.reload
|
||||
user_history2.reload
|
||||
user_history3.reload
|
||||
user_history1.end_history
|
||||
user_history1.max_concurrent_connections.should == 2
|
||||
end
|
||||
|
||||
it "two overlapping histories with different user within bounds of history1" do
|
||||
third_user = FactoryGirl.create(:user);
|
||||
|
||||
user_history3 = FactoryGirl.create(:music_session_user_history, :history => history, :user => third_user)
|
||||
|
||||
# if user2 comes and goes 2 times while user one is there, it shouldn't be a false 3
|
||||
user_history1.session_removed_at = user_history1.created_at + 5
|
||||
user_history2.created_at = user_history1.created_at + 1
|
||||
user_history2.session_removed_at = user_history1.created_at + 3
|
||||
user_history3.created_at = user_history1.created_at + 2
|
||||
user_history3.session_removed_at = user_history1.created_at + 4
|
||||
user_history1.save.should be_true
|
||||
user_history2.save.should be_true
|
||||
user_history3.save.should be_true
|
||||
user_history1.reload
|
||||
user_history2.reload
|
||||
user_history3.reload
|
||||
user_history1.end_history
|
||||
user_history1.max_concurrent_connections.should == 3
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -243,6 +243,23 @@ describe User do
|
|||
its(:remember_token) { should_not be_blank }
|
||||
end
|
||||
|
||||
describe "user progression only touches once" do
|
||||
it "allows first touch" do
|
||||
@user.update_progression_field (:first_downloaded_client_at)
|
||||
@user.errors.any?.should be_false
|
||||
@user.first_downloaded_client_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "ignores second touch" do
|
||||
time = DateTime.now - 1
|
||||
@user.update_progression_field(:first_downloaded_client_at, time)
|
||||
first_value = @user.first_downloaded_client_at
|
||||
@user.update_progression_field(:first_downloaded_client_at)
|
||||
@user.errors.any?.should be_false
|
||||
@user.first_downloaded_client_at.should == first_value
|
||||
end
|
||||
end
|
||||
|
||||
describe "authenticate (class-instance)" do
|
||||
before { @user.email_confirmed=true; @user.save }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
context.JK.FtueWizard = function(app) {
|
||||
context.JK.FtueWizard.latencyTimeout = true;
|
||||
context.JK.FtueWizard.latencyMS = Number.MAX_VALUE;
|
||||
var rest = context.JK.Rest();
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var jamClient = context.jamClient;
|
||||
|
|
@ -110,10 +111,14 @@
|
|||
logger.debug(latencyMS + " is <= 20. Setting FTUE status to true");
|
||||
ftueSave(true); // Save the profile
|
||||
context.jamClient.FTUESetStatus(true); // No FTUE wizard next time
|
||||
rest.userCertifiedGear({success:true});
|
||||
|
||||
// notify anyone curious about how it went
|
||||
$('div[layout-id=ftue]').trigger('ftue_success');
|
||||
}
|
||||
else {
|
||||
rest.userCertifiedGear({success:false, reason:"latency=" + latencyMS});
|
||||
}
|
||||
|
||||
updateGauge();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,6 +213,44 @@
|
|||
return deferred;
|
||||
}
|
||||
|
||||
function userDownloadedClient(options) {
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/users/progression/downloaded_client",
|
||||
processData: false
|
||||
});
|
||||
}
|
||||
|
||||
function userCertifiedGear(options) {
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/users/progression/certified_gear",
|
||||
processData: false,
|
||||
data: JSON.stringify({
|
||||
success: options.success,
|
||||
reason: options.reason
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function userSocialPromoted(options) {
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/users/progression/social_promoted",
|
||||
processData: false
|
||||
});
|
||||
}
|
||||
|
||||
function signout() {
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
|
|
@ -245,6 +283,9 @@
|
|||
this.serverHealthCheck = serverHealthCheck;
|
||||
this.acceptFriendRequest = acceptFriendRequest;
|
||||
this.signout = signout;
|
||||
this.userDownloadedClient = userDownloadedClient;
|
||||
this.userCertifiedGear = userCertifiedGear;
|
||||
this.userSocialPromoted = userSocialPromoted;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
var download = $(context._.template($('#client-download-link').html(), options, { variable: 'data' }));
|
||||
|
||||
download.find('a').data('platform', platform).click(function() {
|
||||
rest.userDownloadedClient();
|
||||
context.JK.GA.trackDownload($(this).data('platform'));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,4 +36,13 @@ class ApiController < ApplicationController
|
|||
|
||||
@user = User.find(params[:id])
|
||||
end
|
||||
|
||||
def optional_auth_user
|
||||
if current_user.nil?
|
||||
@user = nil
|
||||
else
|
||||
auth_user
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -109,6 +109,19 @@ class ApiMusicSessionsController < ApiController
|
|||
respond_with @connection, responder: ApiResponder
|
||||
end
|
||||
|
||||
def participant_rating
|
||||
@history = MusicSessionUserHistory.find(params[:id])
|
||||
@history.rating = params[:rating]
|
||||
@history.save
|
||||
|
||||
if @history.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @history
|
||||
else
|
||||
render :json => {}, :status => :ok
|
||||
end
|
||||
end
|
||||
|
||||
def lookup_session
|
||||
@music_session = MusicSession.find(params[:id])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ class ApiUsersController < ApiController
|
|||
:notification_index, :notification_destroy, # notifications
|
||||
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
|
||||
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
|
|
@ -529,6 +528,48 @@ class ApiUsersController < ApiController
|
|||
|
||||
end
|
||||
|
||||
|
||||
# user progression tracking
|
||||
def downloaded_client
|
||||
@user = current_user
|
||||
@user.update_progression_field(:first_downloaded_client_at)
|
||||
|
||||
if @user.errors.any?
|
||||
respond_with @user, :status => :unprocessable_entity
|
||||
return
|
||||
end
|
||||
render :json => {}, :status => 200
|
||||
|
||||
end
|
||||
# user progression tracking
|
||||
def qualified_gear
|
||||
@user = current_user
|
||||
if params[:success]
|
||||
@user.update_progression_field(:first_certified_gear_at)
|
||||
else
|
||||
@user.failed_qualification(params[:reason])
|
||||
end
|
||||
|
||||
if @user.errors.any?
|
||||
respond_with @user, :status => :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
|
||||
# user progression tracking
|
||||
def social_promoted
|
||||
@user = current_user
|
||||
@user.update_progression_field(:first_social_promoted_at)
|
||||
|
||||
if @user.errors.any?
|
||||
respond_with @user, :status => :unprocessable_entity
|
||||
return
|
||||
end
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
|
||||
###################### RECORDINGS #######################
|
||||
# def recording_index
|
||||
# @recordings = User.recording_index(current_user, params[:id])
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ class SessionsController < ApplicationController
|
|||
@login_error = true
|
||||
render 'new', :layout => "landing"
|
||||
else
|
||||
|
||||
if jkclient_agent?
|
||||
user.update_progression_field(:first_ran_client_at)
|
||||
end
|
||||
|
||||
@session_only_cookie = !jkclient_agent? && !params[:user].nil? && 0 == params[:user][:remember_me].to_i
|
||||
complete_sign_in user
|
||||
end
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ SampleApp::Application.routes.draw do
|
|||
match '/sessions/:id/tracks/:track_id' => 'api_music_sessions#track_show', :via => :get, :as => 'api_session_track_detail'
|
||||
match '/sessions/:id/tracks/:track_id' => 'api_music_sessions#track_destroy', :via => :delete
|
||||
|
||||
match '/participant_histories/:id/rating' => 'api_music_sessions#participant_rating', :via => :post
|
||||
|
||||
# genres
|
||||
match '/genres' => 'api_genres#index', :via => :get
|
||||
|
||||
|
|
@ -174,6 +176,11 @@ SampleApp::Application.routes.draw do
|
|||
match '/users/:id/avatar' => 'api_users#delete_avatar', :via => :delete
|
||||
match '/users/:id/filepicker_policy' => 'api_users#generate_filepicker_policy', :via => :get
|
||||
|
||||
# user progression
|
||||
match '/users/progression/downloaded_client' => 'api_users#downloaded_client', :via => :post
|
||||
match '/users/progression/certified_gear' => 'api_users#qualified_gear', :via => :post
|
||||
match '/users/progression/social_promoted' => 'api_users#social_promoted', :via => :post
|
||||
|
||||
# user recordings
|
||||
# match '/users/:id/recordings' => 'api_users#recording_index', :via => :get
|
||||
# match '/users/:id/recordings/:recording_id' => 'api_users#recording_show', :via => :get, :as => 'api_recording_detail'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
require 'recaptcha'
|
||||
class MusicSessionManager < BaseManager
|
||||
class
|
||||
MusicSessionManager < BaseManager
|
||||
|
||||
include Recaptcha::Verify
|
||||
|
||||
|
|
|
|||
|
|
@ -592,6 +592,20 @@ describe "Music Session API ", :type => :api do
|
|||
music_session_perf_data.music_session_user_history.should == MusicSessionUserHistory.find_by_client_id(client.client_id)
|
||||
end
|
||||
|
||||
it "rating" do
|
||||
user = FactoryGirl.create(:user)
|
||||
client = FactoryGirl.create(:connection, :user => user)
|
||||
music_session = FactoryGirl.create(:music_session, :creator => user, :description => "My Session")
|
||||
msuh = FactoryGirl.create(:music_session_user_history, :music_session_id => music_session.id, :client_id => client.client_id, :user_id => user.id)
|
||||
msuh.rating.should be_nil
|
||||
login(user)
|
||||
post "/api/participant_histories/#{msuh.id}/rating.json", { :rating => 0 }.to_json, "CONTENT_TYPE" => "application/json"
|
||||
last_response.status.should == 200
|
||||
msuh.reload
|
||||
msuh.rating.should == 0
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# user progression is achieved by different aspects of the code working together in a cross-cutting fashion.
|
||||
# due to this, it's nice to have a single place where all the parts of user progression are tested
|
||||
# https://jamkazam.atlassian.net/wiki/pages/viewpage.action?pageId=3375145
|
||||
|
||||
describe "User Progression", :type => :api do
|
||||
|
||||
include Rack::Test::Methods
|
||||
|
||||
subject { page }
|
||||
|
||||
def login(user)
|
||||
post '/sessions', "session[email]" => user.email, "session[password]" => user.password
|
||||
rack_mock_session.cookie_jar["remember_token"].should == user.remember_token
|
||||
end
|
||||
|
||||
describe "user progression" do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:defopts) { { :description => "a session", :fan_chat => true, :fan_access => true, :approval_required => false, :genres => ["classical"], :musician_access => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono"}], :legal_terms => true, :intellectual_property => true} }
|
||||
|
||||
before do
|
||||
login(user)
|
||||
end
|
||||
|
||||
it "downloaded_client" do
|
||||
user.first_downloaded_client_at.should be_nil
|
||||
post "/api/users/progression/downloaded_client"
|
||||
last_response.status.should eql(200)
|
||||
JSON.parse(last_response.body).should eql({ })
|
||||
user.reload
|
||||
user.first_downloaded_client_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "downloaded_client twice" do
|
||||
post "/api/users/progression/downloaded_client"
|
||||
user.reload
|
||||
first = user.first_downloaded_client_at
|
||||
post "/api/users/progression/downloaded_client"
|
||||
user.reload
|
||||
user.first_downloaded_client_at.should == first
|
||||
end
|
||||
|
||||
it "qualified gear" do
|
||||
user.first_certified_gear_at.should be_nil
|
||||
post "/api/users/progression/certified_gear.json", { :success => true}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(200)
|
||||
JSON.parse(last_response.body).should eql({ })
|
||||
user.reload
|
||||
user.first_certified_gear_at.should_not be_nil
|
||||
user.last_failed_certified_gear_at.should be_nil
|
||||
user.last_failed_certified_gear_reason.should be_nil
|
||||
end
|
||||
|
||||
it "failed qualified gear" do
|
||||
user.first_certified_gear_at.should be_nil
|
||||
post "/api/users/progression/certified_gear.json", { :success => false, :reason => "latency=30"}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(200)
|
||||
JSON.parse(last_response.body).should eql({ })
|
||||
user.reload
|
||||
user.first_certified_gear_at.should be_nil
|
||||
user.last_failed_certified_gear_at.should_not be_nil
|
||||
user.last_failed_certified_gear_reason.should == "latency=30"
|
||||
end
|
||||
|
||||
|
||||
it "social promoted" do
|
||||
user.first_social_promoted_at.should be_nil
|
||||
post "/api/users/progression/social_promoted"
|
||||
last_response.status.should eql(200)
|
||||
JSON.parse(last_response.body).should eql({ })
|
||||
user.reload
|
||||
user.first_social_promoted_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "joined any session" do
|
||||
user.first_music_session_at.should be_nil
|
||||
client = FactoryGirl.create(:connection, :user => user, :ip_address => "1.1.1.1", :client_id => "1.1")
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
user.reload
|
||||
user.first_music_session_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "ran client" do
|
||||
user.first_ran_client_at.should be_nil
|
||||
# change user-agent to look like the real client, and see ran_client flag set
|
||||
post '/sessions', {"session[email]" => user.email, "session[password]" => user.password}, { "HTTP_USER_AGENT" => " JamKazam " }
|
||||
user.reload
|
||||
user.first_ran_client_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "real session" do
|
||||
# to make a real session, we need a session at least 15 minutes long and containting 3 concurrent users
|
||||
|
||||
user2 = FactoryGirl.create(:user)
|
||||
user3 = FactoryGirl.create(:user)
|
||||
|
||||
user.first_real_music_session_at.should be_nil
|
||||
client = FactoryGirl.create(:connection, :user => user, :ip_address => "1.1.1.1", :client_id => "1_1")
|
||||
client2 = FactoryGirl.create(:connection, :user => user2, :ip_address => "1.1.1.2", :client_id => "1_2")
|
||||
client3 = FactoryGirl.create(:connection, :user => user3, :ip_address => "1.1.1.3", :client_id => "1_3")
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
music_session = JSON.parse(last_response.body)
|
||||
|
||||
login(user2)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
|
||||
login(user3)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client3.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
|
||||
# instrument the created_at of the music_history field to be at the beginning of time, so that we cross the 15 minute threshold of a 'real session
|
||||
history1 = MusicSessionUserHistory.where(:user_id => user.id, :music_session_id => music_session["id"]).first
|
||||
history2 = MusicSessionUserHistory.where(:user_id => user2.id, :music_session_id => music_session["id"]).first
|
||||
history3 = MusicSessionUserHistory.where(:user_id => user3.id, :music_session_id => music_session["id"]).first
|
||||
history1.should_not be_nil
|
||||
history2.should_not be_nil
|
||||
history3.should_not be_nil
|
||||
history1.created_at = Time.at(0)
|
||||
history2.created_at = Time.at(0)
|
||||
history3.created_at = Time.at(0)
|
||||
history1.save
|
||||
history2.save
|
||||
history3.save
|
||||
|
||||
# now log out of the session for all 3 users
|
||||
login(user)
|
||||
delete "/api/participants/#{client.client_id}.json", '', "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(204)
|
||||
login(user2)
|
||||
delete "/api/participants/#{client2.client_id}.json", '', "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(204)
|
||||
login(user3)
|
||||
delete "/api/participants/#{client3.client_id}.json", '', "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(204)
|
||||
|
||||
user.reload
|
||||
user2.reload
|
||||
user3.reload
|
||||
user.first_real_music_session_at.should_not be_nil
|
||||
user2.first_real_music_session_at.should_not be_nil
|
||||
user3.first_real_music_session_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "invites user" do
|
||||
user.first_invited_at.should be_nil
|
||||
post '/api/invited_users.json', {:email => 'tester@jamkazam.com', :note => "please join"}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
user.reload
|
||||
user.first_invited_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "good session" do
|
||||
user = FactoryGirl.create(:user)
|
||||
user.first_good_music_session_at.should be_nil
|
||||
|
||||
client = FactoryGirl.create(:connection, :user => user)
|
||||
music_session = FactoryGirl.create(:music_session, :creator => user, :description => "My Session")
|
||||
msuh = FactoryGirl.create(:music_session_user_history, :music_session_id => music_session.id, :client_id => client.client_id, :user_id => user.id)
|
||||
login(user)
|
||||
post "/api/participant_histories/#{msuh.id}/rating.json", { :rating => 0 }.to_json, "CONTENT_TYPE" => "application/json"
|
||||
|
||||
user.reload
|
||||
user.first_good_music_session_at.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
Loading…
Reference in New Issue