Merge branch 'feature/scheduled_sessions' into feature/create_session_flow

This commit is contained in:
Bert Owen 2014-06-02 02:57:15 +08:00
commit e49c98c659
255 changed files with 13967 additions and 2183 deletions

View File

@ -1,7 +1,7 @@
source 'http://rubygems.org'
source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/'
devenv = ENV["BUILD_NUMBER"].nil? || ENV["TEST_WWW"] == "1"
devenv = ENV["BUILD_NUMBER"].nil?
if devenv
gem 'jam_db', :path=> "../db/target/ruby_package"
@ -72,9 +72,6 @@ gem 'postgres_ext', '1.0.0'
gem 'resque_mailer'
gem 'rest-client'
gem 'geokit-rails'
gem 'postgres_ext', '1.0.0'
group :libv8 do
gem 'libv8', "~> 3.11.8"
end
@ -104,11 +101,7 @@ group :development, :test do
gem 'capybara'
gem 'rspec-rails'
gem 'guard-rspec', '0.5.5'
gem 'jasmine', '1.3.1'
gem 'pry'
gem 'pry-remote'
gem 'pry-stack_explorer'
gem 'pry-debugger'
gem 'jasmine', '1.3.1'
gem 'execjs', '1.4.0'
gem 'therubyracer' #, '0.11.0beta8'
gem 'factory_girl_rails', '4.1.0'
@ -120,4 +113,12 @@ end
group :test do
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'
end
gem 'capybara-webkit'
gem 'capybara-screenshot'
gem 'poltergeist'
end
gem 'pry'
gem 'pry-remote'
gem 'pry-stack_explorer'
gem 'pry-debugger'

View File

@ -38,7 +38,7 @@ ActiveAdmin.register_page "Dashboard" do
end
end
end
end
end
end
# column do

View File

@ -115,7 +115,7 @@ ActiveAdmin.register JamRuby::EmailBatch, :as => 'Batch Emails' do
end
member_action :batch_send, :method => :get do
resource.deliver_batch
resource.deliver_batch_async
redirect_to admin_batch_email_path(resource.id)
end

View File

@ -1,29 +0,0 @@
ActiveAdmin.register JamRuby::EmailError, :as => 'Email Errors' do
menu :label => 'Email Errors', :parent => 'Email'
config.batch_actions = false
config.filters = false
config.clear_action_items!
index do
column 'User' do |eerr|
eerr.user ? link_to(eerr.user.name, admin_user_path(eerr.user_id)) : 'N/A'
end
column 'Error Type' do |eerr| eerr.error_type end
column 'Email Address' do |eerr| eerr.email_address end
column 'Status' do |eerr| eerr.status end
column 'Reason' do |eerr| eerr.reason end
column 'Email Date' do |eerr| eerr.email_date end
end
controller do
def scoped_collection
@eerrors ||= end_of_association_chain
.includes([:user])
.order('email_date DESC')
end
end
end

221
admin/app/admin/feeds.rb Normal file
View File

@ -0,0 +1,221 @@
ActiveAdmin.register_page 'Feed' do
content do
# get user information via params
user_id = nil
user_id = params[:feed][:user_id] if params[:feed] && params[:feed][:user_id] != ''
user_name = 'All'
user_name = User.find(user_id).to_label if user_id
render :partial => 'form', locals: {user_name: user_name, user_id: user_id }
page = (params[:page] ||= 1).to_i
per_page = 10
offset = (page - 1) * per_page
# get feed ids
where_sql = ''
where_sql = "where user_id = '#{user_id}'" if user_id
sql_feed_ids = "SELECT id, 'music_sessions' as type, created_at FROM music_sessions #{where_sql}
UNION ALL
SELECT DISTINCT recording_id as id, 'recordings' as type, created_at FROM recorded_tracks #{where_sql}
UNION ALL
SELECT id, 'diagnostics' as type, created_at FROM diagnostics #{where_sql}
ORDER BY created_at DESC
OFFSET #{offset}
LIMIT #{per_page};"
sql_feed_count = "SELECT COUNT(*) FROM (
SELECT id, 'music_sessions' as type, created_at FROM music_sessions #{where_sql}
UNION ALL
SELECT DISTINCT recording_id as id, 'recordings' as type, created_at FROM recorded_tracks #{where_sql}
UNION ALL
SELECT id, 'diagnostics' as type, created_at FROM diagnostics #{where_sql}
ORDER BY created_at DESC
) AS IDS;"
feed_count = ActiveRecord::Base.connection.execute(sql_feed_count).values[0][0].to_i
id_types = ActiveRecord::Base.connection.execute(sql_feed_ids).values
@feed_pages = WillPaginate::Collection.create(page, per_page) do |pager|
pager.total_entries = feed_count
pager.replace(id_types)
end
div class: 'feed-pagination' do
will_paginate @feed_pages
end
recordings = []
sessions = []
diagnostics = []
id_types.each do |id_and_type|
if id_and_type[1] == "music_sessions"
sessions << JamRuby::MusicSession.find(id_and_type[0])
elsif id_and_type[1] == "recordings"
recordings << JamRuby::Recording.find(id_and_type[0])
elsif id_and_type[1] == "diagnostics"
diagnostics << JamRuby::Diagnostic.find(id_and_type[0])
else
raise "Unknown type returned from feed ids"
end
end
columns do
column do
panel "Music Sessions - #{user_name}" do
if sessions.count > 0
table_for(sessions) do
column :creator do |msh|
link_to msh.creator.to_label, admin_feed_path({feed: {user_id: msh.creator.id}})
end
column :created_at do |msh| msh.created_at.strftime('%b %d %Y, %H:%M') end
column :duration do |msh| "#{msh.duration_minutes.round(2)} minutes" end
column :members do |msh|
uu = msh.unique_users
if uu.length > 0
uu.each do |u|
span link_to u.to_label + ', ', admin_feed_path({feed: {user_id: u.id}})
end
else
span para 'No members'
end
end
column :band do |msh| auto_link(msh.band, msh.band.try(:name)) end
column :fan_access do |msh| msh.fan_access end
column :plays do |msh| msh.plays.count end
column :likes do |msh| msh.likes.count end
column :comments do |msh|
if msh.comment_count > 0
text_node "(#{msh.comment_count}) "
msh.comments.each do |comment|
text_node comment.user.to_label + ', '
end
else
span para 'No comments'
end
end
end
else
span class: 'text-center' do
para 'No session activities.'
end
end
end
panel "Recordings - #{user_name}" do
if recordings.count > 0
table_for(recordings) do
column :starter do |rec|
link_to rec.owner.to_label, admin_feed_path({feed: {user_id: rec.owner.id}})
end
column :mixes do |rec|
ul do
mixes = rec.mixes
if mixes.count > 0
mixes.each do |mix|
li do
text_node "Created At: #{mix.created_at.strftime('%b %d %Y, %H:%M')}, "
text_node "Started At: #{mix.started_at.strftime('%b %d %Y, %H:%M')}, "
text_node "Completed At: #{mix.completed_at.strftime('%b %d %Y, %H:%M')}, "
text_node "Error Count: #{mix.error_count}, "
text_node "Error Reason: #{mix.error_reason}, "
text_node "Error Detail: #{mix.error_detail}, "
text_node "Download Count: #{mix.download_count}, "
if !mix.nil? && !mix[:ogg_url].nil?
span link_to 'Download OGG', mix.sign_url(3600, 'ogg')
else
text_node 'OGG download not available'
end
if !mix.nil? && !mix[:mp3_url].nil?
span link_to 'Download MP3', mix.sign_url(3600, 'mp3')
else
text_node 'MP3 download not available'
end
end
end
else
span para 'No mixes'
end
end
end
column :recorded_tracks do |rec|
ul do
rts = rec.recorded_tracks
if rts.count > 0
rts.each do |gt|
li do
span link_to gt.musician.to_label, admin_feed_path({feed: {user_id: gt.musician.id}})
span ", #{gt.instrument_id}, "
span "Download Count: #{gt.download_count}, "
span "Fully uploaded: #{gt.fully_uploaded}, "
span "Upload failures: #{gt.upload_failures}, "
span "Part failures: #{gt.part_failures}, "
if gt[:url]
# span link_to 'Download', gt.sign_url(3600)
else
span 'No track available'
end
end
end
else
span para 'No recorded tracks'
end
end
end
column :claimed_recordings do |rec|
ul do
crs = rec.claimed_recordings
if crs.count > 0
crs.each do |cr|
li do
span cr.name
span link_to cr.user.to_label, admin_feed_path({feed: {user_id: cr.user.id}})
span ", Public: #{cr.is_public}"
end
end
else
span para 'No claimed recordings'
end
end
end
end
else
span class: 'text-center' do
para 'No recording activities.'
end
end
end
panel "Diagnostics - #{user_name}" do
if diagnostics.count > 0 then
table_for(diagnostics) do
column :user do |d|
span link_to d.user.to_label, admin_feed_path({feed: {user_id: d.user.id}})
end
column :created_at do |d| d.created_at.strftime('%b %d %Y, %H:%M') end
column :type
column :creator
column :data do |d|
span style: "white-space: pre;" do
begin
JSON.pretty_generate(JSON.parse(d.data))
rescue
d.data
end
end
end
end
else
span class: 'text-center' do
para 'No diagnostic activities.'
end
end
end
end
end
div class: 'feed-pagination' do
will_paginate @feed_pages
end
end
end

View File

@ -103,7 +103,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name
def get_autocomplete_items(parameters)
items = User.select("DISTINCT email, first_name, last_name, id").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
User.select("email, first_name, last_name, id").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
end
def create

View File

@ -0,0 +1,32 @@
ActiveAdmin.register JamRuby::LatencyTester, :as => 'LatencyTester' do
config.filters = true
config.per_page = 50
config.clear_action_items!
config.sort_order = "client_id"
menu :parent => 'Operations'
controller do
def scoped_collection
@latency_testers ||= end_of_association_chain
.order('client_id')
end
end
index :as => :block do |latency_tester|
div :for => latency_tester do
h3 "#{latency_tester.client_id}"
columns do
column do
panel 'Details' do
attributes_table_for(latency_tester) do
row :connection do |latency_tester| latency_tester.connection ? "last updated at: #{latency_tester.connection.updated_at}" : "no connection" end
end
end
end
end
end
end
end

View File

@ -9,6 +9,7 @@
/*
*= require jquery.ui.all
*= require custom
*/
// Active Admin's got SASS!
@import "active_admin/mixins";

View File

@ -2,4 +2,24 @@
.version-info {
font-size:small;
color:lightgray;
}
.text-center {
text-align: center;
}
.feed-pagination {
height: 20px;
margin-bottom: 15px;
.pagination {
float: left !important;
ul {
list-style-type: none;
li {
float: left;
}
}
}
}

View File

@ -0,0 +1,6 @@
<%= semantic_form_for :feed, url: admin_feed_path, method: :get do |f| %>
<%= f.inputs do %>
<%= f.input :user, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, :input_html => { :id_element => "#feed_user_id" } %>
<%= f.input :user_id, :as => :hidden %>
<% end %>
<% end %>

0
admin/log/phantomjs.out Normal file
View File

View File

@ -28,6 +28,17 @@ FactoryGirl.define do
end
end
end
factory :connection, :class => JamRuby::Connection do
sequence(:client_id) { |n| "Client#{n}" }
ip_address "1.1.1.1"
as_musician true
addr 0
locidispid 0
client_type 'client'
association :user, factory: :user
end
factory :artifact_update, :class => JamRuby::ArtifactUpdate do
sequence(:version) { |n| "0.1.#{n}" }
uri { "http://somewhere/jkclient.msi" }
@ -46,4 +57,145 @@ FactoryGirl.define do
description { |n| "Instrument #{n}" }
end
factory :genre, :class => JamRuby::Genre do
description { |n| "Genre #{n}" }
end
factory :music_session, :class => JamRuby::MusicSession do
sequence(:name) { |n| "Music Session #{n}" }
sequence(:description) { |n| "Music Session Description #{n}" }
fan_chat true
fan_access true
approval_required false
musician_access true
legal_terms true
language 'english'
legal_policy 'standard'
genre JamRuby::Genre.first
association :creator, :factory => :user
end
factory :music_session_user_history, :class => JamRuby::MusicSessionUserHistory do
ignore do
history nil
user nil
end
music_session_id { history.id }
user_id { user.id }
sequence(:client_id) { |n| "Connection #{n}" }
end
factory :recorded_track, :class => JamRuby::RecordedTrack do
instrument JamRuby::Instrument.first
sound 'stereo'
sequence(:client_id) { |n| "client_id-#{n}"}
sequence(:track_id) { |n| "track_id-#{n}"}
sequence(:client_track_id) { |n| "client_track_id-#{n}"}
md5 'abc'
length 1
fully_uploaded true
association :user, factory: :user
association :recording, factory: :recording
end
factory :recording, :class => JamRuby::Recording do
association :owner, factory: :user
association :music_session, factory: :active_music_session
factory :recording_with_track do
before(:create) { |recording, evaluator|
recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: evaluator.owner)
}
end
end
factory :claimed_recording, :class => JamRuby::ClaimedRecording do
sequence(:name) { |n| "name-#{n}" }
sequence(:description) { |n| "description-#{n}" }
is_public true
association :genre, factory: :genre
association :user, factory: :user
before(:create) { |claimed_recording|
claimed_recording.recording = FactoryGirl.create(:recording_with_track, owner: claimed_recording.user)
}
end
factory :mix, :class => JamRuby::Mix do
started_at Time.now
completed_at Time.now
ogg_md5 'abc'
ogg_length 1
sequence(:ogg_url) { |n| "recordings/ogg/#{n}" }
mp3_md5 'abc'
mp3_length 1
sequence(:mp3_url) { |n| "recordings/mp3/#{n}" }
completed true
before(:create) {|mix|
user = FactoryGirl.create(:user)
mix.recording = FactoryGirl.create(:recording_with_track, owner: user)
mix.recording.claimed_recordings << FactoryGirl.create(:claimed_recording, user: user, recording: mix.recording)
}
end
factory :diagnostic, :class => JamRuby::Diagnostic do
type JamRuby::Diagnostic::NO_HEARTBEAT_ACK
creator JamRuby::Diagnostic::CLIENT
data Faker::Lorem.sentence
association :user, factory: :user
end
factory :active_music_session_no_user_history, :class => JamRuby::ActiveMusicSession do
association :creator, factory: :user
ignore do
name "My Music Session"
description "Come Music Session"
fan_chat true
fan_access true
approval_required false
musician_access true
legal_terms true
genre JamRuby::Genre.first
band nil
end
before(:create) do |session, evaluator|
music_session = FactoryGirl.create(:music_session, name: evaluator.name, description: evaluator.description, fan_chat: evaluator.fan_chat,
fan_access: evaluator.fan_access, approval_required: evaluator.approval_required, musician_access: evaluator.musician_access,
genre: evaluator.genre, creator: evaluator.creator, band: evaluator.band)
session.id = music_session.id
end
factory :active_music_session do
after(:create) { |session|
FactoryGirl.create(:music_session_user_history, :history => session.music_session, :user => session.creator)
}
factory :music_session_with_mount do
association :mount, :factory => :icecast_mount
end
end
end
factory :latency_tester, :class => JamRuby::LatencyTester do
ignore do
connection nil
make_connection true
end
sequence(:client_id) { |n| "LatencyTesterClientId-#{n}" }
after(:create) do |latency_tester, evaluator|
latency_tester.connection = evaluator.connection if evaluator.connection
latency_tester.connection = FactoryGirl.create(:connection, client_type: Connection::TYPE_LATENCY_TESTER, client_id: latency_tester.client_id) if evaluator.make_connection
latency_tester.save
end
end
end

View File

@ -0,0 +1,88 @@
require 'spec_helper'
describe 'Feeds' do
subject { page }
before(:each) do
MusicSession.delete_all
Recording.delete_all
Diagnostic.delete_all
User.delete_all
end
let(:admin) { FactoryGirl.create(:admin) }
let(:user) { FactoryGirl.create(:user) }
let(:music_session) { FactoryGirl.create(:music_session, :creator => user) }
let(:recording) { FactoryGirl.create(:recording_with_track, :owner => user) }
let(:diagnostic) { FactoryGirl.create(:diagnostic, :user => user) }
context 'empty dashboard' do
before(:each) do
visit admin_feed_path
end
it { should have_selector('h2', text: 'Feed') }
it 'has no feeds' do
should have_selector('p', text: 'No session activities.')
should have_selector('p', text: 'No recording activities.')
should have_selector('p', text: 'No diagnostic activities.')
end
end
context 'admin enters a user name' do
before(:each) do
user.touch
visit admin_feed_path
end
it 'auto-completes with email + full name', :js => true do
within('form.feed') do
fill_in 'feed_user', with: user.email[0..3]
end
page.execute_script %Q{ $('form.feed input#feed_user').trigger('focus') }
page.execute_script %Q{ $('form.feed input#feed_user').trigger('keydown') }
find('a.ui-corner-all', text: user.to_label).trigger(:click)
should have_selector('form.feed #feed_user', user.to_label)
should have_selector('form.feed #feed_user_id[value="' + user.id + '"]', visible:false)
end
end
context 'with existing activities' do
before(:each) do
music_session.touch
recording.touch
diagnostic.touch
visit admin_feed_path
end
it 'shows session, recording, diagnostic' do
should have_selector("tr#jam_ruby_music_session_#{music_session.id}")
should have_selector("tr#jam_ruby_recording_#{recording.id}")
should have_selector("tr#jam_ruby_diagnostic_#{diagnostic.id}")
end
it 'shows activities for one user', :js => true do
within('form.feed') do
fill_in 'feed_user', with: user.email[0..3]
end
page.execute_script %Q{ $('form.feed input#feed_user').trigger('focus') }
page.execute_script %Q{ $('form.feed input#feed_user').trigger('keydown') }
find('a.ui-corner-all', text: user.to_label).trigger(:click)
should have_selector('form.feed #feed_user', user.to_label)
should have_selector('form.feed #feed_user_id[value="' + user.id + '"]', visible:false)
page.execute_script %Q{ $('form.feed').trigger('submit') }
should have_selector("tr#jam_ruby_music_session_#{music_session.id}")
should have_selector("tr#jam_ruby_recording_#{recording.id}")
should have_selector("tr#jam_ruby_diagnostic_#{diagnostic.id}")
end
end
end

View File

@ -0,0 +1,36 @@
require 'spec_helper'
describe 'Feeds' do
subject { page }
before(:each) do
end
describe "latency_tester with connection" do
let!(:latency_tester) {FactoryGirl.create(:latency_tester)}
before(:each) do
visit admin_latency_testers_path
end
it "shows connection info" do
should have_selector('td', text: "last updated at: #{latency_tester.connection.updated_at}")
end
end
describe "latency_tester with no connection" do
let!(:latency_tester) {FactoryGirl.create(:latency_tester, client_id: 'abc', make_connection: false)}
before(:each) do
visit admin_latency_testers_path
end
it "shows no connection" do
should have_selector('td', text: "no connection")
end
end
end

View File

@ -22,6 +22,9 @@ require 'rspec/autorun'
# load capybara
require 'capybara/rails'
require 'capybara/rspec'
require 'capybara-screenshot/rspec'
require 'capybara/poltergeist'
#include Rails.application.routes.url_helpers
@ -30,6 +33,11 @@ require 'capybara/rails'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
Capybara.register_driver :poltergeist do |app|
driver = Capybara::Poltergeist::Driver.new(app, { debug: false, phantomjs_logger: File.open('log/phantomjs.out', 'w') })
end
Capybara.javascript_driver = :poltergeist
Capybara.default_wait_time = 10
RSpec.configure do |config|
# ## Mock Framework
@ -46,7 +54,7 @@ RSpec.configure do |config|
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
config.use_transactional_fixtures = false
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of

View File

@ -0,0 +1,30 @@
module Snapshot
SS_PATH = 'snapshots.html'
def set_up_snapshot(filepath = SS_PATH)
@size = [1280, 720] #arbitrary
@file = File.new(filepath, "w+")
@file.puts "<HTML><BODY BGCOLOR=grey>"
@file.puts "<H1>Snapshot #{ENV["BUILD_NUMBER"]} - #{@size[0]}x#{@size[1]}</H1>"
end
def snapshot_example
page.driver.resize(@size[0], @size[1])
@file.puts "<H3>Example name: #{get_description}</H3><BR><BR>"
end
def snap!(title = get_description)
base64 = page.driver.render_base64(:png)
@file.puts '<H3>' + title + '</H3>'
@file.puts '<img alt="' + title +'" src="data:image/png;base64,' + base64 + '" />'
@file.puts '<BR><BR><BR>'
end
def tear_down_snapshot
@file.puts "</BODY></HTML>"
@file.close()
end
end

5
atlassian-ide-plugin.xml Normal file
View File

@ -0,0 +1,5 @@
<atlassian-ide-plugin>
<project-configuration id="1">
<servers id="2" />
</project-configuration>
</atlassian-ide-plugin>

View File

@ -1 +1,10 @@
this is just for getting this maxmind data over there so i can use it.
source for iso3166-1 data:
http://dev.maxmind.com/static/csv/codes/iso3166.csv
source for iso3166-2 data (compatible):
http://geolite.maxmind.com/download/geoip/misc/region_codes.csv

View File

@ -1,13 +0,0 @@
AB,Alberta
BC,British Columbia
MB,Manitoba
NB,New Brunswick
NL,Newfoundland and Labrador
NS,Nova Scotia
NT,Northwest Territories
NU,Nunavut
ON,Ontario
PE,Prince Edward Island
QC,Quebec
SK,Saskatchewan
YT,Yukon
1 AB Alberta
2 BC British Columbia
3 MB Manitoba
4 NB New Brunswick
5 NL Newfoundland and Labrador
6 NS Nova Scotia
7 NT Northwest Territories
8 NU Nunavut
9 ON Ontario
10 PE Prince Edward Island
11 QC Quebec
12 SK Saskatchewan
13 YT Yukon

4106
db/geodata/region_codes.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
AA,Armed Forces America
AE,Armed Forces
AP,Armed Forces Pacific
AK,Alaska
AL,Alabama
AR,Arkansas
AZ,Arizona
CA,California
CO,Colorado
CT,Connecticut
DC,District of Columbia
DE,Delaware
FL,Florida
GA,Georgia
GU,Guam
HI,Hawaii
IA,Iowa
ID,Idaho
IL,Illinois
IN,Indiana
KS,Kansas
KY,Kentucky
LA,Louisiana
MA,Massachusetts
MD,Maryland
ME,Maine
MI,Michigan
MN,Minnesota
MO,Missouri
MS,Mississippi
MT,Montana
NC,North Carolina
ND,North Dakota
NE,Nebraska
NH,New Hampshire
NJ,New Jersey
NM,New Mexico
NV,Nevada
NY,New York
OH,Ohio
OK,Oklahoma
OR,Oregon
PA,Pennsylvania
PR,Puerto Rico
RI,Rhode Island
SC,South Carolina
SD,South Dakota
TN,Tennessee
TX,Texas
UT,Utah
VA,Virginia
VI,Virgin Islands
VT,Vermont
WA,Washington
WI,Wisconsin
WV,West Virginia
WY,Wyoming
1 AA Armed Forces America
2 AE Armed Forces
3 AP Armed Forces Pacific
4 AK Alaska
5 AL Alabama
6 AR Arkansas
7 AZ Arizona
8 CA California
9 CO Colorado
10 CT Connecticut
11 DC District of Columbia
12 DE Delaware
13 FL Florida
14 GA Georgia
15 GU Guam
16 HI Hawaii
17 IA Iowa
18 ID Idaho
19 IL Illinois
20 IN Indiana
21 KS Kansas
22 KY Kentucky
23 LA Louisiana
24 MA Massachusetts
25 MD Maryland
26 ME Maine
27 MI Michigan
28 MN Minnesota
29 MO Missouri
30 MS Mississippi
31 MT Montana
32 NC North Carolina
33 ND North Dakota
34 NE Nebraska
35 NH New Hampshire
36 NJ New Jersey
37 NM New Mexico
38 NV Nevada
39 NY New York
40 OH Ohio
41 OK Oklahoma
42 OR Oregon
43 PA Pennsylvania
44 PR Puerto Rico
45 RI Rhode Island
46 SC South Carolina
47 SD South Dakota
48 TN Tennessee
49 TX Texas
50 UT Utah
51 VA Virginia
52 VI Virgin Islands
53 VT Vermont
54 WA Washington
55 WI Wisconsin
56 WV West Virginia
57 WY Wyoming

View File

@ -161,4 +161,11 @@ scheduled_sessions_2.sql
scheduled_sessions_3.sql
scheduled_sessions_cancel_all.sql
scheduled_sessions_started_at.sql
scheduled_sessions_open_rsvps.sql
scheduled_sessions_open_rsvps.sql
add_last_jam_user_fields.sql
remove_lat_lng_user_fields.sql
update_get_work_for_larger_radius.sql
periodic_emails.sql
remember_extra_scoring_data.sql
indexing_for_regions.sql
latency_tester.sql

View File

@ -0,0 +1,8 @@
ALTER TABLE users ADD COLUMN last_jam_addr BIGINT;
ALTER TABLE users ADD COLUMN last_jam_locidispid BIGINT;
-- (j)oin session as musician, (r)egister, (f)tue, (n)etwork test
ALTER TABLE users ADD COLUMN last_jam_updated_reason CHAR(1);
ALTER TABLE users ADD COLUMN last_jam_updated_at TIMESTAMP;

View File

@ -0,0 +1,2 @@
create index regions_countrycode_ndx on regions (countrycode);
create unique index regions_countrycode_region_ndx on regions (countrycode, region);

8
db/up/latency_tester.sql Normal file
View File

@ -0,0 +1,8 @@
CREATE TABLE latency_testers (
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
client_id VARCHAR(64) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE connections ALTER COLUMN user_id DROP NOT NULL;

15
db/up/periodic_emails.sql Normal file
View File

@ -0,0 +1,15 @@
ALTER TABLE email_batches ADD COLUMN type VARCHAR(64) NOT NULL DEFAULT 'JamRuby::EmailBatch';
ALTER TABLE email_batches ADD COLUMN sub_type VARCHAR(64);
ALTER TABLE email_batches ALTER COLUMN body DROP NOT NULL;
ALTER TABLE email_batches ALTER COLUMN subject DROP NOT NULL;
ALTER TABLE email_batch_sets ADD COLUMN trigger_index INTEGER NOT NULL DEFAULT 0;
ALTER TABLE email_batch_sets ADD COLUMN sub_type VARCHAR(64);
ALTER TABLE email_batch_sets ADD COLUMN user_id VARCHAR(64);
CREATE INDEX email_batch_sets_progress_idx ON email_batch_sets(user_id, sub_type);
CREATE INDEX users_musician_email_idx ON users(subscribe_email, musician);
UPDATE users set first_social_promoted_at = first_liked_us;
ALTER TABLE users DROP column first_liked_us;

View File

@ -0,0 +1,3 @@
-- add column to hold the raw scoring data that the client posted.
alter table scores add column scoring_data varchar(4000);

View File

@ -0,0 +1,2 @@
alter table users drop column lat;
alter table users drop column lng;

View File

@ -0,0 +1,16 @@
DROP FUNCTION get_work (mylocidispid BIGINT);
CREATE FUNCTION get_work (mylocidispid BIGINT, myaddr BIGINT) RETURNS TABLE (client_id VARCHAR(64)) ROWS 5 VOLATILE AS $$
BEGIN
CREATE TEMPORARY TABLE foo (locidispid BIGINT, locid INT);
INSERT INTO foo SELECT DISTINCT locidispid, locidispid/1000000 FROM connections WHERE client_type = 'client';
DELETE FROM foo WHERE locidispid IN (SELECT DISTINCT blocidispid FROM current_scores WHERE alocidispid = mylocidispid AND (current_timestamp - score_dt) < INTERVAL '24 hours');
DELETE FROM foo WHERE locid NOT IN (SELECT locid FROM geoiplocations WHERE geog && st_buffer((SELECT geog FROM geoiplocations WHERE locid = mylocidispid/1000000), 4023360));
CREATE TEMPORARY TABLE bar (client_id VARCHAR(64), locidispid BIGINT, r DOUBLE PRECISION);
INSERT INTO bar SELECT l.client_id, l.locidispid, random() FROM connections l, foo f WHERE l.locidispid = f.locidispid AND l.client_type = 'client' AND addr != myaddr;
DROP TABLE foo;
DELETE FROM bar b WHERE r != (SELECT MAX(r) FROM bar b0 WHERE b0.locidispid = b.locidispid);
RETURN QUERY SELECT b.client_id FROM bar b ORDER BY r LIMIT 5;
DROP TABLE bar;
RETURN;
END;
$$ LANGUAGE plpgsql;

2
monitor/.rspec Executable file
View File

@ -0,0 +1,2 @@
--color
--format progress

11
monitor/Gemfile Executable file
View File

@ -0,0 +1,11 @@
source "https://rubygems.org"
gem "rspec"
gem "capybara"
gem "capybara-screenshot"
gem "poltergeist"
gem "launchy" # used for opening pages/screenshots when debugging
# these used only for the Fixnum#seconds method :-/
gem "i18n"
gem "activesupport"

59
monitor/Gemfile.lock Executable file
View File

@ -0,0 +1,59 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (3.1.12)
multi_json (~> 1.0)
addressable (2.3.6)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
capybara-screenshot (0.3.19)
capybara (>= 1.0, < 3)
launchy
cliver (0.3.2)
diff-lcs (1.2.5)
i18n (0.6.9)
launchy (2.4.2)
addressable (~> 2.3)
mime-types (2.2)
mini_portile (0.5.3)
multi_json (1.10.0)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
nokogiri (1.6.1-x86-mingw32)
mini_portile (~> 0.5.0)
poltergeist (1.5.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
rack (1.5.2)
rack-test (0.6.2)
rack (>= 1.0)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.8)
rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6)
websocket-driver (0.3.3)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
x86-mingw32
DEPENDENCIES
activesupport
capybara
capybara-screenshot
i18n
launchy
poltergeist
rspec

View File

@ -1,9 +1,14 @@
require 'spec_helper'
# these tests MUST be idempotent and DO use actual production user accounts on www
www = 'http://www.jamkazam.com'
# these tests should be idempotent, and not spammy to other JK users
# because they DO use actual production user accounts on www (see the TestUsers below)
describe "Production site at #{www}", :test_www => true, :js => true, :type => :feature, :capybara_feature => true do
# Jenkins executes rspec on this folder every 15 minutes or so.
# SO don't use this to test something like a public session unless you want all the world to see
www = ENV['MONITOR_URL'] || 'http://www.jamkazam.com'
describe "Deployed site at #{www}", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
@ -12,7 +17,6 @@ describe "Production site at #{www}", :test_www => true, :js => true, :type =>
Capybara.current_driver = Capybara.javascript_driver
Capybara.app_host = www
Capybara.run_server = false
Capybara.default_wait_time = 10
end
TestUser = Class.new do
@ -28,7 +32,6 @@ describe "Production site at #{www}", :test_www => true, :js => true, :type =>
first_name + ' ' + last_name
end
end
user1 = TestUser.new({ email: 'anthony+jim@jamkazam.com', password: 'j4m!t3st3r', first_name: 'Jim', last_name: 'Smith', id: '68e8eea2-140d-44c1-b711-10d07ce70f96' })
user2 = TestUser.new({ email: 'anthony+john@jamkazam.com', password: 'j4m!t3st3r', first_name: 'John', last_name: 'Jones', id: '5bbcf689-2f73-452d-815a-c4f44e9e7f3e' })
@ -46,7 +49,7 @@ describe "Production site at #{www}", :test_www => true, :js => true, :type =>
end
it "is possible for #{user1} and #{user2} to see each other online, and to send messages" do
# this example heavily based on text_message_spec.rb
# this example heavily based on text_message_spec.rb in 'web'
in_client(user1) do
sign_in_poltergeist(user1)
@ -89,4 +92,3 @@ describe "Production site at #{www}", :test_www => true, :js => true, :type =>
end
end

50
monitor/spec/spec_helper.rb Executable file
View File

@ -0,0 +1,50 @@
require 'rubygems'
require 'active_support/time'
require 'capybara'
require 'capybara/rspec'
require 'capybara-screenshot'
require 'capybara-screenshot/rspec'
require 'capybara/poltergeist'
require 'support/client_interactions' #TODO: Strip out the helper methods that production_spec does not use
require 'support/utilities'
require 'support/stubs' # to make the JamXXXX warnings go away
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# Require this file using `require "spec_helper"` to ensure that it is only
# loaded once.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
config.filter_run :focus
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = 'random'
config.include Capybara::DSL
config.before(:each) do
page.driver.headers = { 'User-Agent' => 'monitor' }
end
end
#Capybara.register_driver :poltergeist do |app|
# Capybara::Poltergeist::Driver.new(app, { phantomjs_logger: File.open('console.log', 'w') })
#end
Capybara.javascript_driver = :poltergeist
Capybara.default_driver = :poltergeist
Capybara.run_server = false # since we're testing an app outside this project
Capybara.default_wait_time = 15 # ^^ ditto
Capybara.configure do |config|
config.match = :one
config.exact_options = true
config.ignore_hidden_elements = true
config.visible_text_only = true
end

View File

@ -0,0 +1,126 @@
# methods here all assume you are in /client
NOTIFICATION_PANEL = '[layout-id="panelNotifications"]'
# enters text into the search sidebar
def site_search(text, options = {})
within('#searchForm') do
fill_in "search-input", with: text
end
if options[:expand]
page.driver.execute_script("jQuery('#searchForm').submit()")
find('h1', text:'search results')
end
end
# goes to the musician tile, and tries to find a musician
def find_musician(user)
visit "/client#/musicians"
timeout = 30
start = Time.now
# scroll by 100px until we find a user with the right id
while page.all('#end-of-musician-list').length == 0
page.execute_script('jQuery("#musician-filter-results").scrollTo("+=100px", 0, {axis:"y"})')
found = page.all(".result-list-button-wrapper[data-musician-id='#{user.id}']")
if found.length == 1
return found[0]
elsif found.length > 1
raise "ambiguous results in musician list"
end
if Time.now - start > timeout
raise "unable to find musician #{user} within #{timeout} seconds"
end
end
raise "unable to find musician #{user}"
end
def initiate_text_dialog(user)
# verify that the chat window is grayed out
site_search(user.first_name, expand: true)
find("#search-results a[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name).hover_intent
find('#musician-hover #btnMessage').trigger(:click)
find('h1', text: 'conversation with ' + user.name)
end
# sends a text message in the chat interface.
def send_text_message(msg, options={})
find('#text-message-dialog') # assert that the dialog is showing already
within('#text-message-dialog form.text-message-box') do
fill_in 'new-text-message', with: msg
end
find('#text-message-dialog .btn-send-text-message').trigger(:click)
find('#text-message-dialog .previous-message-text', text: msg) unless options[:should_fail]
# close the dialog if caller specified close_on_send
if options[:close_on_send]
find('#text-message-dialog .btn-close-dialog', text: 'CLOSE').trigger(:click) if options[:close_on_send]
page.should have_no_selector('#text-message-dialog')
end
if options[:should_fail]
find('#notification').should have_text(options[:should_fail])
end
end
# sends a chat message during session
def send_chat_message(msg)
find("[layout-id=\"panelChat\"] .chat-sender").should be_visible
within("[layout-id=\"panelChat\"] .chat-sender form.chat-message-form") do
fill_in 'new-chat-message', with: msg
end
find("[layout-id=\"panelChat\"] .chat-sender .btn-send-chat-message").trigger(:click)
end
def open_notifications
find("#{NOTIFICATION_PANEL} .panel-header").trigger(:click)
end
def hover_intent(element)
element.hover
element.hover
end
# forces document.hasFocus() to return false
def document_blur
page.evaluate_script(%{(function() {
// save original
if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
window.document.hasFocus = function() {
console.log("document.hasFocus() returns false");
return false;
}
})()})
end
def document_focus
page.evaluate_script(%{(function() {
// save original
if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
window.document.hasFocus = function() {
console.log("document.hasFocus() returns true");
return true;
}
})()})
end
# simulates focus event on window
def window_focus
page.evaluate_script(%{window.jQuery(window).trigger('focus');})
end
def close_websocket
page.evaluate_script("window.JK.JamServer.close(true)")
end

9
monitor/spec/support/stubs.rb Executable file
View File

@ -0,0 +1,9 @@
module JamRuby
class User
end
end
def signin_path
'/signin'
end

481
monitor/spec/support/utilities.rb Executable file
View File

@ -0,0 +1,481 @@
# add a hover_intent method to element, so that you can do find(selector).hover_intent
module Capybara
module Node
class Element
def attempt_hover
begin
hover
rescue => e
end
end
def hover_intent
hover
sleep 0.3
attempt_hover
sleep 0.3
attempt_hover
end
end
end
end
# holds a single test's session name's, mapped to pooled session names
$capybara_session_mapper = {}
# called in before (or after) test, to make sure each test run has it's own map of session names
def reset_session_mapper
$capybara_session_mapper.clear
Capybara.session_name = :default
end
# manages the mapped session name
def mapped_session_name(session_name)
return :default if session_name == :default # special treatment for the built-in session
$capybara_session_mapper[session_name] ||= 'session_' + $capybara_session_mapper.length.to_s
end
# in place of ever using Capybara.session_name directly,
# this utility is used to handle the mapping of session names in a way across all tests runs
def in_client(name)
session_name = name.class == JamRuby::User ? name.id : name
Capybara.session_name = mapped_session_name(session_name)
yield
end
def cookie_jar
Capybara.current_session.driver.browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar
end
#see also ruby/spec/support/utilities.rb
JAMKAZAM_TESTING_BUCKET = 'jamkazam-testing' #at least, this is the name given in jam-ruby
def wipe_s3_test_bucket
s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id,
:secret_access_key => Rails.application.config.aws_secret_access_key)
test_bucket = s3.buckets[JAMKAZAM_TESTING_BUCKET]
if test_bucket.name == JAMKAZAM_TESTING_BUCKET
test_bucket.objects.each do |obj|
obj.delete
end
end
end
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "SIGN IN"
# Sign in when not using Capybara as well.
cookie_jar[:remember_token] = user.remember_token
end
def set_cookie(k, v)
case Capybara.current_session.driver
when Capybara::Poltergeist::Driver
page.driver.set_cookie(k,v)
when Capybara::RackTest::Driver
headers = {}
Rack::Utils.set_cookie_header!(headers,k,v)
cookie_string = headers['Set-Cookie']
Capybara.current_session.driver.browser.set_cookie(cookie_string)
when Capybara::Selenium::Driver
page.driver.browser.manage.add_cookie(:name=>k, :value=>v)
else
raise "no cookie-setter implemented for driver #{Capybara.current_session.driver.class.name}"
end
end
def sign_in_poltergeist(user, options = {})
validate = options[:validate]
validate = true if validate.nil?
visit signin_path
fill_in "Email Address:", with: user.email
fill_in "Password:", with: user.password
click_button "SIGN IN"
wait_until_curtain_gone
# presence of this means websocket gateway is not working
page.should have_no_selector('.no-websocket-connection') if validate
end
def sign_out()
if Capybara.javascript_driver == :poltergeist
page.driver.remove_cookie(:remember_token)
else
page.driver.browser.manage.remove_cookie :name => :remember_token
end
end
def sign_out_poltergeist(options = {})
find('.userinfo').hover()
click_link 'Sign Out'
should_be_at_root if options[:validate]
end
def should_be_at_root
find('h1', text: 'Play music together over the Internet as if in the same room')
end
def leave_music_session_sleep_delay
# add a buffer to ensure WSG has enough time to expire
sleep_dur = (Rails.application.config.websocket_gateway_connect_time_stale_browser +
Rails.application.config.websocket_gateway_connect_time_expire_browser) * 1.4
sleep sleep_dur
end
def wait_for_ajax(wait=Capybara.default_wait_time)
wait = wait * 10 #(because we sleep .1)
counter = 0
while page.execute_script("$.active").to_i > 0
counter += 1
sleep(0.1)
raise "AJAX request took longer than #{wait} seconds." if counter >= wait
end
end
# waits until the user object has been requested, which comes after the 'curtain' is lifted
# and after a call to /api/user/:id for the current user is called initially
def wait_until_user(wait=Capybara.default_wait_time)
wait = wait * 10 #(because we sleep .1)
counter = 0
# while page.execute_script("$('.curtain').is(:visible)") == "true"
# counter += 1
# sleep(0.1)
# raise "Waiting for user to populate took longer than #{wait} seconds." if counter >= wait
# end
end
def wait_until_curtain_gone
page.should have_no_selector('.curtain')
end
def wait_to_see_my_track
within('div.session-mytracks') {first('div.session-track.track')}
end
def repeat_for(duration=Capybara.default_wait_time)
finish_time = Time.now + duration.seconds
loop do
yield
sleep 1 # by default this will execute the block every 1 second
break if (Time.now > finish_time)
end
end
def determine_test_name(metadata, test_name_buffer = '')
description = metadata[:description_args]
if description.kind_of?(Array)
description = description[0]
end
if metadata.has_key? :example_group
return determine_test_name(metadata[:example_group], "#{description} #{test_name_buffer}")
else
return "#{description} #{test_name_buffer}"
end
end
def get_description
description = example.metadata[:description_args]
if description.kind_of?(Array)
description = description[0]
end
return description
end
# will select the value from a easydropdown'ed select element
def jk_select(text, select)
# the approach here is to find the hidden select element, and work way back up to the elements that need to be interacted with
find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown")]').trigger(:click)
find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown-wrapper") and contains(@class, "easydropdown-wrapper") and contains(@class, "open")]').find('li', text: text).trigger(:click)
# works, but is 'cheating' because of visible = false
#select(genre, :from => 'genres', :visible => false)
end
# takes, or creates, a unique session description which is returned for subsequent calls to join_session to use
# in finding this session)
def create_session(options={})
creator = options[:creator] || FactoryGirl.create(:user)
unique_session_desc = options[:description] || "create_join_session #{SecureRandom.urlsafe_base64}"
genre = options[:genre] || 'Rock'
musician_access = options[:musician_access].nil? ? true : options[:musician_access]
fan_access = options[:fan_access].nil? ? true : options[:fan_access]
# create session in one client
in_client(creator) do
page.driver.resize(1500, 800) # makes sure all the elements are visible
emulate_client
sign_in_poltergeist creator
wait_until_curtain_gone
visit "/client#/createSession"
expect(page).to have_selector('h2', text: 'session info')
within('#create-session-form') do
fill_in('description', :with => unique_session_desc)
#select(genre, :from => 'genres', :visible => false) # this works, but is 'cheating' because easydropdown hides the native select element
jk_select(genre, '#create-session-form select[name="genres"]')
jk_select(musician_access ? 'Public' : 'Private', '#create-session-form select#musician-access')
jk_select(fan_access ? 'Public' : 'Private', '#create-session-form select#fan-access')
find('#create-session-form div.musician-access-false.iradio_minimal').trigger(:click)
find('div.intellectual-property ins').trigger(:click)
find('#btn-create-session').trigger(:click) # fails if page width is low
end
# verify that the in-session page is showing
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
return creator, unique_session_desc, genre
end
# this code assumes that there are no music sessions in the database. it should fail on the
# find('.join-link') call if > 1 session exists because capybara will complain of multiple matches
def join_session(joiner, options)
description = options[:description]
in_client(joiner) do
page.driver.resize(1500, 800) # makes sure all the elements are visible
emulate_client
sign_in_poltergeist joiner
wait_until_curtain_gone
visit "/client#/findSession"
# verify the session description is seen by second client
expect(page).to have_text(description)
find('.join-link').trigger(:click)
find('#btn-accept-terms').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
def emulate_client
page.driver.headers = { 'User-Agent' => ' JamKazam ' }
end
def create_join_session(creator, joiners=[], options={})
options[:creator] = creator
creator, unique_session_desc = create_session(options)
# find session in second client
joiners.each do |joiner|
join_session(joiner, description: unique_session_desc)
end
return creator, unique_session_desc
end
def formal_leave_by user
in_client(user) do
find('#session-leave').trigger(:click)
#find('#btn-accept-leave-session').trigger(:click)
expect(page).to have_selector('h2', text: 'feed')
end
end
def start_recording_with(creator, joiners=[], genre=nil)
create_join_session(creator, joiners, {genre: genre})
in_client(creator) do
find('#recording-start-stop').trigger(:click)
find('#recording-status').should have_content 'Stop Recording'
end
joiners.each do |joiner|
in_client(joiner) do
find('#notification').should have_content 'started a recording'
find('#recording-status').should have_content 'Stop Recording'
end
end
end
def stop_recording
find('#recording-start-stop').trigger(:click)
end
def assert_recording_finished
find('#recording-status').should have_content 'Make a Recording'
should have_selector('h1', text: 'recording finished')
end
def check_recording_finished_for(users=[])
users.each do |user|
in_client(user) do
assert_recording_finished
end
end
end
def claim_recording(name, description)
find('#recording-finished-dialog h1')
fill_in "claim-recording-name", with: name
fill_in "claim-recording-description", with: description
find('#keep-session-recording').trigger(:click)
page.should have_no_selector('h1', text: 'recording finished')
end
def set_session_as_private()
find('#session-settings-button').trigger(:click)
within('#session-settings-dialog') do
jk_select("Private", '#session-settings-dialog #session-settings-musician-access')
#select('Private', :from => 'session-settings-musician-access')
find('#session-settings-dialog-submit').trigger(:click)
end
# verify it's dismissed
page.should have_no_selector('h1', text: 'update session settings')
end
def set_session_as_public()
find('#session-settings-button').trigger(:click)
within('#session-settings-dialog') do
jk_select("Public", '#session-settings-dialog #session-settings-musician-access')
# select('Public', :from => 'session-settings-musician-access')
find('#session-settings-dialog-submit').trigger(:click)
end
# verify it's dismissed
page.should have_no_selector('h1', text: 'update session settings')
end
def get_options(selector)
find(selector, :visible => false).all('option', :visible => false).collect(&:text).uniq
end
def selected_genres(selector='#session-settings-genre')
page.evaluate_script("JK.GenreSelectorHelper.getSelectedGenres('#{selector}')")
end
def random_genre
['African',
'Ambient',
'Asian',
'Blues',
'Classical',
'Country',
'Electronic',
'Folk',
'Hip Hop',
'Jazz',
'Latin',
'Metal',
'Pop',
'R&B',
'Reggae',
'Religious',
'Rock',
'Ska',
'Other'].sample
end
def change_session_genre #randomly just change it
here = 'select.genre-list'
#wait_for_ajax
find('#session-settings-button').trigger(:click)
find('#session-settings-dialog') # ensure the dialog is visible
within('#session-settings-dialog') do
wait_for_ajax
@new_genre = get_options(here).-(["Select Genre"]).-(selected_genres).sample.to_s
jk_select(@new_genre, '#session-settings-dialog select[name="genres"]')
wait_for_ajax
find('#session-settings-dialog-submit').trigger(:click)
end
return @new_genre
end
def get_session_genre
here = 'select.genre-list'
find('#session-settings-button').trigger(:click)
wait_for_ajax
@current_genres = selected_genres
find('#session-settings-dialog-submit').trigger(:click)
return @current_genres.join(" ")
end
def find_session_contains?(text)
visit "/client#/findSession"
wait_for_ajax
within('#find-session-form') do
expect(page).to have_text(text)
end
end
def assert_all_tracks_seen(users=[])
users.each do |user|
in_client(user) do
users.reject {|u| u==user}.each do |other|
find('div.track-label', text: other.name)
#puts user.name + " is able to see " + other.name + "\'s track"
end
end
end
end
def view_profile_of user
id = user.kind_of?(JamRuby::User) ? user.id : user
# assume some user signed in already
visit "/client#/profile/#{id}"
wait_until_curtain_gone
end
def view_band_profile_of band
id = band.kind_of?(JamRuby::Band) ? band.id :
band.kind_of?(JamRuby::BandMusician) ? band.bands.first.id : band
visit "/client#/bandProfile/#{id}"
wait_until_curtain_gone
end
def sidebar_search_for string, category
visit "/client#/home"
find('#search-input')
fill_in "search", with: string
sleep 1
page.execute_script("JK.Sidebar.searchForInput()")
wait_for_ajax
jk_select(category, "search_text_type")
wait_for_ajax
end
def show_user_menu
page.execute_script("$('ul.shortcuts').show()")
#page.execute_script("JK.UserDropdown.menuHoverIn()")
end
# wait for the easydropdown version of the specified select element to become visible
def wait_for_easydropdown(select)
find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown")]')
end
# defaults to enter key (13)
def send_key(selector, keycode = 13)
keypress_script = "var e = $.Event('keyup', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
page.driver.execute_script(keypress_script)
end
def special_characters
["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"]
end
def garbage length
output = ''
length.times { output << special_characters.sample }
output.slice(0, length)
end

8
resetdb.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh -x
dropdb --if-exists jam
dropdb --if-exists jam_db_build
dropdb --if-exists jam_ruby_test
dropdb --if-exists jam_web_test
dropdb --if-exists jam_websockets_test
createdb -Upostgres jam || sudo su postgres -c "createdb jam"

View File

@ -4,7 +4,7 @@ unless ENV["LOCAL_DEV"] == "1"
source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/'
end
devenv = ENV["BUILD_NUMBER"].nil? || ENV["TEST_WWW"] == "1"
devenv = ENV["BUILD_NUMBER"].nil?
if devenv
gem 'jam_db', :path=> "../db/target/ruby_package"
@ -57,6 +57,7 @@ group :test do
gem 'faker'
gem 'resque_spec' #, :path => "/home/jam/src/resque_spec/"
gem 'timecop'
gem 'rspec-prof'
end
# Specify your gem's dependencies in jam_ruby.gemspec

View File

@ -42,7 +42,11 @@ require "jam_ruby/resque/scheduled/icecast_config_retry"
require "jam_ruby/resque/scheduled/icecast_source_check"
require "jam_ruby/resque/scheduled/cleanup_facebook_signup"
require "jam_ruby/resque/scheduled/unused_music_notation_cleaner"
require "jam_ruby/resque/scheduled/user_progress_emailer"
require "jam_ruby/resque/scheduled/daily_session_emailer"
require "jam_ruby/resque/scheduled/new_musician_emailer"
require "jam_ruby/resque/google_analytics_event"
require "jam_ruby/resque/batch_email_job"
require "jam_ruby/mq_router"
require "jam_ruby/base_manager"
require "jam_ruby/connection_manager"
@ -82,6 +86,7 @@ require "jam_ruby/models/band_invitation"
require "jam_ruby/models/band_musician"
require "jam_ruby/models/connection"
require "jam_ruby/models/diagnostic"
require "jam_ruby/models/latency_tester"
require "jam_ruby/models/friendship"
require "jam_ruby/models/active_music_session"
require "jam_ruby/models/music_session_comment"
@ -145,10 +150,14 @@ require "jam_ruby/models/country"
require "jam_ruby/models/region"
require "jam_ruby/models/city"
require "jam_ruby/models/email_batch"
require "jam_ruby/models/email_batch_periodic"
require "jam_ruby/models/email_batch_new_musician"
require "jam_ruby/models/email_batch_progression"
require "jam_ruby/models/email_batch_scheduled_sessions"
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/app/mailers/progress_mailer"
require "jam_ruby/models/affiliate_partner"
require "jam_ruby/models/chat_message"

View File

@ -1,5 +1,6 @@
module JamRuby
class BatchMailer < JamRuby::AsyncMailer
class BatchMailer < ActionMailer::Base
include SendGrid
layout "user_mailer"
sendgrid_category :use_subject_lines
@ -23,10 +24,10 @@ module JamRuby
end
end
def send_batch_email(batch_id, user_ids)
users = User.find_all_by_id(user_ids)
def send_batch_email(batch_id, user_id)
user = User.find_by_id(user_id)
batch = EmailBatch.find(batch_id)
self._send_batch(batch, users)
self._send_batch(batch, [user])
end
def send_batch_email_test(batch_id)

View File

@ -0,0 +1,26 @@
module JamRuby
class ProgressMailer < ActionMailer::Base
include SendGrid
layout "user_mailer"
sendgrid_category :use_subject_lines
sendgrid_unique_args :env => Environment.mode
default :from => UserMailer::DEFAULT_SENDER
def send_reminder(batch_set)
user = batch_set.user
sendgrid_recipients([user.email])
sendgrid_substitute('@USERID', [user.id])
sendgrid_substitute(EmailBatchProgression::VAR_FIRST_NAME, [user.first_name])
mail(:to => user.email,
:subject => batch_set.subject,
:title => batch_set.title) do |format|
format.text { render batch_set.sub_type }
format.html { render batch_set.sub_type }
end
end
end
end

View File

@ -41,6 +41,7 @@
sendgrid_recipients([user.email])
sendgrid_substitute('@USERID', [user.id])
sendgrid_substitute(EmailBatchProgression::VAR_FIRST_NAME, [user.first_name])
mail(:to => user.email, :subject => "Welcome to JamKazam") do |format|
format.text
@ -101,14 +102,14 @@
end
end
def new_musicians(user, new_nearby, host='www.jamkazam.com')
@user, @new_nearby, @host = user, new_nearby, host
def new_musicians(user, new_musicians, host='www.jamkazam.com')
@user, @new_musicians, @host = user, new_musicians, host
sendgrid_recipients([user.email])
sendgrid_substitute('@USERID', [user.id])
sendgrid_unique_args :type => "new_musicians"
mail(:to => user.email, :subject => "JamKazam New Musicians in Your Area") do |format|
mail(:to => user.email, :subject => EmailBatchNewMusician.subject) do |format|
format.text
format.html
end
@ -222,7 +223,7 @@
subject = "Session Invitation"
unique_args = {:type => "scheduled_session_invitation"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -241,7 +242,7 @@
subject = "Session RSVP"
unique_args = {:type => "scheduled_session_rsvp"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -260,7 +261,7 @@
subject = "Session RSVP Approved"
unique_args = {:type => "scheduled_session_rsvp_approved"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -279,7 +280,7 @@
subject = "Session RSVP Cancelled"
unique_args = {:type => "scheduled_session_rsvp_cancelled"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -298,7 +299,7 @@
subject = "Your Session RSVP Cancelled"
unique_args = {:type => "scheduled_session_rsvp_cancelled_org"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -317,7 +318,7 @@
subject = "Session Cancelled"
unique_args = {:type => "scheduled_session_cancelled"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -336,7 +337,7 @@
subject = "Session Rescheduled"
unique_args = {:type => "scheduled_session_rescheduled"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -355,7 +356,7 @@
subject = "Session Rescheduled"
unique_args = {:type => "scheduled_session_reminder"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
@ -374,7 +375,7 @@
subject = "New Session Comment"
unique_args = {:type => "scheduled_session_comment"}
@body = msg
@session_name = session.description
@session_name = session.name
@session_date = session.scheduled_start
@comment = comment
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
@ -390,6 +391,24 @@
end
end
def scheduled_session_daily(receiver, sessions_and_latency)
sendgrid_category "Notification"
sendgrid_unique_args :type => "scheduled_session_daily"
sendgrid_recipients([receiver.email])
sendgrid_substitute('@USERID', [receiver.id])
@user = receiver
@sessions_and_latency = sessions_and_latency
@title = 'New Scheduled Sessions Matched to You'
mail(:to => receiver.email,
:subject => EmailBatchScheduledSessions.subject) do |format|
format.text
format.html
end
end
def band_session_join(email, msg, session_id)
subject = "A band that you follow has joined a session"
unique_args = {:type => "band_session_join"}

View File

@ -0,0 +1,14 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you have downloaded the free JamKazam application, but you have not yet run the app. You can find other musicians and listen to sessions and recordings on our website, but you need to run the JamKazam application to play with other musicians online. If you are having trouble installing or running the app, please visit our JamKazam support center at the link below, and post a request for assistance so that we can help you get up and running.
</p>
<p>
<a style="color: #588C98;" href="https://jamkazam.desk.com/">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,7 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you have downloaded the free JamKazam application, but you have not yet run the app. You can find other musicians and listen to sessions and recordings on our website, but you need to run the JamKazam application to play with other musicians online. If you are having trouble installing or running the app, please visit our JamKazam support center at the link below, and post a request for assistance so that we can help you get up and running.
-- Team JamKazam

View File

@ -0,0 +1,18 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you have registered as a JamKazam musician, but you have not yet downloaded and started using the free JamKazam application. You can find other musicians and listen to sessions and recordings on our website, but you need the free JamKazam application to play with other musicians online. Please click the link below to go to the download page for the free JamKazam application, or visit our JamKazam support center so that we can help you get up and running.
</p>
<p>
<a style="color: #588C98;" href="http://www.jamkazam.com/downloads">Go to Download Page</a>
</p>
<p>
<a style="color: #588C98;" href="https://jamkazam.desk.com">Go to Support Center</a>
</p>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,11 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you have registered as a JamKazam musician, but you have not yet downloaded and started using the free JamKazam application. You can find other musicians and listen to sessions and recordings on our website, but you need the free JamKazam application to play with other musicians online. Please click the link below to go to the download page for the free JamKazam application, or visit our JamKazam support center so that we can help you get up and running.
Go to Download Page: http://www.jamkazam.com/downloads
Go to Support Center: https://jamkazam.desk.com
-- Team JamKazam

View File

@ -0,0 +1,19 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you have not yet successfully set up your audio gear and passed the JamKazam latency and input/output audio gear tests. This means that you cannot yet play in online sessions with other musicians. If you are having trouble with this step, please click the link below for a knowledge base article that can help you get past this hurdle. If the test says your audio gear is not fast enough, or if your audio quality sounds poor, or if you are just confused, its very likely the tips in this article will help you get things set up and optimized so you can start playing online.
</p>
<p><a style="color: #588C98;" href="http://bit.ly/1i4Uul4">http://bit.ly/1i4Uul4</a>
</p>
<p>And if this knowledge base article does not get you fixed up, please visit our JamKazam support center at the link below, and post a request for assistance so that we can help you get up and running:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,13 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you have not yet successfully set up your audio gear and passed the JamKazam latency and input/output audio gear tests. This means that you cannot yet play in online sessions with other musicians. If you are having trouble with this step, please click the link below for a knowledge base article that can help you get past this hurdle. If the test says your audio gear is not fast enough, or if your audio quality sounds poor, or if you are just confused, its very likely the tips in this article will help you get things set up and optimized so you can start playing online.
http://bit.ly/1i4Uul4
And if this knowledge base article does not get you fixed up, please visit our JamKazam support center at the link below, and post a request for assistance so that we can help you get up and running:
https://jamkazam.desk.com
-- Team JamKazam

View File

@ -0,0 +1,31 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you havent yet played in a JamKazam session with multiple musicians that lasted long enough to be productive or fun. Since thats the whole point of the service, we wanted to reach out to see if we can help you. Please take a quick look at the suggestions below to see if they help you!
</p>
<p>Find Other Musicians on JamKazam<br />
Its still very early in our companys development, so we dont have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you wont see private sessions where groups of musicians dont want to be interrupted in their sessions.
</p>
<p>If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: <a href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
</p>
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
</p>
<p>Invite Your Friends to Play<br />
One of the best ways to connect and play with others is to invite your friends from the “real world” to join you on JamKazam, and then set up a time to meet online and get into a session together. To do this, just go to www.jamkazam.com and sign in. Then move your mouse over your picture or name in the upper right corner of the screen. A menu will be displayed. Click the Invite Friends option, and then you can click Facebook, Google, or Email to easily invite your friends from different services to join you on JamKazam.
</p>
<p>Troubleshoot Session Problems<br />
If you are having audio quality problems or other issues when you get into a session, please click the link below to visit our support center, and check the knowledge base articles under the Troubleshooting header to find solutions. And if that doesnt work, please post a request for assistance in the support center so that we can help you get up and running:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,22 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you havent yet played in a JamKazam session with multiple musicians that lasted long enough to be productive or fun. Since thats the whole point of the service, we wanted to reach out to see if we can help you. Please take a quick look at the suggestions below to see if they help you!
Find Other Musicians on JamKazam
Its still very early in our companys development, so we dont have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you wont see private sessions where groups of musicians dont want to be interrupted in their sessions.
If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: http://www.jamkazam.com/client#/musicians
This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
Invite Your Friends to Play
One of the best ways to connect and play with others is to invite your friends from the “real world” to join you on JamKazam, and then set up a time to meet online and get into a session together. To do this, just go to www.jamkazam.com and sign in. Then move your mouse over your picture or name in the upper right corner of the screen. A menu will be displayed. Click the Invite Friends option, and then you can click Facebook, Google, or Email to easily invite your friends from different services to join you on JamKazam.
Troubleshoot Session Problems
If you are having audio quality problems or other issues when you get into a session, please click the link below to visit our support center, and check the knowledge base articles under the Troubleshooting header to find solutions. And if that doesnt work, please post a request for assistance in the support center so that we can help you get up and running:
https://jamkazam.desk.com
-- Team JamKazam

View File

@ -0,0 +1,27 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you havent yet connected with any friends on JamKazam. Connecting with friends is the best way to help you get into sessions with other musicians on JamKazam. Here are a couple of good ways to connect with others.
</p>
<p>Find Other Musicians on JamKazam<br />
To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: <a href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
</p>
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
</p>
<p>Invite Your Friends to Play<br >
One of the best ways to connect and play with others is to invite your friends from the “real world” to join you on JamKazam, and then set up a time to meet online and get into a session together. To do this, just go to www.jamkazam.com and sign in. Then move your mouse over your picture or name in the upper right corner of the screen. A menu will be displayed. Click the Invite Friends option, and then you can click Facebook, Google, or Email to easily invite your friends from different services to join you on JamKazam.
</p>
<p>If you have any trouble, please visit our support center at the link below any time to get help:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com/">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,20 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you havent yet connected with any friends on JamKazam. Connecting with friends is the best way to help you get into sessions with other musicians on JamKazam. Here are a couple of good ways to connect with others.
Find Other Musicians on JamKazam
To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: http://www.jamkazam.com/client#/musicians
This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
Invite Your Friends to Play
One of the best ways to connect and play with others is to invite your friends from the “real world” to join you on JamKazam, and then set up a time to meet online and get into a session together. To do this, just go to www.jamkazam.com and sign in. Then move your mouse over your picture or name in the upper right corner of the screen. A menu will be displayed. Click the Invite Friends option, and then you can click Facebook, Google, or Email to easily invite your friends from different services to join you on JamKazam.
If you have any trouble, please visit our support center at the link below any time to get help:
https://jamkazam.desk.com
-- Team JamKazam

View File

@ -0,0 +1,16 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you have not invited any of your friends to join you to play together on JamKazam. Its still very early in our companys development, so we dont have zillions of users online on our service yet. One of the best ways to connect and play with others is to invite your friends from the “real world” to join you on JamKazam, and then set up a time to meet online and get into a session together. To do this, just go to www.jamkazam.com and sign in. Then move your mouse over your picture or name in the upper right corner of the screen. A menu will be displayed. Click the Invite Friends option, and then you can click Facebook, Google, or Email to easily invite your friends from different services to join you on JamKazam.
</p>
<p>If you have any trouble, please visit our support center at the link below any time to get help:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,11 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you have not invited any of your friends to join you to play together on JamKazam. Its still very early in our companys development, so we dont have zillions of users online on our service yet. One of the best ways to connect and play with others is to invite your friends from the “real world” to join you on JamKazam, and then set up a time to meet online and get into a session together. To do this, just go to www.jamkazam.com and sign in. Then move your mouse over your picture or name in the upper right corner of the screen. A menu will be displayed. Click the Invite Friends option, and then you can click Facebook, Google, or Email to easily invite your friends from different services to join you on JamKazam.
If you have any trouble, please visit our support center at the link below any time to get help:
https://jamkazam.desk.com
-- Team JamKazam

View File

@ -0,0 +1,14 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>JamKazam is a young company/service built through the sweat and commitment of a small group of music-loving techies. Please help us continue to grow the service and attract more musicians to play online by liking and/or following us on Facebook, Twitter, and Google+. Just click the icons below to give us little push, thanks!
</p>
<% [:twitter, :facebook, :google].each do |site| %>
<%= link_to(image_tag("http://www.jamkazam.com/assets/content/icon_#{site}.png", :style => "vertical-align:top"), "http://www.jamkazam.com/endorse/@USERID/#{site}?src=email") %>&nbsp;
<% end %>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,12 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
JamKazam is a young company/service built through the sweat and commitment of a small group of music-loving techies. Please help us continue to grow the service and attract more musicians to play online by liking and/or following us on Facebook, Twitter, and Google+. Just click the icons below to give us little push, thanks!
<% [:twitter, :facebook, :google].each do |site| %>
http://www.jamkazam.com/endorse/@USERID/#{site}?src=email
<% end %>
-- Team JamKazam

View File

@ -0,0 +1,19 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you havent yet rated any of your JamKazam sessions as “good”. It may be that you just are not a “rater”, and that is totally fine. But if you are not having good, high quality, productive and fun sessions, we want to help you get there!
</p>
<p>If you are having audio quality problems or other issues when you get into a session, please click the link below to visit our support center, and check the knowledge base articles under the Troubleshooting header to find solutions. And if that doesnt work, please post a request for assistance in the support center so that we can help you get up and running:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>We really want you to be successful and have fun with this new way of playing music with others, so please reach out and let us help you!
</p>
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,13 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you havent yet rated any of your JamKazam sessions as “good”. It may be that you just are not a “rater”, and that is totally fine. But if you are not having good, high quality, productive and fun sessions, we want to help you get there!
If you are having audio quality problems or other issues when you get into a session, please click the link below to visit our support center, and check the knowledge base articles under the Troubleshooting header to find solutions. And if that doesnt work, please post a request for assistance in the support center so that we can help you get up and running:
https://jamkazam.desk.com
We really want you to be successful and have fun with this new way of playing music with others, so please reach out and let us help you!
-- Team JamKazam

View File

@ -0,0 +1,19 @@
<% provide(:title, @title) %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p>We noticed that you havent yet made a recording during any of your JamKazam sessions. Recordings are extra special on JamKazam because:
</p>
<ul><li>Recordings are made both as a master mix and at the track/stem level.</li>
<li>You can easily play along with your recordings when your friends arent available.</li>
<li>You can share your recordings with family, friends and fans via Facebook, Twitter, etc.</li>
</ul>
<!--<p>To understand all of the things you can do with recordings on JamKazam, watch this YouTube tutorial video:<br />
www.youtube.com/blahblahblah
</p>-->
<p>-- Team JamKazam
</p>

View File

@ -0,0 +1,11 @@
<%= @title %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you havent yet made a recording during any of your JamKazam sessions. Recordings are extra special on JamKazam because:
- Recordings are made both as a master mix and at the track/stem level.
- You can easily play along with your recordings when your friends arent available.
- You can share your recordings with family, friends and fans via Facebook, Twitter, etc.
-- Team JamKazam

View File

@ -1,4 +1,8 @@
<% provide(:title, 'New JamKazam Musicians in your Area') %>
<% provide(:title, 'New Musicians You Should Check Out') %>
Hi <%= @user.first_name %>,
<p>The following new musicians have joined JamKazam within the last week, and have Internet connections with low enough latency to you that you can have a good online session together. We'd suggest that you look through the new musicians listed below to see if any match your musical interests, and if so, click through to their profile page on the JamKazam website to send them a message or a request to connect as a JamKazam friend:
</p>
<% link_style = "background-color:#ED3618; margin:0px 8px 0px 8px; border: solid 1px #F27861; outline: solid 2px #ED3618; padding:3px 10px; font-family:Raleway, Arial, Helvetica, sans-serif; font-size:12px; font-weight:300; cursor:pointer; color:#FC9; text-decoration:none;" %>
<p>
@ -20,3 +24,8 @@
<% end %>
</table>
</p>
<p>There are currently <%= @new_nearby.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: <a href="http://www.jamkazam.com/client#/musicians">http://www.jamkazam.com/client#/musicians</a>.
</p>
<p>Best Regards,</p>
Team JamKazam

View File

@ -1,9 +1,17 @@
New JamKazam Musicians in your Area
Hi <%= @user.first_name %>,
The following new musicians have joined JamKazam within the last week, and have Internet connections with low enough latency to you that you can have a good online session together. We'd suggest that you look through the new musicians listed below to see if any match your musical interests, and if so, click through to their profile page on the JamKazam website to send them a message or a request to connect as a JamKazam friend:
<% @new_nearby.each do |user| %>
<%= user.name %> (http://<%= @host %>/client#/profile/<%= user.id %>)
<%= user.location %>
<% user.instruments.collect { |inst| inst.description }.join(', ') %>
<%= user.biography %>
<% end %>
There are currently <%= @new_nearby.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: http://www.jamkazam.com/client#/musicians.
Best Regards,
Team JamKazam

View File

@ -0,0 +1,38 @@
<% provide(:title, @title) %>
<p>Hello <%= @user.first_name %> --
</p>
<p>The following new sessions that that have been posted during the last 24 hours:
</p>
<ol>
<li>Need someone who plays an instrument that you play</li>
<li>Were posted by someone to whom you have either a good or medium latency connection</li>
</ol>
<p>Take a look through these new sessions below, and just click the RSVP button on the far right side of the row for any session in which you'd like to play. This will let the session organizer know you're interested, and you'll be notified if the session organizer accepts your request to play in that session!
</p>
<table style="margin-top:6px; width:98%; font-size:11px; color:#fff; background-color:#262626; border:solid 1px #4d4d4d;" cellspacing="0" cellpadding="0" border="0">
<!-- header -->
<tr>
<th align="left" width="20%">GENRE</th>
<th align="left" width="60%">DESCRIPTION</th>
<th width="20%" style="text-align:center">LATENCY</th>
</tr>
<!-- session row goes here -->
<% @sessions_and_latency.each do |sess| %>
<tr>
<td><%= sess.genre.description %></td>
<td><%= sess.description %></td>
<td style="text-align:center"><%= sess.latency_store %></td>
</tr>
<% end %>
</table>
<p>To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page at: <a href="http://www.jamkazam.com/client#/findSession">http://www.jamkazam.com/client#/findSession</a>.
</p>
<p>Best Regards,</p>
Team JamKazam

View File

@ -0,0 +1,21 @@
<% provide(:title, @title) %>
Hello <%= @user.first_name %> --
The following new sessions that that have been posted during the last 24 hours:
1. Need someone who plays an instrument that you play
2. Were posted by someone to whom you have either a good or medium latency connection
Take a look through these new sessions below, and just click the RSVP button on the far right side of the row for any session in which you'd like to play. This will let the session organizer know you're interested, and you'll be notified if the session organizer accepts your request to play in that session!
GENRE | DESCRIPTION | LATENCY
<% @sessions_and_latency.each do |sess| %>
<%= sess.genre.description %> | <%= sess.description %> | <%= sess.latency_store %>
<% end %>
To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page at: http://www.jamkazam.com/client#/findSession.
Best Regards,
Team JamKazam

View File

@ -1,27 +1,45 @@
<% provide(:title, 'Welcome to JamKazam!') %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<p> We're delighted that you have decided to try the JamKazam service,
and we hope that you will enjoy using JamKazam to play music with others.
Following are links to some resources that can help to get you up and running quickly.
</p>
<p>
Tutorial videos that show you how to use the key features of the product:</br>
Getting Started Video<br/>
We recommend watching this video before you jump into the service just to get oriented. It will really help you hit the ground running:
<a style="color: #588C98;" href="https://www.youtube.com/watch?v=VexH4834o9I">https://www.youtube.com/watch?v=VexH4834o9I</a>
</p>
<p>
Other Great Tutorial Videos<br />
There are several other very great videos that will help you understand how to find and connect with other musicians on the service, create your own sessions or find and join other musicians sessions, play in sessions, record and share your performances, and even live broadcast your sessions to family, friends, and fans. Check these helpful videos out here:
<a style="color: #588C98;" href="https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos">https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos</a>
</p>
<p>
Getting Started knowledge base articles:</br>
Knowledge Base Articles<br />
You can find Getting Started knowledge base articles on things like frequently asked questions (FAQ), minimum system requirements for your Windows or Mac computer, how to troubleshoot audio problems in sessions, and more here:
<a style="color: #588C98;" href="https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles">https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles</a>
</p>
<p>
Support Portal in case you run into trouble and need help:</br>
JamKazam Support Portal<br />
If you run into trouble and need help, please reach out to us. We will be glad to do everything we can to get you up and running. You can find our support portal here:
<a style="color: #588C98;" href="https://jamkazam.desk.com/">https://jamkazam.desk.com/</a>
</p>
<p>
We look forward to seeing - and hearing - you online soon!
JamKazam Community Forum<br />
And if you just want to chat, share tips and war stories, and hang out with fellow JamKazamers, you can visit our community forum here:
<a style="color: #588C98;" href="http://forums.jamkazam.com/">http://forums.jamkazam.com/</a>
</p>
&nbsp;&nbsp;- Team JamKazam
<p>
Please take a moment to like or follow us by clicking the icons below, and we look forward to seeing and hearing you online soon!
</p>
&nbsp;&nbsp;-- Team JamKazam

View File

@ -1,16 +1,27 @@
We're delighted that you have decided to try the JamKazam service,
and we hope that you will enjoy using JamKazam to play music with others.
Following are links to some resources that can help to get you up and running quickly.
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
Tutorial videos that show you how to use the key features of the product:
We're delighted that you have decided to try the JamKazam service, and we hope that you will enjoy using JamKazam to play music with others. Following are links to some resources that can help to get you up and running quickly.
Getting Started Video
We recommend watching this video before you jump into the service just to get oriented. It will really help you hit the ground running:
https://www.youtube.com/watch?v=VexH4834o9I
Other Great Tutorial Videos
There are several other very great videos that will help you understand how to find and connect with other musicians on the service, create your own sessions or find and join other musicians sessions, play in sessions, record and share your performances, and even live broadcast your sessions to family, friends, and fans. Check these helpful videos out here:
https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos
Getting Started knowledge base articles:
Knowledge Base Articles
You can find Getting Started knowledge base articles on things like frequently asked questions (FAQ), minimum system requirements for your Windows or Mac computer, how to troubleshoot audio problems in sessions, and more here:
https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles
Support Portal in case you run into trouble and need help:
https://jamkazam.desk.com/
JamKazam Support Portal
If you run into trouble and need help, please reach out to us. We will be glad to do everything we can to get you up and running. You can find our support portal here:
https://jamkazam.desk.com
We look forward to seeing - and hearing - you online soon!
JamKazam Community Forum
And if you just want to chat, share tips and war stories, and hang out with fellow JamKazamers, you can visit our community forum here:
http://forums.jamkazam.com
- Team JamKazam
Please take a moment to like or follow us by clicking the icons below, and we look forward to seeing and hearing you online soon!
-- Team JamKazam

View File

@ -49,8 +49,8 @@
<table align="center" width="650" cellpadding="10" bgcolor="#156572" cellspacing="0">
<tr>
<td align="center" valign="top">
<% [:twitter, :facebook, :google].each do |src| %>
<%= link_to(image_tag("http://www.jamkazam.com/assets/content/icon_#{src}.png", :style => "vertical-align:top"), "http://www.jamkazam.com/endorse/@USERID/#{src}") %>&nbsp;
<% [:twitter, :facebook, :google].each do |site| %>
<%= link_to(image_tag("http://www.jamkazam.com/assets/content/icon_#{site}.png", :style => "vertical-align:top"), "http://www.jamkazam.com/endorse/@USERID/#{site}?src=email") %>&nbsp;
<% end %>
</td>
</tr>

View File

@ -153,11 +153,11 @@ SQL
# NOTE this is only used for testing purposes;
# actual deletes will be processed in the websocket context which cleans up dependencies
def expire_stale_connections()
self.stale_connection_client_ids().each { |client| self.delete_connection(client[:client_id]) }
self.stale_connection_client_ids.each { |client| self.delete_connection(client[:client_id]) }
end
# expiring connections in stale state, which deletes them
def stale_connection_client_ids()
def stale_connection_client_ids
clients = []
ConnectionManager.active_record_transaction do |connection_manager|
conn = connection_manager.pg_conn
@ -347,7 +347,7 @@ SQL
connection = Connection.find_by_client_id_and_user_id!(client_id, user.id)
connection.join_the_session(music_session, as_musician, tracks)
connection.join_the_session(music_session, as_musician, tracks, user)
# connection.music_session_id = music_session.id
# connection.as_musician = as_musician
# connection.joining_session = true

View File

@ -41,7 +41,7 @@ module ValidationMessages
INVALID_FPFILE = "is not valid"
#connection
USER_OR_LATENCY_TESTER_PRESENT = "user or latency_tester must be present"
SELECT_AT_LEAST_ONE = "Please select at least one track" # DO NOT CHANGE THIS TEXT MESSAGE UNLESS YOU CHANGE createSession.js.erb, which is looking for it
FAN_CAN_NOT_JOIN_AS_MUSICIAN = "A fan can not join a music session as a musician"
MUSIC_SESSION_MUST_BE_SPECIFIED = "A music session must be specified"

View File

@ -22,13 +22,11 @@ module JamRuby
Jampb::ClientMessage.parse(payload)
end
# create a login message using user/pass
def login_with_user_pass(username, password, options = {})
# create a login message using client_id (used by latency_tester)
def login_with_client_id(client_id)
login = Jampb::Login.new(
:username => username,
:password => password,
:client_id => options[:client_id],
:client_type => options[:client_type]
:client_id => client_id,
:client_type => Connection::TYPE_LATENCY_TESTER
)
Jampb::ClientMessage.new(
@ -38,6 +36,22 @@ module JamRuby
)
end
# create a login message using user/pass
def login_with_user_pass(username, password, options = {})
login = Jampb::Login.new(
:username => username,
:password => password,
:client_id => options[:client_id],
:client_type => options[:client_type]
)
Jampb::ClientMessage.new(
:type => ClientMessage::Type::LOGIN,
:route_to => SERVER_TARGET,
:login => login
)
end
# create a login message using token (a cookie or similar)
def login_with_token(token, options = {})
login = Jampb::Login.new(

View File

@ -27,7 +27,10 @@ module JamRuby
start = params[:start].presence
start = start.to_i || 0
query = ChatMessage.offset(start).limit(limit)
music_session_id = params[:music_session]
query = ChatMessage.where('music_session_id = ?', music_session_id)
.offset(start).limit(limit)
if query.length == 0
[query, nil]

View File

@ -6,6 +6,7 @@ module JamRuby
# client_types
TYPE_CLIENT = 'client'
TYPE_BROWSER = 'browser'
TYPE_LATENCY_TESTER = 'latency_tester'
attr_accessor :joining_session
@ -13,11 +14,14 @@ module JamRuby
belongs_to :user, :class_name => "JamRuby::User"
belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", foreign_key: :music_session_id
has_one :latency_tester, class_name: 'JamRuby::LatencyTester', foreign_key: :client_id, primary_key: :client_id
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
validates :as_musician, :inclusion => {:in => [true, false]}
validates :client_type, :inclusion => {:in => [TYPE_CLIENT, TYPE_BROWSER]}
validates :client_type, :inclusion => {:in => [TYPE_CLIENT, TYPE_BROWSER, TYPE_LATENCY_TESTER]}
validate :can_join_music_session, :if => :joining_session?
validate :user_or_latency_tester_present
after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
after_create :did_create
after_save :report_add_participant
@ -134,7 +138,7 @@ module JamRuby
end
def did_create
self.user.update_lat_lng(self.ip_address) if self.user && self.ip_address
# self.user.update_lat_lng(self.ip_address) if self.user && self.ip_address
end
def report_add_participant
@ -148,13 +152,18 @@ module JamRuby
true
end
def join_the_session(music_session, as_musician, tracks)
def join_the_session(music_session, as_musician, tracks, user)
self.music_session_id = music_session.id
self.as_musician = as_musician
self.joining_session = true
self.joined_session_at = Time.now
associate_tracks(tracks) unless tracks.nil?
self.save
# if user joins the session as a musician, update their addr and location
if as_musician
user.update_addr_loc(self, 'j')
end
end
def associate_tracks(tracks)
@ -185,5 +194,12 @@ module JamRuby
end
end
def user_or_latency_tester_present
if user.nil? && client_type != TYPE_LATENCY_TESTER
puts client_type
errors.add(:connection, ValidationMessages::USER_OR_LATENCY_TESTER_PRESENT)
end
end
end
end

View File

@ -17,6 +17,9 @@ module JamRuby
# this implies a coding error
MISSING_CLIENT_STATE = 'MISSING_CLIENT_STATE'
# the underlying database connection is gone when the heartbeat comes in
MISSING_CONNECTION = 'MISSING_CONNECTION'
# websocket gateway did not recognize message. indicates out-of-date websocket-gateway
UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE'
@ -26,9 +29,15 @@ module JamRuby
# websocket gateway got a client with the same client_id as an already-connected client
DUPLICATE_CLIENT = 'DUPLICATE_CLIENT'
# info about how the test went
NETWORK_TEST_RESULT = 'NETWORK_TEST_RESULT'
# step 2 of the FTUE... could the user select their gear?
GEAR_SELECTION = 'GEAR_SELECTION'
DIAGNOSTIC_TYPES = [NO_HEARTBEAT_ACK, WEBSOCKET_CLOSED_REMOTELY, EXPIRED_STALE_CONNECTION,
MISSING_CLIENT_STATE, UNKNOWN_MESSAGE_TYPE, MISSING_ROUTE_TO,
DUPLICATE_CLIENT, WEBSOCKET_CLOSED_LOCALLY]
DUPLICATE_CLIENT, WEBSOCKET_CLOSED_LOCALLY, NETWORK_TEST_RESULT, GEAR_SELECTION]
# creator types #
CLIENT = 'client'

View File

@ -13,7 +13,7 @@ module JamRuby
VAR_LAST_NAME = '@LASTNAME'
DEFAULT_SENDER = "noreply@jamkazam.com"
BATCH_SIZE = 5
BATCH_SIZE = 500
BODY_TEMPLATE =<<FOO
Hello #{VAR_FIRST_NAME},
@ -78,13 +78,26 @@ FOO
self.test_emails.present? && (self.tested? || self.pending?)
end
def deliver_batch
self.perform_event('do_batch_run!')
User.email_opt_in.find_in_batches(batch_size: BATCH_SIZE) do |users|
self.email_batch_sets << EmailBatchSet.deliver_set(self.id, users.map(&:id))
def deliver_batch_sets!
User.email_opt_in.find_each do |user|
bset = EmailBatchSet.sent_email(self, user.id)
if 'test' == Rails.env
BatchMailer.send_batch_email(self.id, bset.user_id).deliver!
else
BatchMailer.send_batch_email(self.id, bset.user_id).deliver
end
end
end
def deliver_batch
self.perform_event('do_batch_run!')
self.deliver_batch_sets!
end
def deliver_batch_async
BatchEmailJob.enqueue(self.id)
end
def test_count
self.test_emails.split(',').count
end
@ -195,5 +208,13 @@ FOO
})
end
def batch_substitutions(users=[])
{}
end
def body_for_users(users=[])
self.body
end
end
end

View File

@ -0,0 +1,118 @@
module JamRuby
class EmailBatchNewMusician < EmailBatchPeriodic
BATCH_SIZE = 500
SINCE_DAYS = 14
VAR_MUSICIAN_COUNT = "@MUSICIAN_COUNT"
VAR_MUSICIAN_TABLE = "@MUSICIAN_TABLE"
TMP_NEW = 'tmp_new_musicians'
TMP_CAND = 'tmp_receiver_candidates'
TMP_PAIRS = 'tmp_receivers_new_musicians'
def self.subject
"New musicians with good Internet connections to you have joined JamKazam!"
end
# inserts eligible sessions to temp table
def _fetch_new_musicians
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{TMP_NEW}")
sql =<<SQL
SELECT
users.id AS new_user_id, users.last_jam_locidispid AS last_jam_locidispid
INTO TEMP TABLE #{TMP_NEW}
FROM users
WHERE
musician = 't' AND
last_jam_locidispid IS NOT NULL AND
users.created_at > '#{time_since_last_batch(SINCE_DAYS)}'
SQL
ActiveRecord::Base.connection.execute(sql)
end
def _fetch_receiver_candidates
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{TMP_CAND}")
sql =<<SQL
SELECT
users.id AS receiver_candidate_id, users.last_jam_locidispid AS last_jam_locidispid
INTO TEMP TABLE #{TMP_CAND}
FROM users
FULL OUTER JOIN #{TMP_NEW} ON users.id = #{TMP_NEW}.new_user_id
WHERE
users.musician = 't' AND
users.subscribe_email = 't' AND
users.last_jam_locidispid IS NOT NULL AND
(#{TMP_NEW}.new_user_id IS NULL OR users.id IS NULL)
SQL
ActiveRecord::Base.connection.execute(sql)
end
def _fetch_eligible_receivers
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{TMP_PAIRS}")
# load eligible recipients into tmp table
sql =<<SQL
SELECT
#{TMP_NEW}.new_user_id,
#{TMP_CAND}.receiver_candidate_id AS receiver_id,
scores.score AS latency
INTO TEMP TABLE #{TMP_PAIRS}
FROM scores
INNER JOIN #{TMP_CAND} ON #{TMP_CAND}.last_jam_locidispid = scores.alocidispid
INNER JOIN #{TMP_NEW} ON #{TMP_NEW}.last_jam_locidispid = scores.blocidispid
WHERE
scores.score < #{Score::MAX_YELLOW_LATENCY}
SQL
ActiveRecord::Base.connection.execute(sql)
end
def fetch_recipients
objs = []
# load new musicians into tmp table
self._fetch_new_musicians
# load receiver candidates into tmp table
self._fetch_receiver_candidates
# load email receivers into tmp table
self._fetch_eligible_receivers
sql = "SELECT DISTINCT receiver_id FROM #{TMP_PAIRS} GROUP BY receiver_id"
ActiveRecord::Base.connection.execute(sql).each do |result|
receiver = User.find_by_id(result['receiver_id'])
sql = "SELECT new_user_id, latency FROM #{TMP_PAIRS} WHERE receiver_id = '#{receiver.id}'"
new_musicians = ActiveRecord::Base.connection.execute(sql).collect do |rr|
new_user = User.where(['id = ?',rr['new_user_id']])
.limit(1)
.includes([:instruments])
.first
new_user.latency_store = result['latency']
new_user
end
block_given? ? yield(receiver, new_musicians) : objs << [receiver, new_musicians]
end
objs
end
def deliver_batch_sets!
self.opt_in_count = 0
self.fetch_recipients do |user, new_musicians|
self.opt_in_count += 1
bset = EmailBatchSet.new_musician_set(self, user, new_musicians)
UserMailer.new_musicians(uu, new_musicians).deliver
end
self.sent_count = self.opt_in_count
self.save
self.did_batch_run!
end
def self.send_new_musician_batch
oo = self.create
oo..deliver_batch
oo
end
end
end

View File

@ -0,0 +1,46 @@
module JamRuby
class EmailBatchPeriodic < EmailBatch
self.abstract_class = true
def time_since_last_batch_query
self.class
.where(['created_at < ?', self.created_at])
.order('created_at DESC')
.limit(1)
end
def time_since_last_batch(default_days=2)
if previous = self.time_since_last_batch_query.first
return previous.created_at
end
Time.now - default_days.days
end
def fetch_recipients(since=nil)
yield([]) if block_given?
end
def self.subject(subtype=nil)
''
end
def self.body
''
end
def self.new(*args)
oo = super
oo.body = ''
oo.subject = self.subject
oo
end
def deliver_batch_sets!
end
def clear_batch_sets!
self.email_batch_sets.map(&:destroy)
end
end
end

View File

@ -0,0 +1,227 @@
# -*- coding: utf-8 -*-
module JamRuby
class EmailBatchProgression < EmailBatchPeriodic
BATCH_SIZE = 500
SINCE_WEEKS = 2
SUBTYPES = [:client_notdl, # Registered Musician Has Not Downloaded Client
:client_dl_notrun, # Registered Musician Has Downloaded Client But Not Yet Run It
:client_run_notgear, # Registered Musician Has Run Client But Not Successfully Qualified Audio Gear
:gear_notsess, # Registered Musician Has Successfully Qualified Audio Gear But Has Not Participated in a Real Session
:sess_notgood, # Registered Musician Has Participated In a "Real" Session But Has Not Had a "Good" Session
:sess_notrecord, # Registered Musician Has Participated In a "Real" Session But Has Not Made a Recording
:reg_notinvite, # Registered Musician Has Not Invited Friends to Join JamKazam
:reg_notconnect, # Registered Musician Has Not Connected with any Friends on JamKazam
:reg_notlike, # Registered Musician Has Not Liked Jamkazam
]
SUBTYPE_METADATA = {
:client_notdl => {
:subject => "Get the free JamKazam app now and start playing with others!",
:title => "Download the Free JamKazam App"
},
:client_dl_notrun => {
:subject => "Having trouble running the JamKazam application?",
:title => "Running the JamKazam App"
},
:client_run_notgear => {
:subject => "Having trouble setting up your audio gear for JamKazam?",
:title => "Setting Up and Qualifying Your Audio Gear on JamKazam"
},
:gear_notsess => {
:subject => "Having trouble getting into a session with other musicians?",
:title => "Tips on Getting into Sessions with Other Musicians"
},
:sess_notgood => {
:subject => "Have you played in a “good” session on JamKazam yet?",
:title => "Having a Good Session on JamKazam"
},
:reg_notinvite => {
:subject => "Invite your friends to JamKazam, best way to play online!",
:title => "Invite Your Friends to Come and Play with You"
},
:reg_notconnect => {
:subject => "Make friends on JamKazam and play more music!",
:title => "Connecting with Friends on JamKazam"
},
:reg_notlike => {
:subject => "Please give us a LIKE!",
:title => "Like/Follow JamKazam"
},
:sess_notrecord => {
:subject => "Want to make recordings during your JamKazam sessions?",
:title => "Making & Sharing Recordings on JamKazam"
},
}
def self.subject(subtype=nil)
SUBTYPE_METADATA[subtype][:subject] if subtype
end
def self.title(subtype=nil)
SUBTYPE_METADATA[subtype][:title] if subtype
end
def self.subtype_trigger_interval(subtype)
[:reg_notlike, :sess_notrecord].include?(subtype) ? [7,14,21] : [2,5,14]
end
def self.days_past_for_trigger_index(subtype, idx)
self.subtype_trigger_interval(subtype)[idx]
end
def days_diff(tidx)
self.class.days_past_for_trigger_index(self.sub_type,tidx) - self.class.days_past_for_trigger_index(self.sub_type, tidx - 1)
end
def days_past_for_trigger_index(idx)
self.class.subtype_trigger_interval(self.sub_type)[idx]
end
def make_set(uu, trigger_idx)
EmailBatchSet.progress_set(self, uu, trigger_idx)
end
def trigger_date_constraint(trigger_idx, tbl='tt')
intervals = self.class.subtype_trigger_interval(self.sub_type)
date_constraint = (Time.now - intervals[trigger_idx].days + 1.hour).strftime('%Y-%m-%d %H:%M:%S')
case self.sub_type.to_sym
when :client_notdl, :reg_notconnect, :reg_notinvite, :reg_notlike
return "#{tbl}.created_at < '#{date_constraint}'"
when :client_dl_notrun
return "#{tbl}.first_downloaded_client_at < '#{date_constraint}'"
when :client_run_notgear
return "#{tbl}.first_ran_client_at < '#{date_constraint}'"
when :gear_notsess
return "#{tbl}.first_certified_gear_at < '#{date_constraint}'"
when :sess_notgood
return "#{tbl}.first_real_music_session_at < '#{date_constraint}'"
when :sess_notrecord
return "#{tbl}.first_real_music_session_at < '#{date_constraint}'"
end
end
def progress_column_constraint(tbl='tt')
case self.sub_type.to_sym
when :client_notdl
return "#{tbl}.first_downloaded_client_at IS NULL"
when :client_dl_notrun
return "#{tbl}.first_downloaded_client_at IS NOT NULL AND #{tbl}.first_ran_client_at IS NULL"
when :client_run_notgear
return "#{tbl}.first_ran_client_at IS NOT NULL AND #{tbl}.first_certified_gear_at IS NULL"
when :gear_notsess
return "#{tbl}.first_certified_gear_at IS NOT NULL AND #{tbl}.first_real_music_session_at IS NULL"
when :sess_notgood
return "#{tbl}.first_real_music_session_at IS NOT NULL AND #{tbl}.first_good_music_session_at IS NULL"
when :sess_notrecord
return "#{tbl}.first_real_music_session_at IS NOT NULL AND #{tbl}.first_recording_at IS NULL"
when :reg_notinvite
return "#{tbl}.first_invited_at IS NULL"
when :reg_notconnect
return "#{tbl}.first_friended_at IS NULL"
when :reg_notlike
return "#{tbl}.first_social_promoted_at IS NULL"
end
''
end
def fetch_recipients(trigger_idx=0, per_page=BATCH_SIZE)
fetched = []
offset = 0
if 0==trigger_idx
prev_date_sql = 'tt.prev_trigger_date IS NULL'
else
prev_date_sql = "tt.prev_trigger_date + interval '#{self.days_diff(trigger_idx)} days' <= '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}'"
end
countsql =<<SQL
SELECT COUNT(*)
FROM (SELECT "users".*,
(SELECT COALESCE(MAX(ebs.trigger_index),-1)
FROM email_batch_sets ebs
WHERE
ebs.sub_type = '#{self.sub_type}' AND
ebs.user_id = users.id
) AS tidx,
(SELECT created_at
FROM email_batch_sets ebs
WHERE
ebs.sub_type = '#{self.sub_type}' AND
ebs.user_id = users.id AND trigger_index = #{trigger_idx - 1}
) AS prev_trigger_date
FROM users) tt
WHERE
tt."subscribe_email" = 't' AND
tt."musician" = 't' AND
(#{progress_column_constraint}) AND
(#{self.trigger_date_constraint(trigger_idx)}) AND
tt.tidx = #{trigger_idx - 1} AND
#{prev_date_sql}
SQL
num_users = User.count_by_sql(countsql)
loops = (num_users / per_page) + (num_users % per_page) - 1
0.upto(loops) do |nn|
offset = nn * per_page
sql =<<SQL
SELECT tt.*
FROM (SELECT "users".*,
(SELECT COALESCE(MAX(ebs.trigger_index),-1)
FROM email_batch_sets ebs
WHERE
ebs.sub_type = '#{self.sub_type}' AND
ebs.user_id = users.id
) AS tidx,
(SELECT created_at
FROM email_batch_sets ebs
WHERE
ebs.sub_type = '#{self.sub_type}' AND
ebs.user_id = users.id AND trigger_index = #{trigger_idx - 1}
) AS prev_trigger_date
FROM users) tt
WHERE
tt."subscribe_email" = 't' AND
tt."musician" = 't' AND
(#{progress_column_constraint}) AND
(#{self.trigger_date_constraint(trigger_idx)}) AND
tt.tidx = #{trigger_idx - 1} AND
#{prev_date_sql}
ORDER BY tt.id
ASC LIMIT #{per_page}
OFFSET #{offset}
SQL
users = User.find_by_sql(sql)
block_given? ? yield(users) : fetched.concat(users)
end
fetched
end
def deliver_batch_sets!
self.opt_in_count = 0
sent = 0
3.times do |trigger_idx|
self.fetch_recipients(trigger_idx) do |users|
users.each do |uu|
self.email_batch_sets << (bset = self.make_set(uu, trigger_idx))
ProgressMailer.send_reminder(bset).deliver
sent += 1
end
end
end
self.sent_count = sent
self.save
self.did_batch_run!
end
def self.send_progress_batch
self::SUBTYPES.collect do |stype|
ebatch = self.create
ebatch.update_attribute(:sub_type, stype.to_s)
ebatch
end.each do |ebatch|
ebatch.deliver_batch
end
end
end
end

View File

@ -0,0 +1,122 @@
module JamRuby
class EmailBatchScheduledSessions < EmailBatchPeriodic
BATCH_SIZE = 500
SINCE_DAYS = 2
MIN_HOURS_START = 2
TMP_SNAP = 'tmp_scheduled_session_snapshot'
TMP_USER = 'tmp_scheduled_session_user'
def self.subject
"New sessions have been scheduled that may be a good match for you!"
end
# inserts eligible sessions to temp table
def _collect_eligible_sessions
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{TMP_SNAP}")
sql =<<SQL
SELECT
rs.instrument_id AS instrument_id,
msess.id AS session_id,
msess.user_id AS creator_id,
users.last_jam_locidispid AS creator_score_idx
INTO TEMP TABLE #{TMP_SNAP}
FROM music_sessions msess
INNER JOIN rsvp_slots AS rs ON rs.music_session_id = msess.id
LEFT JOIN rsvp_requests_rsvp_slots AS rrrs ON rrrs.rsvp_slot_id = rs.id
INNER JOIN users ON users.id = msess.user_id
WHERE
musician_access = 't' AND
approval_required = 'f' AND
msess.created_at > '#{time_since_last_batch(SINCE_DAYS)}' AND
scheduled_start >= '#{Time.now() + MIN_HOURS_START.hours}' AND
(rrrs.rsvp_slot_id IS NULL OR rrrs.chosen != 't')
SQL
ActiveRecord::Base.connection.execute(sql)
end
def _collect_eligible_recipients
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{TMP_USER}")
# load eligible recipients into tmp table
sql =<<SQL
SELECT
users.id AS user_id,
users.last_jam_locidispid AS user_score_idx,
tmp.session_id AS session_id,
tmp.creator_id AS creator_id,
tmp.creator_score_idx AS creator_score_idx
INTO TEMP TABLE #{TMP_USER}
FROM users
INNER JOIN musicians_instruments AS mi ON mi.user_id = users.id
LEFT JOIN #{TMP_SNAP} AS tmp ON tmp.instrument_id = mi.instrument_id
WHERE
users.musician = 't' AND
users.subscribe_email = 't' AND
tmp.session_id IS NOT NULL
GROUP BY users.id, tmp.session_id, tmp.creator_id, tmp.creator_score_idx
SQL
ActiveRecord::Base.connection.execute(sql)
end
# select recipients whose score is below minimum threshold
def _select_scored_recipients
sql =<<SQL
SELECT DISTINCT user_id, scores.score AS latency
FROM #{TMP_USER}
INNER JOIN scores ON scores.alocidispid = #{TMP_USER}.creator_score_idx AND scores.blocidispid = #{TMP_USER}.user_score_idx
WHERE
scores.score < #{Score::MAX_YELLOW_LATENCY}
SQL
ActiveRecord::Base.connection.execute(sql)
end
def fetch_recipients
objs = []
# load eligible sessions into tmp table
self._collect_eligible_sessions
# load eligible mail recipients into tmp table
self._collect_eligible_recipients
# now just get the sessions/latency for each distinct mail recipient
_select_scored_recipients.each do |result|
user = User.find_by_id(result['user_id'])
sql = "SELECT session_id FROM #{TMP_USER} WHERE user_id = '#{user.id}'"
sessions = ActiveRecord::Base.connection.execute(sql).collect do |rr|
msess = MusicSession.where(['id = ?',rr['session_id']])
.limit(1)
.includes([:genre, :creator])
.first
msess.latency_store = result['latency']
msess
end
block_given? ? yield(user, sessions) : objs << [user, sessions]
end
objs
end
def deliver_batch_sets!
self.opt_in_count = 0
sent = 0
self.fetch_recipients do |receiver, sessions_and_latency|
self.opt_in_count += 1
sent += 1
bset = EmailBatchSet.scheduled_session_set(self, receiver, sessions_and_latency)
UserMailer.scheduled_session_daily(receiver, sessions_and_latency).deliver
end
self.sent_count = sent
self.save
self.did_batch_run!
end
def self.send_daily_session_batch
oo = self.create
oo..deliver_batch
oo
end
end
end

View File

@ -3,21 +3,85 @@ module JamRuby
self.table_name = "email_batch_sets"
belongs_to :email_batch, :class_name => 'JamRuby::EmailBatch'
belongs_to :user, :class_name => 'JamRuby::User'
def self.deliver_set(batch_id, user_ids)
# def self.load_set(batch, user_ids)
# bset = self.new
# bset.email_batch_id = batch.id
# bset.user_ids = user_ids.join(',')
# bset.started_at = Time.now
# bset.batch_count = user_ids.size
# bset.save!
# bset
# end
def self.sent_email(batch, user_id)
bset = self.new
bset.email_batch_id = batch_id
bset.user_ids = user_ids.join(',')
bset.email_batch_id = batch.id
bset.user_id = user_id
bset.started_at = Time.now
bset.batch_count = user_ids.size
bset.batch_count = 1
bset.save!
if 'test' == Rails.env
BatchMailer.send_batch_email(bset.email_batch_id, user_ids).deliver!
else
BatchMailer.send_batch_email(bset.email_batch_id, user_ids).deliver
end
bset
end
def self.progress_set(batch, user, trigger_idx)
bset = self.new
bset.email_batch = batch
bset.user = user
bset.started_at = Time.now
bset.batch_count = 1
bset.trigger_index = trigger_idx
bset.sub_type = batch.sub_type
bset.save!
bset
end
def self.scheduled_session_set(batch, receiver, sessions)
bset = self.new
bset.email_batch = batch
bset.user = receiver
bset.user_ids = sessions.map(&:id).join(',')
bset.started_at = Time.now
bset.batch_count = 1
bset.save!
bset
end
def self.new_musician_set(batch, receiver, new_musicians)
bset = self.new
bset.email_batch = batch
bset.user = receiver
bset.user_ids = new_musicians.map(&:id).join(',')
bset.started_at = Time.now
bset.batch_count = 1
bset.save!
bset
end
def subject
unless sub_type.blank?
return EmailBatchProgression.subject(self.sub_type.to_sym)
end
''
end
def title
unless sub_type.blank?
return EmailBatchProgression.title(self.sub_type.to_sym)
end
''
end
def previous_trigger_date
return nil if 0 == self.trigger_index.to_i || self.user_id.nil?
self.class
.where(['email_batch__id = ? AND user_id = ? AND sub_type = ? AND trigger_index = ?',
self.email_batch_id, self.user_id, self.sub_type, self.trigger_index - 1])
.pluck(:created_at)
.limit(1)
.first
end
end
end

View File

@ -1,80 +0,0 @@
module JamRuby
class EmailError < ActiveRecord::Base
self.table_name = "email_errors"
belongs_to :user, :class_name => 'JamRuby::User'
default_scope :order => 'email_date DESC'
ERR_BOUNCE = :bounce
ERR_INVALID = :invalid
SENDGRID_UNAME = 'jamkazam'
SENDGRID_PASSWD = 'jamjamblueberryjam'
def self.sendgrid_url(resource, action='get', params='')
start_date, end_date = self.date_range
"https://api.sendgrid.com/api/#{resource}.#{action}.json?api_user=#{EmailError::SENDGRID_UNAME}&api_key=#{EmailError::SENDGRID_PASSWD}&date=1&start_date=#{start_date.strftime('%Y-%m-%d')}&end_date=#{end_date.strftime('%Y-%m-%d')}&#{params}"
end
def self.date_range
tt = Time.now
if eerr = self.first
return [eerr.email_date, tt]
end
[tt - 1.year, tt]
end
def self.did_capture?(email_addy)
self.where(:email_address => email_addy).limit(1).first.present?
end
def self.bounce_errors
uu = self.sendgrid_url('bounces')
response = RestClient.get(uu)
if 200 == response.code
return JSON.parse(response.body).collect do |jj|
next if self.did_capture?(jj['email'])
ee = EmailError.new
ee.error_type = 'bounces'
ee.email_address = jj['email']
ee.user_id = User.where(:email => ee.email_address).pluck(:id).first
ee.status = jj['status']
ee.email_date = jj['created']
ee.reason = jj['reason']
ee.save!
# RestClient.delete(self.sendgrid_url('bounces', 'delete', "email=#{ee.email_address}"))
ee
end
end
end
def self.invalid_errors
uu = self.sendgrid_url('invalidemails')
response = RestClient.get(uu)
if 200 == response.code
return JSON.parse(response.body).collect do |jj|
next if self.did_capture?(jj['email'])
ee = EmailError.new
ee.error_type = 'invalidemails'
ee.email_address = jj['email']
ee.user_id = User.where(:email => ee.email_address).pluck(:id).first
ee.email_date = jj['created']
ee.reason = jj['reason']
ee.save!
uu =
# RestClient.delete(self.sendgrid_url('invalidemails', 'delete', "email=#{ee.email_address}"))
ee
end
end
end
def self.capture_errors
EmailError.bounce_errors
EmailError.invalid_errors
end
end
end

View File

@ -3,15 +3,15 @@ module JamRuby
self.table_name = "connections"
def self.get_work(mylocidispid)
list = self.get_work_list(mylocidispid)
def self.get_work(mylocidispid, myaddr)
list = self.get_work_list(mylocidispid, myaddr)
return nil if list.nil?
return nil if list.length == 0
return list[0]
end
def self.get_work_list(mylocidispid)
r = GetWork.select(:client_id).find_by_sql("select get_work(#{mylocidispid}) as client_id")
def self.get_work_list(mylocidispid, myaddr)
r = GetWork.select(:client_id).find_by_sql("select get_work(#{mylocidispid}, #{myaddr}) as client_id")
#puts("r = #{r}")
a = r.map {|i| i.client_id}
#puts("a = #{a}")

View File

@ -0,0 +1,79 @@
module JamRuby
class LatencyTester < ActiveRecord::Base
belongs_to :connection, class_name: 'JamRuby::Connection', foreign_key: :client_id, primary_key: :client_id
def heartbeat_interval_client
nil
end
def connection_expire_time_client
nil
end
def self.select_latency_tester
LatencyTester.joins(:connection).first!
end
# we need to find that latency_tester with the specified connection (and reconnect it)
# or bootstrap a new latency_tester
def self.connect(options)
client_id = options[:client_id]
ip_address = options[:ip_address]
connection_stale_time = options[:connection_stale_time]
connection_expire_time = options[:connection_expire_time]
# first try to find a LatencyTester with that client_id
latency_tester = LatencyTester.find_by_client_id(client_id)
if latency_tester
if latency_tester.connection
connection = latency_tester.connection
else
connection = Connection.new
connection.client_id = client_id
latency_tester.connection = connection
end
else
latency_tester = LatencyTester.new
latency_tester.client_id = client_id
unless latency_tester.save
return latency_tester
end
connection = Connection.new
connection.latency_tester = latency_tester
connection.client_id = client_id
end
if ip_address and !ip_address.eql?(connection.ip_address)
# locidispid stuff
addr = JamIsp.ip_to_num(ip_address)
isp = JamIsp.lookup(addr)
if isp.nil? then ispid = 0 else ispid = isp.coid end
block = GeoIpBlocks.lookup(addr)
if block.nil? then locid = 0 else locid = block.locid end
location = GeoIpLocations.lookup(locid)
if location.nil?
# todo what's a better default location?
locidispid = 0
else
locidispid = locid*1000000+ispid
end
connection.ip_address = ip_address
connection.addr = addr
connection.locidispid = locidispid
end
connection.client_type = 'latency_tester'
connection.aasm_state = Connection::CONNECT_STATE.to_s
connection.stale_time = connection_stale_time
connection.expire_time = connection_expire_time
connection.as_musician = false
unless connection.save
return connection
end
return latency_tester
end
end
end

View File

@ -88,35 +88,42 @@ module JamRuby
end
end
end
User.find_each { |usr| usr.update_lat_lng }
# User.find_each { |usr| usr.update_lat_lng }
Band.find_each { |bnd| bnd.update_lat_lng }
end
def self.where_latlng(relation, params, current_user=nil)
if 0 < (distance = params[:distance].to_i)
latlng = []
if location_city = params[:city]
if geo = self.where(:city => params[:city]).limit(1).first
# this is only valid to call when relation is about bands
distance = params[:distance].to_i
if distance > 0
latlng = nil
location_city = params[:city]
location_state = params[:state]
location_country = params[:country]
remote_ip = params[:remote_ip]
if location_city and location_state and location_country
geo = self.where(city: location_city, region: location_state, country: location_country).limit(1).first
if geo and geo.lat and geo.lng and (geo.lat != 0 or geo.lng != 0)
# it isn't reasonable for both to be 0...
latlng = [geo.lat, geo.lng]
end
elsif current_user
if current_user.lat.nil? || current_user.lng.nil?
if params[:remote_ip] && (geo = self.ip_lookup(params[:remote_ip]))
geo.lat = nil if geo.lat = 0
geo.lng = nil if geo.lng = 0
latlng = [geo.lat, geo.lng] if geo.lat && geo.lng
end
else
latlng = [current_user.lat, current_user.lng]
elsif current_user and current_user.locidispid and current_user.locidispid != 0
location = GeoIpLocations.lookup(current_user.locidispid/1000000)
if location and location.latitude and location.longitude and (location.latitude != 0 or location.longitude != 0)
# it isn't reasonable for both to be 0...
latlng = [location.latitude, location.longitude]
end
elsif remote_ip
geo = self.ip_lookup(remote_ip)
if geo and geo.lat and geo.lng and (geo.lat != 0 or geo.lng != 0)
# it isn't reasonable for both to be 0...
latlng = [geo.lat, geo.lng]
end
elsif params[:remote_ip] && (geo = self.ip_lookup(params[:remote_ip]))
geo.lat = nil if geo.lat = 0
geo.lng = nil if geo.lng = 0
latlng = [geo.lat, geo.lng] if geo.lat && geo.lng
end
if latlng.present?
relation = relation.where(['lat IS NOT NULL AND lng IS NOT NULL'])
.within(distance, :origin => latlng)
if latlng
relation = relation.where(['lat IS NOT NULL AND lng IS NOT NULL']).within(distance, origin: latlng)
end
end
relation

View File

@ -10,6 +10,9 @@ module JamRuby
attr_accessor :legal_terms, :recurring_mode, :language_description, :scheduled_start_time, :access_description
# used for temporary data store of latency between creator and some other user
attr_accessor :latency_store
self.table_name = "music_sessions"
self.primary_key = 'id'
@ -294,7 +297,17 @@ module JamRuby
end
def language_description
ISO_639.find_by_code(self.language).english_name
if self.language.blank?
self.language = "en"
end
iso639Details = ISO_639.find_by_code(self.language)
unless iso639Details.blank?
return iso639Details.english_name
else
return "English"
end
end
def scheduled_start_time

View File

@ -712,7 +712,7 @@ module JamRuby
@@mq_router.publish_to_user(target_user.id, msg)
begin
UserMailer.send_scheduled_session_rsvp_cancelled(target_user.email, notification_msg, music_session).deliver
UserMailer.scheduled_session_rsvp_cancelled(target_user.email, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send send_scheduled_session_rsvp_cancelled email to offline user #{target_user.email} #{e}")
end

View File

@ -7,47 +7,50 @@ module JamRuby
self.where(countrycode: country).order('regionname asc').all
end
def self.import_from_xx_region(countrycode, file)
def self.import_from_region_codes(file)
# File xx_region.csv
# File region_codes.csv
# Format:
# region,regionname
# countrycode,region,regionname
# what this does is not replace the contents of the table, but rather update the specifies rows with the names.
# any rows not specified are left alone. the parameter countrycode denote the country of the region (when uppercased)
raise "countrycode (#{MaxMindIsp.quote_value(countrycode)}) is missing or invalid (it must be two characters)" unless countrycode and countrycode.length == 2
countrycode = countrycode.upcase
# what this does is replace the contents of the table with the new data.
self.transaction do
self.connection.execute "update #{self.table_name} set regionname = region where countrycode = #{MaxMindIsp.quote_value(countrycode)}"
self.connection.execute "delete from #{self.table_name}"
File.open(file, 'r:ISO-8859-1') do |io|
saved_level = ActiveRecord::Base.logger ? ActiveRecord::Base.logger.level : 0
saved_level = ActiveRecord::Base.logger ? ActiveRecord::Base.logger.level : -1
count = 0
ncols = 2
errors = 0
ncols = 3
csv = ::CSV.new(io, {encoding: 'ISO-8859-1', headers: false})
csv.each do |row|
raise "file does not have expected number of columns (#{ncols}): #{row.length}" unless row.length == ncols
region = row[0]
regionname = row[1]
countrycode = row[0]
region = row[1]
regionname = row[2]
stmt = "UPDATE #{self.table_name} SET regionname = #{MaxMindIsp.quote_value(regionname)} WHERE countrycode = #{MaxMindIsp.quote_value(countrycode)} AND region = #{MaxMindIsp.quote_value(region)}"
self.connection.execute stmt
count += 1
if countrycode.length == 2 and region.length == 2 and regionname.length >= 2 and regionname.length <= 64
if ActiveRecord::Base.logger and ActiveRecord::Base.logger.level < Logger::INFO
ActiveRecord::Base.logger.debug "... logging updates to #{self.table_name} suspended ..."
ActiveRecord::Base.logger.level = Logger::INFO
stmt = "INSERT INTO #{self.table_name} (countrycode, region, regionname) VALUES (#{self.connection.quote(countrycode)}, #{self.connection.quote(region)}, #{self.connection.quote(regionname)})"
self.connection.execute stmt
count += 1
if ActiveRecord::Base.logger and ActiveRecord::Base.logger.level < Logger::INFO
ActiveRecord::Base.logger.debug "... logging updates to #{self.table_name} suspended ..."
ActiveRecord::Base.logger.level = Logger::INFO
end
else
ActiveRecord::Base.logger.warn("bogus region_codes record '#{countrycode}', '#{region}', '#{regionname}'") if ActiveRecord::Base.logger
errors += 1
end
end
if ActiveRecord::Base.logger
ActiveRecord::Base.logger.level = saved_level
ActiveRecord::Base.logger.debug "updated #{count} records in #{self.table_name}"
ActiveRecord::Base.logger.debug "inserted #{count} records into #{self.table_name}, #{errors} errors"
end
end # file
end # transaction

View File

@ -42,8 +42,8 @@ module JamRuby
# verify invitation exists for this user and session
invitation = Invitation.where("music_session_id = ? AND receiver_id = ?", music_session.id, user.id)
if invitation.blank?
raise PermissionError, "Only a session invitee can create an RSVP."
if invitation.blank? && !music_session.open_rsvps
raise PermissionError, "Only a session invitee can create an RSVP for this session."
end
# verify slot IDs exist in request
@ -56,6 +56,7 @@ module JamRuby
@rsvp.user = user
slot_ids = params[:rsvp_slots]
instruments = []
# for each slot requested, do the following:
@ -204,9 +205,9 @@ module JamRuby
rsvp_request.canceled = true
rsvp_request.cancel_all = false
when 'no'
rsvp_request.canceled = false
rsvp_request.cancel_all = false
# when 'no'
# rsvp_request.canceled = false
# rsvp_request.cancel_all = false
when 'all'
rsvp_request.canceled = true

View File

@ -3,15 +3,19 @@ require 'ipaddr'
module JamRuby
class Score < ActiveRecord::Base
MAX_YELLOW_LATENCY = 40
self.table_name = 'scores'
attr_accessible :alocidispid, :anodeid, :aaddr, :blocidispid, :bnodeid, :baddr, :score, :score_dt, :scorer
attr_accessible :alocidispid, :anodeid, :aaddr, :blocidispid, :bnodeid, :baddr, :score, :score_dt, :scorer, :scoring_data
default_scope order('score_dt desc')
def self.createx(alocidispid, anodeid, aaddr, blocidispid, bnodeid, baddr, score, score_dt)
def self.createx(alocidispid, anodeid, aaddr, blocidispid, bnodeid, baddr, score, score_dt=nil, score_data=nil)
score_dt = Time.new.utc if score_dt.nil?
Score.create(alocidispid: alocidispid, anodeid: anodeid, aaddr: aaddr, blocidispid: blocidispid, bnodeid: bnodeid, baddr: baddr, score: score, scorer: 0, score_dt: score_dt)
score = score.ceil
raise "score must be positive" if score <= 0
Score.create(alocidispid: alocidispid, anodeid: anodeid, aaddr: aaddr, blocidispid: blocidispid, bnodeid: bnodeid, baddr: baddr, score: score, scorer: 0, score_dt: score_dt, scoring_data: score_data)
Score.create(alocidispid: blocidispid, anodeid: bnodeid, aaddr: baddr, blocidispid: alocidispid, bnodeid: anodeid, baddr: aaddr, score: score, scorer: 1, score_dt: score_dt) if alocidispid != blocidispid
end
@ -25,5 +29,10 @@ module JamRuby
return -1 if s.nil?
return s.score
end
def self.score_conns(c1, c2, score)
self.createx(c1.locidispid, c1.client_id, c1.addr, c2.locidispid, c2.client_id, c2.addr, score)
end
end
end

View File

@ -103,6 +103,18 @@ module JamRuby
DISTANCE_OPTS = B_DISTANCE_OPTS = M_DISTANCE_OPTS = [['Any', 0], [1000.to_s, 1000], [500.to_s, 500], [250.to_s, 250], [100.to_s, 100], [50.to_s, 50], [25.to_s, 25]]
# the values for score ranges are raw roundtrip scores. david often talks of one way scores (<= 20 is good), but
# the client reports scores as roundtrip and the server uses those values throughout
GOOD_SCORE = '.-40'
MODERATE_SCORE = '40-80'
POOR_SCORE = '80-120'
UNACCEPTABLE_SCORE = '120-.'
SCORED_SCORE = '.-.' # does not appear in menu choices
TEST_SCORE = '.-60' # does not appear in menu choices
ANY_SCORE = ''
M_SCORE_OPTS = [['Any', ANY_SCORE], ['Good', GOOD_SCORE], ['Moderate', MODERATE_SCORE], ['Poor', POOR_SCORE], ['Unacceptable', UNACCEPTABLE_SCORE]]
M_SCORE_DEFAULT = ANY_SCORE
F_SORT_RECENT = ['Most Recent', :date]
F_SORT_OLDEST = ['Most Liked', :likes]
F_SORT_LENGTH = ['Most Played', :plays]
@ -120,44 +132,137 @@ module JamRuby
ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering }
end
def self.musician_filter(params={}, current_user=nil)
rel = User.musicians
# produce a list of musicians (users where musician is true)
# params:
# instrument - instrument to search for or blank
# score_limit - a range specification for score, see M_SCORE_OPTS above.
# handled by relation_pagination:
# page - page number to fetch (origin 1)
# per_page - number of entries per page
# handled by order_param:
# orderby - what sort of search, also defines order (followed, plays, playing)
# previously handled by where_latlng:
# distance - defunct!
# city - defunct!
# remote_ip - defunct!
def self.musician_filter(params={}, user=nil, conn=nil)
# puts "================ params #{params.inspect}"
# puts "================ user #{user.inspect}"
# puts "================ conn #{conn.inspect}"
rel = User.musicians_geocoded
rel = rel.select('users.*')
rel = rel.group('users.id')
unless (instrument = params[:instrument]).blank?
rel = rel.joins("RIGHT JOIN musicians_instruments AS minst ON minst.user_id = users.id")
.where(['minst.instrument_id = ? AND users.id IS NOT NULL', instrument])
rel = rel.joins("inner JOIN musicians_instruments AS minst ON minst.user_id = users.id")
.where(['minst.instrument_id = ?', instrument])
end
rel = MaxMindGeo.where_latlng(rel, params, current_user)
# to find appropriate musicians we need to join users with scores to get to those with no scores or bad scores
# weeded out
sel_str = 'users.*'
case ordering = self.order_param(params)
when :plays # FIXME: double counting?
sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}"
rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.user_id = users.id")
.joins("LEFT JOIN recordings AS records ON records.owner_id = users.id")
.group("users.id")
.order("play_count DESC, users.created_at DESC")
when :followed
sel_str = "COUNT(follows) AS search_follow_count, #{sel_str}"
rel = rel.joins("LEFT JOIN follows ON follows.followable_id = users.id")
.group("users.id")
.order("COUNT(follows) DESC, users.created_at DESC")
when :playing
rel = rel.joins("LEFT JOIN connections ON connections.user_id = users.id")
.where(['connections.music_session_id IS NOT NULL AND connections.aasm_state != ?',
'expired'])
.order("users.created_at DESC")
# filter on scores using selections from params
# see M_SCORE_OPTS
score_limit = TEST_SCORE
l = params[:score_limit]
unless l.nil?
score_limit = l
end
rel = rel.select(sel_str)
# puts "================ score_limit #{score_limit}"
locidispid = ((conn and conn.client_type == 'client') ? conn.locidispid : ((user and user.musician) ? user.last_jam_locidispid : nil))
# puts "================ locidispid #{locidispid}"
unless locidispid.nil?
# score_join of left allows for null scores, whereas score_join of inner requires a score however good or bad
# this is ANY_SCORE:
score_join = 'left' # or 'inner'
score_min = nil
score_max = nil
case score_limit
when GOOD_SCORE
score_join = 'inner'
score_min = nil
score_max = 40
when MODERATE_SCORE
score_join = 'inner'
score_min = 40
score_max = 80
when POOR_SCORE
score_join = 'inner'
score_min = 80
score_max = 120
when UNACCEPTABLE_SCORE
score_join = 'inner'
score_min = 120
score_max = nil
when SCORED_SCORE
score_join = 'inner'
score_min = nil
score_max = nil
when TEST_SCORE
score_join = 'inner'
score_min = nil
score_max = 60
when ANY_SCORE
# the default of ANY setup above applies
else
# the default of ANY setup above applies
end
rel = rel.joins("#{score_join} join scores on scores.alocidispid = users.last_jam_locidispid")
.where(['scores.blocidispid = ?', locidispid])
rel = rel.where(['scores.score > ?', score_min]) unless score_min.nil?
rel = rel.where(['scores.score <= ?', score_max]) unless score_max.nil?
rel = rel.select('scores.score')
rel = rel.group('scores.score')
end
ordering = self.order_param(params)
# puts "================ ordering #{ordering}"
case ordering
when :plays # FIXME: double counting?
# sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}"
rel = rel.select('COUNT(records.id)+COUNT(sessions.id) AS search_play_count')
rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.user_id = users.id")
rel = rel.joins("LEFT JOIN recordings AS records ON records.owner_id = users.id")
rel = rel.order("search_play_count DESC")
when :followed
rel = rel.joins('left outer join follows on follows.followable_id = users.id')
rel = rel.select('count(follows.user_id) as search_follow_count')
rel = rel.order('search_follow_count DESC')
when :playing
rel = rel.joins("inner JOIN connections ON connections.user_id = users.id")
rel = rel.where(['connections.aasm_state != ?', 'expired'])
end
unless locidispid.nil?
rel = rel.order('scores.score ASC NULLS LAST')
end
rel = rel.order('users.created_at DESC')
# rel = rel.select(sel_str)
rel, page = self.relation_pagination(rel, params)
rel = rel.includes([:instruments, :followings, :friends])
# puts "======================== sql #{rel.to_sql}"
objs = rel.all
# puts "======================== objs #{objs.inspect}"
# if objs.length > 0
# puts "======================== attributes #{objs[0].attributes}"
# puts "======================== score #{objs[0].score}"
# end
srch = Search.new
srch.search_type = :musicians_filter
srch.page_num, srch.page_count = page, objs.total_pages
srch.musician_results_for_user(objs, current_user)
srch.musician_results_for_user(objs, user)
end
def self.relation_pagination(rel, params)
@ -273,13 +378,28 @@ module JamRuby
false
end
def self.new_musicians(usr, since_date=Time.now - 1.week, max_count=50, radius=M_MILES_DEFAULT)
rel = User.musicians
def self.new_musicians(usr, since_date)
# this attempts to find interesting musicians to tell another musician about where interesting
# is "has a good score and was created recently"
# we're sort of depending upon usr being a musicians_geocoded as well...
# this appears to only be called from EmailBatchNewMusician#deliver_batch_sets! which is
# an offline process and thus uses the last jam location as "home base"
locidispid = usr.last_jam_locidispid
score_limit = 60
limit = 50
rel = User.musicians_geocoded
.where(['created_at >= ? AND users.id != ?', since_date, usr.id])
.within(radius, :origin => [usr.lat, usr.lng])
.order('created_at DESC')
.limit(max_count)
.joins('inner join scores on users.last_jam_locidispid = scores.alocidispid')
.where(['scores.blocidispid = ?', locidispid])
.where(['scores.score <= ?', score_limit])
.order('scores.score') # best scores first
.order('users.created_at DESC') # then most recent
.limit(limit)
objs = rel.all.to_a
if block_given?
yield(objs) if 0 < objs.count
else

View File

@ -12,13 +12,16 @@ module JamRuby
include Geokit::ActsAsMappable::Glue unless defined?(acts_as_mappable)
acts_as_mappable
after_save :check_lat_lng
# after_save :check_lat_lng
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_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :lat, :lng
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_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_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, :updating_progression_field, :mods_json
# used for temporary data store of latency between self and some other user
attr_accessor :latency_store
belongs_to :icecast_server_group, class_name: "JamRuby::IcecastServerGroup", inverse_of: :users, foreign_key: 'icecast_server_group_id'
# authorizations (for facebook, etc -- omniauth)
@ -140,7 +143,7 @@ module JamRuby
scope :musicians, where(:musician => true)
scope :fans, where(:musician => false)
scope :geocoded_users, where(['lat IS NOT NULL AND lng IS NOT NULL'])
scope :geocoded_users, where(User.arel_table[:last_jam_locidispid].not_eq(nil))
scope :musicians_geocoded, musicians.geocoded_users
scope :email_opt_in, where(:subscribe_email => true)
@ -221,11 +224,11 @@ module JamRuby
end
def location= location_hash
unless location_hash.blank?
unless location_hash.nil?
self.city = location_hash[:city]
self.state = location_hash[:state]
self.country = location_hash[:country]
end if self.city.blank?
end
end
def musician?
@ -290,6 +293,11 @@ module JamRuby
self.music_sessions.size
end
def joined_score
nil unless has_attribute?(:score)
read_attribute(:score).to_i
end
# mods comes back as text; so give ourselves a parsed version
def mods_json
@mods_json ||= mods ? JSON.parse(mods, symbolize_names: true) : {}
@ -776,12 +784,20 @@ module JamRuby
end
user.admin = false
user.city = location[:city]
user.state = location[:state]
user.country = location[:country]
user.location = location
# user.city = location[:city]
# user.state = location[:state]
# user.country = location[:country]
user.birth_date = birth_date
if user.musician # only update instruments if the user is a musician
if musician
user.last_jam_addr = location[:addr]
user.last_jam_locidispid = location[:locidispid]
user.last_jam_updated_reason = 'r'
user.last_jam_updated_at = Time.now
end
if musician # only update instruments if the user is a musician
unless instruments.nil?
instruments.each do |musician_instrument_param|
instrument = Instrument.find(musician_instrument_param[:instrument_id])
@ -1095,55 +1111,64 @@ module JamRuby
!self.city.blank? && (!self.state.blank? || !self.country.blank?)
end
def check_lat_lng
if (city_changed? || state_changed? || country_changed?) && !lat_changed? && !lng_changed?
update_lat_lng
end
end
# def check_lat_lng
# if (city_changed? || state_changed? || country_changed?) && !lat_changed? && !lng_changed?
# update_lat_lng
# end
# end
def update_lat_lng(ip_addy=nil)
if provides_location? # ip_addy argument ignored in this case
return false unless ip_addy.nil? # do nothing if attempting to set latlng from an ip address
query = { :city => self.city }
query[:region] = self.state unless self.state.blank?
query[:country] = self.country unless self.country.blank?
if geo = MaxMindGeo.where(query).limit(1).first
geo.lat = nil if geo.lat = 0
geo.lng = nil if geo.lng = 0
if geo.lat && geo.lng && (self.lat != geo.lat || self.lng != geo.lng)
self.update_attributes({ :lat => geo.lat, :lng => geo.lng })
return true
end
end
elsif ip_addy
if geo = MaxMindGeo.ip_lookup(ip_addy)
geo.lat = nil if geo.lat = 0
geo.lng = nil if geo.lng = 0
if self.lat != geo.lat || self.lng != geo.lng
self.update_attributes({ :lat => geo.lat, :lng => geo.lng })
return true
end
end
else
if self.lat || self.lng
self.update_attributes({ :lat => nil, :lng => nil })
return true
end
end
false
end
# def update_lat_lng(ip_addy=nil)
# if provides_location? # ip_addy argument ignored in this case
# return false unless ip_addy.nil? # do nothing if attempting to set latlng from an ip address
# query = { :city => self.city }
# query[:region] = self.state unless self.state.blank?
# query[:country] = self.country unless self.country.blank?
# if geo = MaxMindGeo.where(query).limit(1).first
# geo.lat = nil if geo.lat = 0
# geo.lng = nil if geo.lng = 0
# if geo.lat && geo.lng && (self.lat != geo.lat || self.lng != geo.lng)
# self.update_attributes({ :lat => geo.lat, :lng => geo.lng })
# return true
# end
# end
# elsif ip_addy
# if geo = MaxMindGeo.ip_lookup(ip_addy)
# geo.lat = nil if geo.lat = 0
# geo.lng = nil if geo.lng = 0
# if self.lat != geo.lat || self.lng != geo.lng
# self.update_attributes({ :lat => geo.lat, :lng => geo.lng })
# return true
# end
# end
# else
# if self.lat || self.lng
# self.update_attributes({ :lat => nil, :lng => nil })
# return true
# end
# end
# false
# end
def current_city(ip_addy=nil)
unless self.city
if self.lat && self.lng
# todo this is really dumb, you can't compare lat lng for equality
return MaxMindGeo.where(['lat = ? AND lng = ?',self.lat,self.lng]).limit(1).first.try(:city)
elsif ip_addy
return MaxMindGeo.ip_lookup(ip_addy).try(:city)
end
else
return self.city
end
# unless self.city
# if self.lat && self.lng
# # todo this is really dumb, you can't compare lat lng for equality
# return MaxMindGeo.where(['lat = ? AND lng = ?',self.lat,self.lng]).limit(1).first.try(:city)
# elsif ip_addy
# return MaxMindGeo.ip_lookup(ip_addy).try(:city)
# end
# else
# return self.city
# end
self.city
end
def update_addr_loc(connection, reason)
self.last_jam_addr = connection.addr
self.last_jam_locidispid = connection.locidispid
self.last_jam_updated_reason = reason
self.last_jam_updated_at = Time.now
self.save
end
def top_followings
@ -1153,9 +1178,15 @@ module JamRuby
.limit(3)
end
def nearest_musicians
# FIXME: replace with Scotts scoring query
Search.new_musicians(self, Time.now - 1.week)
end
def self.deliver_new_musician_notifications(since_date=nil)
since_date ||= Time.now-1.week
self.geocoded_users.find_each do |usr|
# return musicians with locidispid not null
self.musicians_geocoded.find_each do |usr|
Search.new_musicians(usr, since_date) do |new_nearby|
UserMailer.new_musicians(usr, new_nearby).deliver
end

View File

@ -0,0 +1,30 @@
require 'resque'
require 'resque-lonely_job'
module JamRuby
class BatchEmailJob
extend Resque::Plugins::LonelyJob
@@log = Logging.logger[BatchEmailJob]
@queue = :batch_emails
def self.perform(batch_id)
if ebatch = EmailBatch.find_by_id(batch_id)
ebatch.deliver_batch
end
end
def self.enqueue(batch_id)
begin
Resque.enqueue(self, batch_id)
true
rescue
# implies redis is down. but since there is no retry logic with this, we should at least log a warn in case we've configured something wrong
@@log.warn("unable to enqueue")
false
end
end
end
end

View File

@ -0,0 +1,15 @@
module JamRuby
class DailySessionEmailer
extend Resque::Plugins::LonelyJob
@queue = :scheduled_daily_session_emailer
@@log = Logging.logger[DailySessionEmailer]
def self.perform
@@log.debug("waking up")
EmailBatchScheduledSessions.send_daily_session_batch
@@log.debug("done")
end
end
end

View File

@ -1,15 +0,0 @@
module JamRuby
class EmailErrorCollector
extend Resque::Plugins::LonelyJob
@queue = :email_error_collector
@@log = Logging.logger[EmailErrorCollector]
def self.perform
@@log.debug("waking up")
EmailError.capture_errors
@@log.debug("done")
end
end
end

View File

@ -0,0 +1,15 @@
module JamRuby
class NewMusicianEmailer
extend Resque::Plugins::LonelyJob
@queue = :scheduled_new_musician_emailer
@@log = Logging.logger[NewMusicianEmailer]
def self.perform
@@log.debug("waking up")
EmailBatchNewMusician.send_new_musician_batch
@@log.debug("done")
end
end
end

View File

@ -0,0 +1,15 @@
module JamRuby
class UserProgressEmailer
extend Resque::Plugins::LonelyJob
@queue = :scheduled_user_progress_emailer
@@log = Logging.logger[UserProgressEmailer]
def self.perform
@@log.debug("waking up")
EmailBatchProgression.send_progress_batch
@@log.debug("done")
end
end
end

View File

@ -118,6 +118,7 @@ FactoryGirl.define do
addr 0
locidispid 0
client_type 'client'
association :user, factory: :user
end
factory :invitation, :class => JamRuby::Invitation do
@ -451,6 +452,15 @@ FactoryGirl.define do
test_emails 4.times.collect { Faker::Internet.safe_email }.join(',')
end
factory :email_batch_new_musician, :class => JamRuby::EmailBatchNewMusician do
end
factory :email_batch_progression, :class => JamRuby::EmailBatchProgression do
end
factory :email_batch_scheduled_session, :class => JamRuby::EmailBatchScheduledSessions do
end
factory :notification, :class => JamRuby::Notification do
factory :notification_text_message do
@ -468,7 +478,7 @@ FactoryGirl.define do
factory :rsvp_slot, class: JamRuby::RsvpSlot do
association :instrument, factory: :instrument
association :music_session, factory: :music_session
association :rsvp_request_slot, factory: :rsvp_request_slot
# association :rsvp_request_slot, factory: :rsvp_request_slot
proficiency_level 'beginner'
end
@ -482,4 +492,19 @@ FactoryGirl.define do
factory :rsvp_request_slot, class: JamRuby::RsvpRequestRsvpSlot do
chosen false
end
factory :latency_tester, :class => JamRuby::LatencyTester do
ignore do
connection nil
make_connection true
end
sequence(:client_id) { |n| "LatencyTesterClientId-#{n}" }
after(:create) do |latency_tester, evaluator|
latency_tester.connection = evaluator.connection if evaluator.connection
latency_tester.connection = FactoryGirl.create(:connection, client_type: Connection::TYPE_LATENCY_TESTER, client_id: latency_tester.client_id) if evaluator.make_connection
latency_tester.save
end
end
end

View File

@ -361,7 +361,7 @@ describe ActiveMusicSession do
@music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true)
# @music_session.connections << @connection
@music_session.save!
@connection.join_the_session(@music_session, true, nil)
@connection.join_the_session(@music_session, true, nil, @user1)
end
describe "not recording" do

View File

@ -21,7 +21,7 @@ describe ClaimedRecording do
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@connection.join_the_session(@music_session, true, nil, @user)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload

Some files were not shown because too many files have changed in this diff Show More