diff --git a/admin/Gemfile b/admin/Gemfile index c8b1d1772..8a47660de 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -116,3 +116,7 @@ group :test do gem 'simplecov-rcov' end +gem 'pry' +gem 'pry-remote' +gem 'pry-stack_explorer' +gem 'pry-debugger' diff --git a/admin/app/admin/affiliate_users.rb b/admin/app/admin/affiliate_users.rb new file mode 100644 index 000000000..b21d512eb --- /dev/null +++ b/admin/app/admin/affiliate_users.rb @@ -0,0 +1,31 @@ +ActiveAdmin.register JamRuby::User, :as => 'Referrals' do + + menu :label => 'Referrals', :parent => 'Affiliates' + + config.batch_actions = false + config.clear_action_items! + config.filters = false + + index do + column 'User' do |oo| link_to(oo.name, "http://www.jamkazam.com/client#/profile/#{oo.id}", {:title => oo.name}) end + column 'Email' do |oo| oo.email end + column 'Created' do |oo| oo.created_at end + column 'Partner' do |oo| oo.affiliate_referral.partner_name end + end + + controller do + + def scoped_collection + rel = end_of_association_chain + .includes([:affiliate_referral]) + .order('created_at DESC') + if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present? + qq = ['affiliate_referral_id = ?', ref_id] + else + qq = ['affiliate_referral_id IS NOT NULL'] + end + @users ||= rel.where(qq) + end + + end +end diff --git a/admin/app/admin/affiliates.rb b/admin/app/admin/affiliates.rb new file mode 100644 index 000000000..e517b3e12 --- /dev/null +++ b/admin/app/admin/affiliates.rb @@ -0,0 +1,49 @@ +ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do + + menu :label => 'Partners', :parent => 'Affiliates' + + config.sort_order = 'created_at DESC' + config.batch_actions = false + # config.clear_action_items! + config.filters = false + + form :partial => 'form' + + index do + column 'User' do |oo| link_to(oo.partner_user.name, "http://www.jamkazam.com/client#/profile/#{oo.partner_user.id}", {:title => oo.partner_user.name}) end + column 'Email' do |oo| oo.partner_user.email end + column 'Name' do |oo| oo.partner_name end + column 'Code' do |oo| oo.partner_code end + column 'Referral Count' do |oo| oo.referral_user_count end + # column 'Referrals' do |oo| link_to('View', admin_referrals_path(AffiliatePartner::PARAM_REFERRAL => oo.id)) end + default_actions + end + + controller do + + def show + redirect_to admin_referrals_path(AffiliatePartner::PARAM_REFERRAL => resource.id) + end + + def create + obj = AffiliatePartner.create_with_params(params[:jam_ruby_affiliate_partner]) + if obj.errors.present? + set_resource_ivar(obj) + render active_admin_template('new') + else + redirect_to admin_affiliates_path + end + end + + def update + obj = resource + vals = params[:jam_ruby_affiliate_partner] + obj.partner_name = vals[:partner_name] + obj.user_email = vals[:user_email] if vals[:user_email].present? + obj.save! + set_resource_ivar(obj) + render active_admin_template('show') + end + + end +end diff --git a/admin/app/admin/email_batch.rb b/admin/app/admin/email_batch.rb index d074fd0d4..e5743eeb8 100644 --- a/admin/app/admin/email_batch.rb +++ b/admin/app/admin/email_batch.rb @@ -97,12 +97,14 @@ ActiveAdmin.register JamRuby::EmailBatch, :as => 'Batch Emails' do def create batch = EmailBatch.create_with_params(params[:jam_ruby_email_batch]) - redirect_to admin_batch_email_path(batch.id) + set_resource_ivar(batch) + render active_admin_template('show') end def update resource.update_with_conflict_validation(params[:jam_ruby_email_batch]) - redirect_to admin_batch_email_path(resource.id) + set_resource_ivar(resource) + render active_admin_template('show') end end diff --git a/admin/app/models/admin_authorization.rb b/admin/app/models/admin_authorization.rb index 692d118f1..809991226 100644 --- a/admin/app/models/admin_authorization.rb +++ b/admin/app/models/admin_authorization.rb @@ -1,7 +1,13 @@ class AdminAuthorization < ActiveAdmin::AuthorizationAdapter def authorized?(action, subject = nil) - subject.is_a?(EmailBatch) && :update == action ? subject.can_run_batch? : true + if subject.is_a?(EmailBatch) && :update == action + subject.can_run_batch? + elsif subject.is_a?(AffiliatePartner) && :destroy == action + false + else + true + end end end diff --git a/admin/app/views/admin/affiliates/_form.html.erb b/admin/app/views/admin/affiliates/_form.html.erb new file mode 100644 index 000000000..a4f14416e --- /dev/null +++ b/admin/app/views/admin/affiliates/_form.html.erb @@ -0,0 +1,13 @@ +<%= semantic_form_for([:admin, resource], :url => resource.new_record? ? admin_affiliates_path : "/admin/affiliates/#{resource.id}") do |f| %> + <%= f.semantic_errors *f.object.errors.keys %> + <%= f.inputs do %> + <%= f.input(:user_email, :input_html => {:maxlength => 255}) %> + <%= f.input(:partner_name, :input_html => {:maxlength => 128}) %> + <% if resource.new_record? %> + <%= f.input(:partner_code, :input_html => {:maxlength => 128}) %> + <% else %> + <%= f.input(:partner_code, :input_html => {:maxlength => 128, :readonly => 'readonly'}) %> + <% end %> + <% end %> + <%= f.actions %> +<% end %> diff --git a/build b/build index 9b97f9176..6d408c180 100755 --- a/build +++ b/build @@ -66,7 +66,7 @@ DEB_SERVER=http://localhost:9010/apt-`uname -p` GEM_SERVER=http://localhost:9000/gems # if still going, then push all debs up - if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* ]]; then + if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* || "$GIT_BRANCH" == *release* ]]; then echo "" echo "PUSHING DB ARTIFACTS" @@ -129,7 +129,7 @@ GEM_SERVER=http://localhost:9000/gems popd > /dev/null else - echo "Skipping publish since branch is neither master or develop..." + echo "Skipping publish since branch is not master, develop, or release/*. branch is $GIT_BRANCH" fi fi diff --git a/db/manifest b/db/manifest index e8485d6cc..4d691b123 100755 --- a/db/manifest +++ b/db/manifest @@ -143,4 +143,6 @@ emails.sql email_batch.sql user_progress_tracking2.sql bands_did_session.sql +email_change_default_sender.sql +affiliate_partners.sql chat_messages.sql \ No newline at end of file diff --git a/db/up/affiliate_partners.sql b/db/up/affiliate_partners.sql new file mode 100644 index 000000000..67f7dfd97 --- /dev/null +++ b/db/up/affiliate_partners.sql @@ -0,0 +1,15 @@ +CREATE TABLE affiliate_partners ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + partner_name VARCHAR(128) NOT NULL, + partner_code VARCHAR(128) NOT NULL, + partner_user_id VARCHAR(64) NOT NULL, + user_email VARCHAR(255), + referral_user_count INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX affiliate_partners_code_idx ON affiliate_partners(partner_code); +CREATE INDEX affiliate_partners_user_idx ON affiliate_partners(partner_user_id); + +ALTER TABLE users ADD COLUMN affiliate_referral_id VARCHAR(64) REFERENCES affiliate_partners(id); diff --git a/db/up/email_change_default_sender.sql b/db/up/email_change_default_sender.sql new file mode 100644 index 000000000..e729980f0 --- /dev/null +++ b/db/up/email_change_default_sender.sql @@ -0,0 +1 @@ +ALTER TABLE email_batches ALTER column from_email SET DEFAULT 'noreply@jamkazam.com'::character varying; \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index ef426e9a8..f596d402d 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -140,6 +140,7 @@ require "jam_ruby/models/email_batch_set" require "jam_ruby/models/email_error" require "jam_ruby/app/mailers/async_mailer" require "jam_ruby/app/mailers/batch_mailer" +require "jam_ruby/models/affiliate_partner" include Jampb diff --git a/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb index 566cd7d22..e01846424 100644 --- a/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb @@ -9,7 +9,7 @@ module JamRuby class InvitedUserMailer < ActionMailer::Base include SendGrid - DEFAULT_SENDER = "support@jamkazam.com" + DEFAULT_SENDER = "noreply@jamkazam.com" default :from => DEFAULT_SENDER diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index f0909d7eb..cd7134591 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -11,7 +11,7 @@ layout "user_mailer" - DEFAULT_SENDER = "support@jamkazam.com" + DEFAULT_SENDER = "noreply@jamkazam.com" default :from => DEFAULT_SENDER diff --git a/ruby/lib/jam_ruby/lib/em_helper.rb b/ruby/lib/jam_ruby/lib/em_helper.rb index c310cf2bf..9c382733a 100644 --- a/ruby/lib/jam_ruby/lib/em_helper.rb +++ b/ruby/lib/jam_ruby/lib/em_helper.rb @@ -65,7 +65,7 @@ module JamWebEventMachine end def self.run - + return if defined?(Rails::Console) current = Thread.current Thread.new do run_em(current) diff --git a/ruby/lib/jam_ruby/models/affiliate_partner.rb b/ruby/lib/jam_ruby/models/affiliate_partner.rb new file mode 100644 index 000000000..6df9dc1a2 --- /dev/null +++ b/ruby/lib/jam_ruby/models/affiliate_partner.rb @@ -0,0 +1,44 @@ +class JamRuby::AffiliatePartner < ActiveRecord::Base + belongs_to :partner_user, :class_name => "JamRuby::User", :foreign_key => :partner_user_id + has_many :user_referrals, :class_name => "JamRuby::User", :foreign_key => :affiliate_referral_id + + attr_accessible :partner_name, :partner_code, :partner_user_id + + PARAM_REFERRAL = :ref + PARAM_COOKIE = :affiliate_ref + + PARTNER_CODE_REGEX = /^[#{Regexp.escape('abcdefghijklmnopqrstuvwxyz0123456789-._+,')}]+{2,128}$/i + + validates :user_email, format: {with: JamRuby::User::VALID_EMAIL_REGEX}, :if => :user_email + validates :partner_name, presence: true + validates :partner_code, presence: true, format: { with: PARTNER_CODE_REGEX } + validates :partner_user, presence: true + + def self.create_with_params(params={}) + oo = self.new + oo.partner_name = params[:partner_name].try(:strip) + oo.partner_code = params[:partner_code].try(:strip).try(:downcase) + oo.partner_user = User.where(:email => params[:user_email].try(:strip)).limit(1).first + oo.partner_user_id = oo.partner_user.try(:id) + oo.save + oo + end + + def self.coded_id(code=nil) + self.where(:partner_code => code).limit(1).pluck(:id).first if code.present? + end + + def self.is_code?(code) + self.where(:partner_code => code).limit(1).pluck(:id).present? + end + + def referrals_by_date + by_date = User.where(:affiliate_referral_id => self.id) + .group('DATE(created_at)') + .having("COUNT(*) > 0") + .order('date_created_at DESC') + .count + block_given? ? yield(by_date) : by_date + end + +end diff --git a/ruby/lib/jam_ruby/models/email_batch.rb b/ruby/lib/jam_ruby/models/email_batch.rb index 309b63305..74dff06ec 100644 --- a/ruby/lib/jam_ruby/models/email_batch.rb +++ b/ruby/lib/jam_ruby/models/email_batch.rb @@ -12,7 +12,7 @@ module JamRuby VAR_FIRST_NAME = '@FIRSTNAME' VAR_LAST_NAME = '@LASTNAME' - DEFAULT_SENDER = "support@jamkazam.com" + DEFAULT_SENDER = "noreply@jamkazam.com" BATCH_SIZE = 1000 BODY_TEMPLATE =< "JamRuby::EventSession" + # affiliate_partner + has_one :affiliate_partner, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :partner_user_id + belongs_to :affiliate_referral, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :affiliate_referral_id, :counter_cache => :referral_user_count + # This causes the authenticate method to be generated (among other stuff) #has_secure_password @@ -735,6 +739,7 @@ module JamRuby invited_user = options[:invited_user] fb_signup = options[:fb_signup] signup_confirm_url = options[:signup_confirm_url] + affiliate_referral_id = options[:affiliate_referral_id] user = User.new @@ -836,6 +841,10 @@ module JamRuby if user.errors.any? raise ActiveRecord::Rollback else + if user.affiliate_referral = AffiliatePartner.find_by_id(affiliate_referral_id) + user.save + end if affiliate_referral_id.present? + # don't send an signup email if email is already confirmed if user.email_confirmed UserMailer.welcome_message(user).deliver diff --git a/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb b/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb new file mode 100644 index 000000000..8470f8cee --- /dev/null +++ b/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe AffiliatePartner do + + let!(:user) { FactoryGirl.create(:user) } + let!(:partner) { + AffiliatePartner.create_with_params({:partner_name => Faker::Company.name, + :partner_code => Faker::Lorem.word, + :user_email => user.email}) + } + + it 'validates required fields' do + pending + expect(partner.referral_user_count).to eq(0) + expect(partner.partner_user).to eq(user) + user.reload + expect(user.affiliate_partner).to eq(partner) + + oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name, + :partner_code => 'a', + :user_email => user.email}) + expect(oo.errors.messages[:partner_code][0]).to eq('is invalid') + oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name, + :partner_code => 'foo bar', + :user_email => user.email}) + expect(oo.errors.messages[:partner_code][0]).to eq('is invalid') + oo = AffiliatePartner.create_with_params({:partner_name => '', + :partner_code => Faker::Lorem.word, + :user_email => user.email}) + expect(oo.errors.messages[:partner_name][0]).to eq("can't be blank") + oo = AffiliatePartner.create_with_params({:partner_name => '', + :partner_code => Faker::Lorem.word, + :user_email => Faker::Internet.email}) + expect(oo.errors.messages[:partner_user][0]).to eq("can't be blank") + + code = Faker::Lorem.word.upcase + oo = AffiliatePartner.create_with_params({:partner_name => Faker::Company.name, + :partner_code => " #{code} ", + :user_email => user.email}) + expect(oo.partner_code).to eq(code.downcase) + end + + it 'has user referrals' do + pending + expect(AffiliatePartner.coded_id(partner.partner_code)).to eq(partner.id) + expect(partner.referral_user_count).to eq(0) + uu = FactoryGirl.create(:user) + uu.affiliate_referral = partner + uu.save + partner.reload + expect(uu.affiliate_referral).to eq(partner) + expect(partner.referral_user_count).to eq(1) + expect(partner.user_referrals[0]).to eq(uu) + end + + it 'groups referrals properly' do + FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 3.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 2.days, :affiliate_referral_id => partner.id) + partner.reload + expect(partner.referral_user_count).to eq(6) + + by_date = partner.referrals_by_date + expect(by_date.count).to eq(4) + keys = by_date.keys + expect(Date.parse(keys.first)).to eq(Date.parse((Time.now - 2.days).to_s)) + expect(by_date[keys.first]).to eq(1) + expect(Date.parse(keys.last)).to eq(Date.parse((Time.now - 7.days).to_s)) + expect(by_date[keys.last]).to eq(2) + end + +end diff --git a/web/app/assets/images/content/icon_dollar.png b/web/app/assets/images/content/icon_dollar.png new file mode 100644 index 000000000..7d6c1aac2 Binary files /dev/null and b/web/app/assets/images/content/icon_dollar.png differ diff --git a/web/app/assets/javascripts/affiliate_report.js b/web/app/assets/javascripts/affiliate_report.js new file mode 100644 index 000000000..662611131 --- /dev/null +++ b/web/app/assets/javascripts/affiliate_report.js @@ -0,0 +1,65 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.AffiliateReportScreen = function(app) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var user = {}; + + function beforeShow(data) { + } + + function afterShow(data) { + renderAffiliateReport(); + } + + function populateAffiliateReport(report) { + console.log(report); + var by_date = ''; + var ii=0, dates_len = report['by_date'].length; + for (var ii=0; ii < dates_len; ii += 1) { + var dd = report['by_date'][ii]; + by_date += '
'+dd[0]+'
'; + by_date += '
'+dd[1].toString()+'
'; + by_date += '
'; + } + var template = context.JK.fillTemplate($('#template-account-affiliate').html(), { + total_count: report['total_count'], + by_date: by_date + }); + $('#account-affiliate-content-scroller').html(template); + } + + /****************** MAIN PORTION OF SCREEN *****************/ + // events for main screen + function events() { + } + + function renderAffiliateReport() { + $.ajax({ + type: "GET", + dataType: "json", + url: "/api/users/" + context.JK.currentUserId + "/affiliate", + processData: false + }).done(populateAffiliateReport) + .error(app.ajaxError); + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('account/affiliate', screenBindings); + events(); + } + + this.initialize = initialize; + this.beforeShow = beforeShow; + this.afterShow = afterShow; + return this; + }; + +})(window,jQuery); diff --git a/web/app/assets/javascripts/banner.js b/web/app/assets/javascripts/banner.js index a301f0b7a..7b11a3211 100644 --- a/web/app/assets/javascripts/banner.js +++ b/web/app/assets/javascripts/banner.js @@ -66,6 +66,7 @@ var me = { initialize: initialize, show: show, + showAlert: showAlert, hide: hide } diff --git a/web/app/assets/javascripts/createSession.js.erb b/web/app/assets/javascripts/createSession.js.erb index e5744cbfe..687527bbd 100644 --- a/web/app/assets/javascripts/createSession.js.erb +++ b/web/app/assets/javascripts/createSession.js.erb @@ -69,7 +69,7 @@ .prop('disabled', true) } - function validateForm() { + function validateForm() { //var errors = []; var isValid = true; var $form = $('#create-session-form'); diff --git a/web/app/assets/javascripts/gear_wizard.js b/web/app/assets/javascripts/gear_wizard.js index 634710d76..48ac0c668 100644 --- a/web/app/assets/javascripts/gear_wizard.js +++ b/web/app/assets/javascripts/gear_wizard.js @@ -6,18 +6,22 @@ context.JK = context.JK || {}; context.JK.GearWizard = function (app) { + var $dialog = null; var $wizardSteps = null; var $currentWizardStep = null; var step = 0; var $templateSteps = null; var $templateButtons = null; + var $templateAudioPort = null; var $ftueButtons = null; var self = null; var operatingSystem = null; // populated by loadDevices var deviceInformation = null; + var musicInputPorts = null; + var musicOutputPorts = null; // SELECT DEVICE STATE var validScore = false; @@ -33,28 +37,32 @@ var STEP_ROUTER_NETWORK = 5; var STEP_SUCCESS = 6; + var PROFILE_DEV_SEP_TOKEN = '^'; + + var iCheckIgnore = false; + var audioDeviceBehavior = { - MacOSX_builtin : { + MacOSX_builtin: { display: 'MacOSX Built-In', videoURL: undefined }, - MACOSX_interface : { + MACOSX_interface: { display: 'MacOSX external interface', videoURL: undefined }, - Win32_wdm : { + Win32_wdm: { display: 'Windows WDM', videoURL: undefined }, - Win32_asio : { + Win32_asio: { display: 'Windows ASIO', videoURL: undefined }, - Win32_asio4all : { + Win32_asio4all: { display: 'Windows ASIO4ALL', videoURL: undefined }, - Linux : { + Linux: { display: 'Linux', videoURL: undefined } @@ -63,7 +71,7 @@ function beforeShowIntro() { var $watchVideo = $currentWizardStep.find('.watch-video'); var videoUrl = 'https://www.youtube.com/watch?v=VexH4834o9I'; - if(operatingSystem == "Win32") { + if (operatingSystem == "Win32") { $watchVideo.attr('href', 'https://www.youtube.com/watch?v=VexH4834o9I'); } $watchVideo.attr('href', videoUrl); @@ -81,6 +89,9 @@ var $inputPorts = $currentWizardStep.find('.input-ports'); var $outputPorts = $currentWizardStep.find('.output-ports'); var $scoreReport = $currentWizardStep.find('.results'); + var $latencyScore = $scoreReport.find('.latency-score'); + var $ioRateScore = $scoreReport.find('.io-rate-score'); + var $ioVarScore = $scoreReport.find('.io-var-score'); var $nextButton = $ftueButtons.find('.btn-next'); // should return one of: @@ -91,24 +102,24 @@ // * Win32_asio4all // * Linux function determineDeviceType(deviceId, displayName) { - if(operatingSystem == "MacOSX") { - if(displayName.toLowerCase().trim() == "built-in") { + if (operatingSystem == "MacOSX") { + if (displayName.toLowerCase().trim() == "built-in") { return "MacOSX_builtin"; } else { return "MacOSX_interface"; } } - else if(operatingSystem == "Win32") { - if(context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) { - return "Win32_wdm"; - } - else if(displayName.toLowerCase().indexOf("asio4all") > -1) { - return "Win32_asio4all" - } - else { - return "Win32_asio"; - } + else if (operatingSystem == "Win32") { + if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) { + return "Win32_wdm"; + } + else if (displayName.toLowerCase().indexOf("asio4all") > -1) { + return "Win32_asio4all" + } + else { + return "Win32_asio"; + } } else { return "Linux"; @@ -122,25 +133,27 @@ var loadedDevices = {}; // augment these devices by determining their type - context._.each(devices, function(displayName, deviceId) { + context._.each(devices, function (displayName, deviceId) { var deviceInfo = {}; deviceInfo.id = deviceId; deviceInfo.type = determineDeviceType(deviceId, displayName); deviceInfo.displayType = audioDeviceBehavior[deviceInfo.type].display; - deviceInfo.displayName = deviceInfo.displayName; + deviceInfo.displayName = displayName; loadedDevices[deviceId] = deviceInfo; + + logger.debug("loaded device: ", deviceInfo); }) - deviceInformation = context._.keys(loadedDevices).sort(); + deviceInformation = loadedDevices; - logger.debug(context.JK.dlen(deviceInformation) + " devices loaded." , deviceInformation); + logger.debug(context.JK.dlen(deviceInformation) + " devices loaded.", deviceInformation); } - // returns a deviceInfo hash for the device matching the deviceId, or null. + // returns a deviceInfo hash for the device matching the deviceId, or undefined. function findDevice(deviceId) { - return {}; + return deviceInformation[deviceId]; } function selectedAudioInput() { @@ -164,22 +177,21 @@ } function initializeNextButtonState() { - console.log(context.jamClient.FTUEGetDevices(false)); - console.log("chat devices", jamClient.FTUEGetChatInputs()); $nextButton.removeClass('button-orange button-grey'); - if(validScore) $nextButton.addClass('button-orange'); + if (validScore) $nextButton.addClass('button-orange'); else $nextButton.addClass('button-grey'); } function initializeAudioInput() { var optionsHtml = ''; optionsHtml = ''; - context._.each(deviceInformation, function(deviceInfo, deviceId) { + context._.each(deviceInformation, function (deviceInfo, deviceId) { + + console.log(arguments) optionsHtml += ''; }); $audioInput.html(optionsHtml); - alert(optionsHtml) context.JK.dropdown($audioInput); initializeAudioInputChanged(); @@ -188,7 +200,7 @@ function initializeAudioOutput() { var optionsHtml = ''; optionsHtml = ''; - context._.each(deviceInformation, function(deviceInfo, deviceId) { + context._.each(deviceInformation, function (deviceInfo, deviceId) { optionsHtml += ''; }); $audioOutput.html(optionsHtml); @@ -198,11 +210,146 @@ } function initializeFramesize() { + context.JK.dropdown($frameSize); + } + function initializeBuffers() { + context.JK.dropdown($bufferIn); + context.JK.dropdown($bufferOut); + } + + // finds out if the $port argument is from a different port pair than what's currently selected + function isNewlySelectedPair($port) { + var portId = $port.attr('data-id'); + // get all inputs currently selected except this one + var $selectedInputs = $inputPorts.find('input[type="checkbox"]:checked').filter('[data-id="' + portId + '"]'); + + console.log("$selectedInputs", $selectedInputs); + var isNewlySelected = true; + context._.each($selectedInputs, function($current) { + var testPairInfo = $($current).data('pair'); + + context._.each(testPairInfo.ports, function(port) { + // if we can find the newly selected item in this pair, then it's not a different pair... + if(port.id == portId) { + isNewlySelected = false; + return false; // break loop + } + }); + + if(isNewlySelected) return false; // break loop + }); + + return isNewlySelected; + } + + // set checkbox state for all items in the pair + function setCheckedForAllInPair($portBox, pairInfo, checked, signalBackend) { + context._.each(pairInfo.ports, function(port) { + var portId = port.id; + var $input = $portBox.find('input[type="checkbox"][data-id="' + portId + '"]'); + if($input.is(':checked') != checked) { + if(checked) { + $input.iCheck('check').attr('checked', 'checked'); + //context.jamClient.FTUESetMusicInput2($input.id); + } + else { + $input.iCheck('uncheck').removeAttr('checked'); + //context.jamClient.FTUEUnsetMusicInput2($input.id); + } + } + }) + } + + function inputPortChanged() { + if(iCheckIgnore) return; + + var $checkbox = $(this); + var portId = $checkbox.data('data-id'); + var inputPortChecked = $checkbox.is(':checked'); + console.log('inputPortChecked: ' + inputPortChecked); + + if(inputPortChecked) { + if(isNewlySelectedPair($checkbox)) { + setCheckedForAllInPair($inputPorts, $checkbox.data('pair'), true, true); + } + else { + //context.jamClient.FTUESetMusicInput2($input.id); + } + } + else { + // context.jamClient.FTUEUnsetMusicInput2($input.id);; + } + } + + // should be called in a ifChanged callback if you want to cancel. bleh. + function cancelICheckChange($checkbox) { + iCheckIgnore = true; + var checked = $checkbox.is(':checked'); + setTimeout(function() { + if(checked) $checkbox.iCheck('uncheck').removeAttr('checked'); + else $checkbox.iCheck('check').attr('checked', 'checked'); + iCheckIgnore = false; + }, 1); + } + + function outputPortChanged() { + if(iCheckIgnore) return; + + var $checkbox = $(this); + var portId = $checkbox.data('data-id'); + var outputPortChecked = $checkbox.is(':checked'); + console.log('outputPortChecked: ' + outputPortChecked); + + if(outputPortChecked) { + var $selectedInputs = $outputPorts.find('input[type="checkbox"]:checked').filter('[data-id="' + portId + '"]'); + $selectedInputs.iCheck('uncheck').removeAttr('checked'); + var pairInfo = $checkbox.data('pair'); + setCheckedForAllInPair($outputPorts, pairInfo, true, false); + console.log("Setting music output"); + context.jamClient.FTUESetMusicOutput(pairInfo.ports.map(function(i) {return i.id}).join(PROFILE_DEV_SEP_TOKEN)); + } + else { + context.JK.Banner.showAlert('You must have at least one output pair selected.'); + // can't allow uncheck of last output + cancelICheckChange($checkbox); + } + } + + function initializeInputPorts(inputPorts) { + context._.each(inputPorts, function(inputPairs) { + // there is no guarantee that a pair has two items. + context._.each(inputPairs.ports, function(inputInPair) { + var inputPort = $(context._.template($templateAudioPort.html(), inputInPair, { variable: 'data' })); + var $checkbox = inputPort.find('input'); + $checkbox.data('pair', inputPairs); // so when it's selected, we can see what other ports, if any, are in the same pair + context.JK.checkbox($checkbox); + $checkbox.on('ifChanged', inputPortChanged); + $inputPorts.append(inputPort); + }); + }); + } + + function initializeOutputPorts(outputPorts) { + var first = true; + context._.each(outputPorts, function(outputPairs) { + context._.each(outputPairs.ports, function(outputInPair) { + var outputPort = $(context._.template($templateAudioPort.html(), outputInPair, { variable: 'data' })); + var $checkbox = outputPort.find('input'); + $checkbox.data('pair', outputPairs); // so when it's selected, we can see what other ports, if any, are in the same pair + context.JK.checkbox($checkbox); + $checkbox.on('ifChanged', outputPortChanged); + $outputPorts.append(outputPort); + }); + if(first) { + first = false; + setCheckedForAllInPair($outputPorts, outputPairs, true, false); + } + }); } function initializeFormElements() { - if(!deviceInformation) throw "devices are not initialized"; + if (!deviceInformation) throw "devices are not initialized"; initializeAudioInput(); initializeAudioOutput(); @@ -225,7 +372,9 @@ } function resetScoreReport() { - $scoreReport.empty(); + $ioRateScore.empty(); + $ioVarScore.empty(); + $latencyScore.empty(); } function updateScoreReport(latencyResult) { @@ -233,9 +382,9 @@ var latencyValue = 'N/A'; var validLatency = false; if (latencyResult && latencyResult.latencyknown) { - var latency = latencyResult.latency; + var latencyValue = latencyResult.latency; latencyValue = Math.round(latencyValue * 100) / 100; - if (latency.latency <= 10) { + if (latencyValue <= 10) { latencyClass = "good"; validLatency = true; } else if (latency.latency <= 20) { @@ -248,57 +397,9 @@ validScore = validLatency; // validScore may become based on IO variance too - $scoreReport.html(latencyValue); + $latencyScore.html(latencyValue + ' ms'); } - function loadAudioDrivers() { - var drivers = context.jamClient.FTUEGetDevices(false); - var chatDrivers = jamClient.FTUEGetChatInputs(); - var optionsHtml = ''; - var chatOptionsHtml = ''; - - - var driverOptionFunc = function (driverKey, index, list) { - if(!drivers[driverKey]) { - logger.debug("skipping unknown device:", driverKey) - } - else { - optionsHtml += ''; - } - }; - - var chatOptionFunc = function (driverKey, index, list) { - chatOptionsHtml += ''; - }; - - var selectors = [ - '[layout-wizard-step="0"] .settings-2-device select', - '[layout-wizard-step="2"] .settings-driver select' - ]; - - // handle standard devices - var sortedDeviceKeys = context._.keys(drivers).sort(); - context._.each(sortedDeviceKeys, driverOptionFunc); - $.each(selectors, function (index, selector) { - var $select = $(selector); - $select.empty(); - $select.html(optionsHtml); - context.JK.dropdown($select); - }); - - selectors = ['[layout-wizard-step="0"] .settings-2-voice select']; - var sortedVoiceDeviceKeys = context._.keys(chatDrivers).sort(); - - // handle voice inputs - context._.each(sortedVoiceDeviceKeys, chatOptionFunc); - $.each(selectors, function (index, selector) { - var $select = $(selector); - $select.empty(); - $select.html(chatOptionsHtml); - context.JK.dropdown($select); - }); - - } function audioInputDeviceUnselected() { validScore = false; initializeNextButtonState(); @@ -362,16 +463,16 @@ } function initializeWatchVideo() { - $watchVideoInput.unbind('click').click(function() { + $watchVideoInput.unbind('click').click(function () { var audioDevice = findDevice(selectedAudioInput()); - if(!audioDevice) { + if (!audioDevice) { context.JK.Banner.showAlert('You must first choose an Audio Input Device so that we can determine which video to show you.'); } else { var videoURL = audioDeviceBehavior[audioDevice.type].videoURL; - if(videoURL) { + if (videoURL) { $(this).attr('href', videoURL); return true; } @@ -383,16 +484,16 @@ return false; }); - $watchVideoOutput.unbind('click').click(function() { + $watchVideoOutput.unbind('click').click(function () { var audioDevice = findDevice(selectedAudioOutput()); - if(!audioDevice) { + if (!audioDevice) { throw "this button should be hidden"; } else { var videoURL = audioDeviceBehavior[audioDevice.type].videoURL; - if(videoURL) { + if (videoURL) { $(this).attr('href', videoURL); return true; } @@ -406,35 +507,52 @@ } function initializeAudioInputChanged() { - $audioInput.unbind('change').change(function(evt) { + $audioInput.unbind('change').change(function (evt) { - var audioDeviceId = selectedAudioInput(); - if(!audioDeviceId) { - audioInputDeviceUnselected(); - return false; - } + var audioDeviceId = selectedAudioInput(); + if (!audioDeviceId) { + audioInputDeviceUnselected(); + return false; + } - var audioDevice = findDevice(selectedAudioInput()); - if(!audioDevice) { - context.JK.alertSupportedNeeded('Unable to find device information for: ' + audioDeviceId); - return false; - } + var audioDevice = findDevice(selectedAudioInput()); + if (!audioDevice) { + context.JK.alertSupportedNeeded('Unable to find device information for: ' + audioDeviceId); + return false; + } - renderScoringStarted(); - jamClient.FTUESetMusicDevice(audioDeviceId); - jamClient.FTUESetInputLatency(selectedAudioInput()); - jamClient.FTUESetOutputLatency(selectedAudioOutput()); - jamClient.FTUESetFrameSize(selectedFramesize()); - logger.debug("Calling FTUESave(false)"); - jamClient.FTUESave(false); + renderScoringStarted(); - var latency = jamClient.FTUEGetExpectedLatency(); - console.log("FTUEGetExpectedLatency: %o", latency); + jamClient.FTUESetMusicDevice(audioDeviceId); - renderScoringStopped(); - }); + // enumerate input and output ports + musicInputPorts = jamClient.FTUEGetMusicInputs2(); + console.log(JSON.stringify(musicInputPorts)); + // [{"inputs":[{"id":"i~5~Built-in Microph~0~0","name":"Built-in Microph - Left"},{"id":"i~5~Built-in Microph~1~0","name":"Built-in Microph - Right"}]}] + musicOutputPorts = jamClient.FTUEGetMusicOutputs2(); + console.log(JSON.stringify(musicOutputPorts)); + // [{"outputs":[{"id":"o~5~Built-in Output~0~0","name":"Built-in Output - Left"},{"id":"o~5~Built-in Output~1~0","name":"Built-in Output - Right"}]}] + + + initializeInputPorts(musicInputPorts); + initializeOutputPorts(musicOutputPorts); + + + jamClient.FTUESetInputLatency(selectedAudioInput()); + jamClient.FTUESetOutputLatency(selectedAudioOutput()); + jamClient.FTUESetFrameSize(selectedFramesize()); + + logger.debug("Calling FTUESave(false)"); + jamClient.FTUESave(false); + + var latency = jamClient.FTUEGetExpectedLatency(); + console.log("FTUEGetExpectedLatency: %o", latency); + + updateScoreReport(latency); + renderScoringStopped(); + }); } function initializeAudioOutputChanged() { @@ -498,7 +616,9 @@ function beforeShowStep($step) { var stepInfo = STEPS[step]; - if(!stepInfo) {throw "unknown step: " + step;} + if (!stepInfo) { + throw "unknown step: " + step; + } stepInfo.beforeShow.call(self); } @@ -511,7 +631,7 @@ $currentWizardStep = $nextWizardStep; var $ftueSteps = $(context._.template($templateSteps.html(), {}, { variable: 'data' })); - var $activeStep = $ftueSteps.find('.ftue-stepnumber[data-step-number="'+ step +'"]'); + var $activeStep = $ftueSteps.find('.ftue-stepnumber[data-step-number="' + step + '"]'); $activeStep.addClass('.active'); $activeStep.next().show(); // show the .ftue-step-title $currentWizardStep.find('.ftuesteps').replaceWith($ftueSteps); @@ -528,11 +648,11 @@ var $btnCancel = $ftueButtonsContent.find('.btn-cancel'); // hide back button if 1st step or last step - if(step == 0 && step == TOTAL_STEPS - 1) { + if (step == 0 && step == TOTAL_STEPS - 1) { $btnBack.hide(); } // hide next button if not on last step - if (step == TOTAL_STEPS - 1 ) { + if (step == TOTAL_STEPS - 1) { $btnNext.hide(); } // hide close if on last step @@ -559,7 +679,7 @@ function beforeShow(args) { step = args.d1; - if(!step) step = 0; + if (!step) step = 0; step = parseInt(step); moveToStep(); } @@ -573,14 +693,14 @@ } function back() { - if($(this).is('.button-grey')) return; + if ($(this).is('.button-grey')) return; step = step - 1; moveToStep(); return false; } function next() { - if($(this).is('.button-grey')) return; + if ($(this).is('.button-grey')) return; step = step + 1; @@ -599,6 +719,7 @@ function route() { } + function initialize() { var dialogBindings = { beforeShow: beforeShow, afterShow: afterShow, afterHide: afterHide }; @@ -609,6 +730,7 @@ $wizardSteps = $dialog.find('.wizard-step'); $templateSteps = $('#template-ftuesteps'); $templateButtons = $('#template-ftue-buttons'); + $templateAudioPort = $('#template-audio-port'); $ftueButtons = $dialog.find('.ftue-buttons'); operatingSystem = context.jamClient.GetOSAsString(); diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss index ad578a628..6685d883d 100644 --- a/web/app/assets/stylesheets/client/content.css.scss +++ b/web/app/assets/stylesheets/client/content.css.scss @@ -429,7 +429,7 @@ ul.shortcuts { padding:2px; } - .account-home, .band-setup, .audio, .get-help, .download-app, .community-forum, .invite-friends { + .account-home, .band-setup, .account-menu-group, .get-help, .download-app, .community-forum, .invite-friends { border-bottom:1px; border-style:solid; border-color:#ED3618; diff --git a/web/app/assets/stylesheets/client/gearWizard.css.scss b/web/app/assets/stylesheets/client/gearWizard.css.scss index 49dc7f34b..f2c635714 100644 --- a/web/app/assets/stylesheets/client/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/gearWizard.css.scss @@ -184,8 +184,30 @@ width:45%; } + + .buffers { + .easydropdown-wrapper:nth-of-type(1) { + left:5px; + } + .easydropdown-wrapper:nth-of-type(2) { + left:35px; + } + .easydropdown, .easydropdown-wrapper { + width:15px; + } + } + + + .ftue-box.results { height: 230px !important; + padding:0; + + .scoring-section { + font-size:15px; + @include border_box_sizing; + height:64px; + } } .audio-output { @@ -510,8 +532,8 @@ .subcolumn.third { right:0px; } - } + .settings-controls { clear:both; diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss index e7ec359f0..92d551f31 100644 --- a/web/app/assets/stylesheets/client/jamkazam.css.scss +++ b/web/app/assets/stylesheets/client/jamkazam.css.scss @@ -558,4 +558,9 @@ hr { width:100%; height:20px; text-align:center; +} + +body.jam .icheckbox_minimal { + display:inline-block; + position:relative; } \ No newline at end of file diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 5dc55cf9c..97fb849c7 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -11,7 +11,8 @@ 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, - :share_session, :share_recording] + :share_session, :share_recording, + :affiliate_report] respond_to :json @@ -605,6 +606,27 @@ class ApiUsersController < ApiController end end + def affiliate_report + begin + affiliate = User + .where(:id => params[:id]) + .includes(:affiliate_partner) + .limit(1) + .first + .affiliate_partner + referrals_by_date = affiliate.referrals_by_date do |by_date| + by_date.inject([]) { |rr, key| rr << key } + end + result = { + :total_count => affiliate.referral_user_count, + :by_date => referrals_by_date + } + render json: result.to_json, status: 200 + rescue + render :json => { :message => $!.to_s }, :status => 400 + end + end + def add_play if params[:id].blank? render :json => { :message => "Playable ID is required" }, :status => 400 diff --git a/web/app/controllers/application_controller.rb b/web/app/controllers/application_controller.rb index c2922f055..3954175af 100644 --- a/web/app/controllers/application_controller.rb +++ b/web/app/controllers/application_controller.rb @@ -4,10 +4,22 @@ class ApplicationController < ActionController::Base include ApplicationHelper include SessionsHelper - # inject username/email into bugsnag data before_bugsnag_notify :add_user_info_to_bugsnag + before_filter do + if params[AffiliatePartner::PARAM_REFERRAL].present? && current_user.nil? + if cookies[AffiliatePartner::PARAM_COOKIE].blank? + code = params[AffiliatePartner::PARAM_REFERRAL].downcase + cookies[AffiliatePartner::PARAM_COOKIE] = code if AffiliatePartner.is_code?(code) + end + end + end + + def affiliate_code + cookies[AffiliatePartner::PARAM_COOKIE] + end + private def add_user_info_to_bugsnag(notif) # Add some app-specific data which will be displayed on a custom diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb index d31bb4094..d380c6b34 100644 --- a/web/app/controllers/users_controller.rb +++ b/web/app/controllers/users_controller.rb @@ -149,19 +149,20 @@ class UsersController < ApplicationController musician = params[:jam_ruby_user][:musician] @user = UserManager.new.signup(remote_ip: request.remote_ip, - first_name: params[:jam_ruby_user][:first_name], - last_name: params[:jam_ruby_user][:last_name], - email: params[:jam_ruby_user][:email], - password: params[:jam_ruby_user][:password], - password_confirmation: params[:jam_ruby_user][:password_confirmation], - terms_of_service: terms_of_service, - instruments: instruments, - birth_date: birth_date, - location: location, - musician: musician, - invited_user: @invited_user, - fb_signup: @fb_signup, - signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm") + first_name: params[:jam_ruby_user][:first_name], + last_name: params[:jam_ruby_user][:last_name], + email: params[:jam_ruby_user][:email], + password: params[:jam_ruby_user][:password], + password_confirmation: params[:jam_ruby_user][:password_confirmation], + terms_of_service: terms_of_service, + instruments: instruments, + birth_date: birth_date, + location: location, + musician: musician, + invited_user: @invited_user, + fb_signup: @fb_signup, + signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm", + affiliate_referral_id: AffiliatePartner.coded_id(self.affiliate_code)) # check for errors if @user.errors.any? diff --git a/web/app/views/clients/_affiliate_report.html.erb b/web/app/views/clients/_affiliate_report.html.erb new file mode 100644 index 000000000..46d04308a --- /dev/null +++ b/web/app/views/clients/_affiliate_report.html.erb @@ -0,0 +1,41 @@ + +
+ +
+ +
+ <%= image_tag "content/icon_dollar.png", {:width => 24, :height => 24} %> +
+ +

affiliate report

+ <%= render "screen_navigation" %> +
+ + + +
+
+
+
+ +
+ + + diff --git a/web/app/views/clients/gear/_gear_wizard.html.haml b/web/app/views/clients/gear/_gear_wizard.html.haml index 0ce16062d..fbeffefbd 100644 --- a/web/app/views/clients/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/gear/_gear_wizard.html.haml @@ -83,6 +83,15 @@ .wizard-step-column %h2 Test Results .ftue-box.results + .left.w50.gold-fill.center.white.scoring-section + .p5 + .latency LATENCY + %span.latency-score + .left.w50.green-fill.center.white.scoring-section + .p5 + .io I/O + %span.io-rate-score + %span.io-var-score .clearall @@ -195,5 +204,11 @@ %a.button-orange.btn-next{href:'#'} NEXT %a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE +%script{type: 'text/template', id: 'template-audio-port'} + .audio-port + %input{ type: 'checkbox', 'data-id' => '{{data.id}}' } + %span + = '{{data.name}}' + diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index bd37726f2..a573d1758 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -38,6 +38,7 @@ <%= render "testBridge" %> <%= render "account" %> <%= render "account_identity" %> +<%= render "affiliate_report" %> <%= render "account_profile" %> <%= render "friendSelector" %> <%= render "account_profile_avatar" %> @@ -182,6 +183,9 @@ var accountIdentityScreen = new JK.AccountIdentityScreen(JK.app); accountIdentityScreen.initialize(); + var affiliateReportScreen = new JK.AffiliateReportScreen(JK.app); + affiliateReportScreen.initialize(); + var accountProfileScreen = new JK.AccountProfileScreen(JK.app); accountProfileScreen.initialize(); diff --git a/web/app/views/layouts/client.html.erb b/web/app/views/layouts/client.html.erb index 7797d1dd5..2565a4ec7 100644 --- a/web/app/views/layouts/client.html.erb +++ b/web/app/views/layouts/client.html.erb @@ -7,12 +7,14 @@ <%= stylesheet_link_tag "client/client", media: "all" %> diff --git a/web/app/views/layouts/corporate.html.erb b/web/app/views/layouts/corporate.html.erb index 0b973c562..b852f8341 100644 --- a/web/app/views/layouts/corporate.html.erb +++ b/web/app/views/layouts/corporate.html.erb @@ -4,12 +4,14 @@ <%= full_title(yield(:title)) %> diff --git a/web/app/views/layouts/landing.erb b/web/app/views/layouts/landing.erb index 54c6ef95a..d88d06461 100644 --- a/web/app/views/layouts/landing.erb +++ b/web/app/views/layouts/landing.erb @@ -8,12 +8,14 @@ <%= stylesheet_link_tag "landing/landing", media: "all" %> diff --git a/web/app/views/layouts/minimal.html.erb b/web/app/views/layouts/minimal.html.erb index f3cc26096..f29412ae4 100644 --- a/web/app/views/layouts/minimal.html.erb +++ b/web/app/views/layouts/minimal.html.erb @@ -8,12 +8,14 @@ <%= stylesheet_link_tag "minimal/minimal", media: "all" %> diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index 532ee9b06..2515ae9da 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -8,12 +8,14 @@ <%= stylesheet_link_tag "web/web", media: "all" %> diff --git a/web/app/views/users/_feed_music_session_ajax.html.haml b/web/app/views/users/_feed_music_session_ajax.html.haml index 58d32ca4b..51f461f2c 100644 --- a/web/app/views/users/_feed_music_session_ajax.html.haml +++ b/web/app/views/users/_feed_music_session_ajax.html.haml @@ -6,7 +6,8 @@ / type and artist .left.ml20.w15 .title{hoveraction: 'session', :'session-id' => '{{data.feed_item.id}}' } SESSION - %a.artist{href: "#", :hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'} + .artist + %a.artist{href: "#", :hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'} = '{{data.feed_item.helpers.artist_name}}' %time.small.created_at.timeago{datetime: '{{data.feed_item.helpers.utc_created_at}}'}= '{{data.feed_item.created_at}}' / name and description diff --git a/web/app/views/users/_feed_recording_ajax.html.haml b/web/app/views/users/_feed_recording_ajax.html.haml index 66595e900..ffe584099 100644 --- a/web/app/views/users/_feed_recording_ajax.html.haml +++ b/web/app/views/users/_feed_recording_ajax.html.haml @@ -6,7 +6,8 @@ / type and artist .left.ml20.w15 .title{hoveraction: 'recording', :'recording-id' => '{{data.candidate_claimed_recording.id}}' } RECORDING - %a.artist{href: "#", :hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'} + .artist + %a.artist{href: "#", :hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'} = '{{data.feed_item.helpers.artist_name}}' %time.small.created_at.timeago{datetime: '{{data.feed_item.helpers.utc_created_at}}'} = '{{data.feed_item.created_at}}' diff --git a/web/app/views/users/_user_dropdown.html.erb b/web/app/views/users/_user_dropdown.html.erb index 6cbb97ed0..5504c5b0f 100644 --- a/web/app/views/users/_user_dropdown.html.erb +++ b/web/app/views/users/_user_dropdown.html.erb @@ -20,7 +20,13 @@ <% if current_user && current_user.musician? %> -
  • <%= link_to "Audio Gear", '/client#/account/audio' %>
  • + <% class_val = current_user.affiliate_partner.present? ? 'audio' : 'audio account-menu-group' %> +
  • <%= link_to "Audio Gear", '/client#/account/audio' %>
  • + <% end %> + <% if current_user && current_user.affiliate_partner.present? %> +
  • <%= link_to "Affiliate Report", '/client#/account/affiliate' %>
  • + <% end %> + <% if current_user && current_user.musician? %>
  • <%= link_to "Band Setup", '/client#/band/setup/new' %>
  • <% end %>
  • <%= link_to "Invite Friends", '#' %> diff --git a/web/config/routes.rb b/web/config/routes.rb index 508fd3170..b48a4c22e 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -271,6 +271,7 @@ SampleApp::Application.routes.draw do # match '/users/:id/recordings/:recording_id' => 'api_users#recording_destroy', :via => :delete match '/users/:id/plays' => 'api_users#add_play', :via => :post, :as => 'api_users_add_play' + match '/users/:id/affiliate' => 'api_users#affiliate_report', :via => :get, :as => 'api_users_affiliate' # bands match '/bands' => 'api_bands#index', :via => :get diff --git a/web/lib/user_manager.rb b/web/lib/user_manager.rb index 728bcb652..34be86248 100644 --- a/web/lib/user_manager.rb +++ b/web/lib/user_manager.rb @@ -26,6 +26,7 @@ class UserManager < BaseManager invited_user = options[:invited_user] fb_signup = options[:fb_signup] signup_confirm_url = options[:signup_confirm_url] + affiliate_referral_id = options[:affiliate_referral_id] @user = User.new @@ -60,7 +61,8 @@ class UserManager < BaseManager photo_url: photo_url, invited_user: invited_user, fb_signup: fb_signup, - signup_confirm_url: signup_confirm_url) + signup_confirm_url: signup_confirm_url, + affiliate_referral_id: affiliate_referral_id) return @user #end diff --git a/web/spec/managers/user_manager_spec.rb b/web/spec/managers/user_manager_spec.rb index ba4d3a33d..e09c9406c 100644 --- a/web/spec/managers/user_manager_spec.rb +++ b/web/spec/managers/user_manager_spec.rb @@ -11,6 +11,13 @@ describe UserManager do end describe "signup" do + let!(:user) { FactoryGirl.create(:user) } + let!(:partner) { + AffiliatePartner.create_with_params({:partner_name => Faker::Company.name, + :partner_code => Faker::Lorem.words(1)[0], + :user_email => user.email}) + } + it "signup successfully" do MaxMindIsp.delete_all # prove that city/state/country will remain nil if no maxmind data MaxMindGeo.delete_all @@ -24,7 +31,8 @@ describe UserManager do terms_of_service: true, instruments: @instruments, musician:true, - signup_confirm_url: "http://localhost:3000/confirm" ) + signup_confirm_url: "http://localhost:3000/confirm", + affiliate_referral_id: AffiliatePartner.coded_id(partner.partner_code)) @user.errors.any?.should be_false @user.first_name.should == "bob" @@ -38,6 +46,9 @@ describe UserManager do @user.subscribe_email.should be_true @user.signup_token.should_not be_nil + @user.reload + expect(@user.affiliate_referral).to eq(partner) + UserMailer.deliveries.length.should == 1 end diff --git a/web/spec/requests/affilate_referral_spec.rb b/web/spec/requests/affilate_referral_spec.rb new file mode 100644 index 000000000..7a61f8ec4 --- /dev/null +++ b/web/spec/requests/affilate_referral_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe "Affiliate Reports", :type => :api do + + include Rack::Test::Methods + + let!(:user) { FactoryGirl.create(:user) } + let!(:partner) { + AffiliatePartner.create_with_params({:partner_name => Faker::Company.name, + :partner_code => Faker::Lorem.words[0], + :user_email => user.email}) + } + + it "valid score" do + FactoryGirl.create(:user, :created_at => Time.now - 5.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id) + FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id) + + post('/api/auth_session.json', + { :email => user.email, :password => user.password }.to_json, + "CONTENT_TYPE" => 'application/json') + last_response.status.should == 200 + expect(JSON.parse(last_response.body)).to eq({ "success" => true }) + + get "/api/users/#{user.id}/affiliate" + + expect(last_response.status).to eq(200) + json = JSON.parse(last_response.body) + + expect(json['total_count']).to eq(7) + by_date = json['by_date'] + expect(by_date.count).to eq(3) + expect(by_date.first.last).to eq(1) + expect(by_date.last.last).to eq(4) + end + +end