From fea26d82f57ff3f6224f73d85d83160becaa5f4f Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 25 Apr 2016 22:01:15 -0500 Subject: [PATCH] VRFS-4037 - create a interested in school landing page --- db/manifest | 3 +- db/up/track_school_signups.sql | 1 + ruby/lib/jam_ruby/app/mailers/admin_mailer.rb | 8 + ruby/lib/jam_ruby/models/lesson_session.rb | 12 +- .../models/lesson_session_analyser.rb | 11 +- .../models/music_session_user_history.rb | 4 +- ruby/lib/jam_ruby/models/user.rb | 6 + .../models/lesson_session_analyser_spec.rb | 27 +- ruby/spec/support/utilities.rb | 4 + .../JamClassScreen.js.jsx.coffee | 28 +- ...ClassSchoolLandingBottomPage.js.jsx.coffee | 433 ++++++++++++++++++ .../JamClassSchoolLandingPage.js.jsx.coffee | 161 +++++++ .../JamClassStudentLandingPage.js.jsx.coffee | 22 +- web/app/controllers/api_users_controller.rb | 2 + web/app/controllers/landings_controller.rb | 8 + .../landings/jam_class_schools.html.slim | 14 + web/config/application.rb | 1 + web/config/environments/development.rb | 1 + web/config/routes.rb | 1 + web/lib/user_manager.rb | 4 +- web/spec/factories.rb | 1 + .../features/book_recurring_lesson_spec.rb | 75 ++- web/spec/features/book_single_lesson_spec.rb | 49 +- web/spec/features/book_test_drive_spec.rb | 31 +- web/spec/features/school_landing_spec.rb | 63 +++ web/spec/features/student_landing_spec.rb | 4 +- web/spec/features/teacher_landing_spec.rb | 12 +- web/spec/managers/user_manager_spec.rb | 20 +- web/spec/support/app_config.rb | 4 + web/spec/support/lessons.rb | 31 ++ web/spec/support/utilities.rb | 7 +- 31 files changed, 980 insertions(+), 68 deletions(-) create mode 100644 db/up/track_school_signups.sql create mode 100644 web/app/assets/javascripts/react-components/landing/JamClassSchoolLandingBottomPage.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/landing/JamClassSchoolLandingPage.js.jsx.coffee create mode 100644 web/app/views/landings/jam_class_schools.html.slim create mode 100644 web/spec/features/school_landing_spec.rb create mode 100644 web/spec/support/lessons.rb diff --git a/db/manifest b/db/manifest index b722f957d..f9df5d1fe 100755 --- a/db/manifest +++ b/db/manifest @@ -342,4 +342,5 @@ jamblaster_connection.sql teacher_progression.sql teacher_complete.sql lessons.sql -lessons_unread_messages.sql \ No newline at end of file +lessons_unread_messages.sql +track_school_signups.sql \ No newline at end of file diff --git a/db/up/track_school_signups.sql b/db/up/track_school_signups.sql new file mode 100644 index 000000000..7e66f48fc --- /dev/null +++ b/db/up/track_school_signups.sql @@ -0,0 +1 @@ +ALTER TABLE USERS ADD COLUMN school_interest BOOLEAN DEFAULT FALSE; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb index f1c4e92f0..6f8db1c02 100644 --- a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb @@ -36,6 +36,14 @@ module JamRuby subject: options[:subject]) end + def partner(options) + mail(to: APP_CONFIG.email_partners_alias, + from: APP_CONFIG.email_generic_from, + body: options[:body], + content_type: "text/plain", + subject: options[:subject]) + end + def recurly_alerts(user, options) body = options[:body] diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index 28d27b90a..09db02a52 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -114,7 +114,7 @@ module JamRuby end def self.analyse_sessions - MusicSession.joins(lesson_session: :lesson_booking).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = false').each do |music_session| + MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = false').each do |music_session| lession_session = music_session.lesson_session lession_session.analyse end @@ -122,13 +122,13 @@ module JamRuby def self.complete_sessions # this will find any paid session (recurring monthly paid, recurring single paid, single paid) - MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session| + MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session| lession_session = music_session.lesson_session lession_session.session_completed end # test drives don't have a lesson_payment_charge, so we don't join against them - MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session| + MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session| lession_session = music_session.lesson_session lession_session.session_completed end @@ -142,7 +142,7 @@ module JamRuby half_hour_from_now = Time.zone.local_to_utc(half_hour_from_now) end - MusicSession.joins(lesson_session: [:lesson_booking]).where('sent_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, half_hour_from_now).each do |music_session| + MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where('sent_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, half_hour_from_now).each do |music_session| lession_session = music_session.lesson_session lession_session.send_starting_notice end @@ -162,7 +162,7 @@ module JamRuby self.status = STATUS_COMPLETED - if lesson_booking.requires_teacher_distribution?(self) + if success && lesson_booking.requires_teacher_distribution?(self) self.teacher_distribution = TeacherDistribution.create_for_lesson(self) end @@ -304,7 +304,7 @@ module JamRuby else if !sent_notices # bad session; just poke user - UserMailer.student_weekly_recurring_no_bill(student, self).deliver + UserMailer.student_lesson_normal_no_bill(self).deliver self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true diff --git a/ruby/lib/jam_ruby/models/lesson_session_analyser.rb b/ruby/lib/jam_ruby/models/lesson_session_analyser.rb index 0526ce385..11a7c77c8 100644 --- a/ruby/lib/jam_ruby/models/lesson_session_analyser.rb +++ b/ruby/lib/jam_ruby/models/lesson_session_analyser.rb @@ -16,6 +16,7 @@ module JamRuby STUDENT_NOT_THERE_WHEN_JOINED = 'student_not_there_when_joined' JOINED_LATE = 'did_not_join_on_time' NO_SHOW = 'no_show' + NEITHER_SHOW = 'neither_show' # what are the potential results? @@ -62,14 +63,13 @@ module JamRuby teacher_ranges = merge_overlapping_ranges(all_teacher_ranges) intersecting = intersecting_ranges(student_ranges, teacher_ranges) - student_analysis = analyse_intersection(lesson_session, student_ranges) teacher_analysis = analyse_intersection(lesson_session, teacher_ranges) together_analysis = analyse_intersection(lesson_session, intersecting) # spec: https://jamkazam.atlassian.net/wiki/display/PS/Product+Specification+-+JamClass#ProductSpecification-JamClass-TeacherReceives&RespondstoLessonBookingRequest - if music_session.session_removed_at.nil? || (music_session.scheduled_start + (lesson_session.duration * 60)) < Time.now + if music_session.session_removed_at.nil? && !((music_session.scheduled_start + (lesson_session.duration * 60)) < Time.now) reason = SESSION_ONGOING bill = false else @@ -107,6 +107,8 @@ module JamRuby reason = STUDENT_FAULT elsif teacher reason = TEACHER_FAULT + else + reason = NEITHER_SHOW end end @@ -150,7 +152,8 @@ module JamRuby def self.analyse_intersection(lesson_session, ranges) - start = lesson_session.scheduled_start + # be sure to call .to_time on any ActiveRecord time, because we get a ton of deprecation warninsg about Time#succ if you use ActiveSupport:: TimeZone + start = lesson_session.scheduled_start.to_time planned_duration_seconds = lesson_session.duration * 60 end_time = start + planned_duration_seconds @@ -286,7 +289,7 @@ module JamRuby end def self.ranges_overlap?(a, b) - a.include?(b.begin) || b.include?(a.begin) + a.cover?(b.begin) || b.cover?(a.begin) end def self.merge_ranges(a, b) diff --git a/ruby/lib/jam_ruby/models/music_session_user_history.rb b/ruby/lib/jam_ruby/models/music_session_user_history.rb index 2e7bd25d4..07daa6215 100644 --- a/ruby/lib/jam_ruby/models/music_session_user_history.rb +++ b/ruby/lib/jam_ruby/models/music_session_user_history.rb @@ -26,7 +26,9 @@ module JamRuby end def range - Range.new(created_at, session_removed_at || Time.now) + # to_time to not use ActiveSupport::TimeWithZone + session_removed_at_time = session_removed_at.to_time if session_removed_at + Range.new(created_at.to_time, session_removed_at_time || Time.now.to_time) end def music_session diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 448d62cb9..05cc1a504 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1090,6 +1090,7 @@ module JamRuby teacher = options[:teacher] school_invitation_code = options[:school_invitation_code] school_id = options[:school_id] + school_interest = options[:school_interest] user = User.new user.validate_instruments = true @@ -1115,6 +1116,7 @@ module JamRuby user.has_redeemable_jamtrack = true user.is_a_student = !!student user.is_a_teacher = !!teacher + user.school_interest = !!school_interest if user.is_a_student || user.is_a_teacher musician = true end @@ -1331,6 +1333,10 @@ module JamRuby UserMailer.welcome_message(user).deliver end + if user.school_interest + AdminMailer.partner({body: "#{user.email} signed up via the /landing/jamclass/schools page.", subject: "#{user.email} is interested in schools"}) + end + if !user.email_confirmed # any errors here should also rollback the transaction; that's OK. If emails aren't going to be delivered, # it's already a really bad situation; make user signup again diff --git a/ruby/spec/jam_ruby/models/lesson_session_analyser_spec.rb b/ruby/spec/jam_ruby/models/lesson_session_analyser_spec.rb index fcd2816b5..8478a0500 100644 --- a/ruby/spec/jam_ruby/models/lesson_session_analyser_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_session_analyser_spec.rb @@ -13,6 +13,32 @@ describe LessonSessionAnalyser do let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] } describe "analyse" do + + after{ + Timecop.return + } + it "neither show" do + lesson = testdrive_lesson(user, teacher) + music_session = lesson.music_session + + Timecop.freeze((lesson.music_session.scheduled_start + lesson.duration * 60) + 1) + + analysis = LessonSessionAnalyser.analyse(lesson) + analysis[:reason].should eql LessonSessionAnalyser::NEITHER_SHOW + analysis[:student].should eql nil + analysis[:bill].should be false + + student = analysis[:student_analysis] + student[:joined_on_time].should be false + student[:joined_late].should be false + student[:waited_correctly].should be false + student[:initial_waiting_pct].should eql nil + student[:potential_waiting_time].should eql nil + + together = analysis[:together_analysis] + together[:session_time].should eql 0 + end + it "teacher joined on time, waited, student no show" do lesson = testdrive_lesson(user, teacher) music_session = lesson.music_session @@ -57,7 +83,6 @@ describe LessonSessionAnalyser do analysis = LessonSessionAnalyser.analyse(lesson) - puts analysis analysis[:reason].should eql LessonSessionAnalyser::STUDENT_FAULT analysis[:student].should eql LessonSessionAnalyser::JOINED_LATE analysis[:bill].should be true diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 21c037c4c..b32b9b348 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -7,6 +7,10 @@ def app_config 'http://localhost:3333' end + def email_partners_alias + 'partners@jamkazam.com' + end + def email_social_alias 'social@jamkazam.com' end diff --git a/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee index 07219f9d9..a722c935b 100644 --- a/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee @@ -286,19 +286,22 @@ UserStore = context.UserStore links = [] matches = {} - for problem, isPresent of user.teacher.profile_pct_summary - data = @lookup[problem] - if data? && !isPresent - matches[data.name] = data + if user.teacher? + for problem, isPresent of user.teacher.profile_pct_summary + data = @lookup[problem] + if data? && !isPresent + matches[data.name] = data - for name, data of matches - if !data.text? - links.push(`{data.name}`) + for name, data of matches + if !data.text? + links.push(`{data.name}`) - for name, data of matches - if data.text? - links.push(`{data.name}`) - `` + for name, data of matches + if data.text? + links.push(`{data.name}`) + `` + else + null onProfileHelp: (data, e) -> e.preventDefault() @@ -388,6 +391,9 @@ UserStore = context.UserStore teacherProfileUri = "/client#/profile/teacher/#{this.state.user.id}" pct = Math.round(this.state.user.teacher?.profile_pct) + if !pct? + pct = 0 + if pct == 100 pctCompleteMsg = `

Your teacher profile is {pct}% complete.

` else diff --git a/web/app/assets/javascripts/react-components/landing/JamClassSchoolLandingBottomPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamClassSchoolLandingBottomPage.js.jsx.coffee new file mode 100644 index 000000000..e484f20f6 --- /dev/null +++ b/web/app/assets/javascripts/react-components/landing/JamClassSchoolLandingBottomPage.js.jsx.coffee @@ -0,0 +1,433 @@ +context = window +rest = context.JK.Rest() + +@JamClassSchoolLandingBottomPage = React.createClass({ + + render: () -> + `
+
+

How JamClass by JamKazam Can Help Your Music School

+ +

Online music lessons offer obvious advantages to your school. You can reach students who live + + more than 30 minutes away – even students across the country. Teach during normal “off + + hours”, when students in your area are tied up at school or at work. And students and parents + + are increasingly interested in online lessons to avoid the time and hassle of traveling to/from + + your school.

+ +

Several companies have built online marketplaces of music teachers, but these markets haven’t + + offered a partnering model for existing schools. And even if they had, online music lessons + + haven’t taken off like other online Internet markets. Why? Because every one of these markets + + has relied on Skype or similar apps – built for voice chat – to deliver online music lessons. This is + + a major problem. Voice technology makes music sound awful in online sessions – so bad that + + teachers can’t assess the student’s tone and sometimes even the pitch of what they are + + playing. These apps also have very high latency – a technical term that means that the student + + and teacher cannot play together, another critical requirement for productive lessons. Since + + Skype wasn’t built for music, it also lacks many other basic features to support effective lessons, + + like a metronome, mixers, backing tracks, etc.

+ +

At JamKazam, we’ve spent years designing, patenting, and building technology specifically to + + enable musicians to play online live in sync with studio quality audio. We’ve built a wide variety + + of critical online music performance features into this platform. And now we’ve built a lesson + + marketplace on top of this foundation to match students to teachers, and we’re investing in + + marketing to drive students into the market.

+ +

+ The bottom line is that your school can now effectively offer and teach online lessons to + + students using our incredible technology. If you secure students through your own marketing + + initiatives, it’s free to use JamKazam. And if we bring new students to your school, we keep a + + portion of the lesson income for these students, helping you to grow your business, increasing + + your revenues and profitability.

+

If this sounds interesting to you, read on to learn more about some of the top features of + + JamClass by JamKazam.

+ +
+

JamClass Kudos

+ +
+ + +

Julie Bonk

+
+ Oft-recorded pianist, teacher, mentor to Grammy winner Norah Jones and Scott Hoying of Pentatonix +
+
+
+ + + +

Carl Brown of GuitarLessions365

+
+
+ + +

Justin Pierce

+
+ Masters degree in jazz studies, performer in multiple bands, saxophone instructor +
+
+
+ + +

Dave Sebree

+
+ Founder of Austin School of Music, Gibson-endorsed guitarist, touring musician +
+
+
+ + +

Sara Nelson

+
+ Cellist for Austin Lyric Opera, frequently recorded with major artists +
+
+
+
+ +
+
+

+
1
+ Play Live In Sync From Different Locations +

+

+

+
+