diff --git a/admin/Gemfile b/admin/Gemfile index f12413723..595d94904 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -108,7 +108,7 @@ end group :development, :test do gem 'capybara' - gem 'rspec-rails', '2.14.2' + gem 'rspec-rails' #, '2.14.2' gem 'jasmine', '1.3.1' gem 'execjs', '1.4.0' #gem 'therubyracer' #, '0.11.0beta8' diff --git a/admin/Gemfile.lock b/admin/Gemfile.lock index ec7d8fc45..d029ef2ce 100644 --- a/admin/Gemfile.lock +++ b/admin/Gemfile.lock @@ -92,7 +92,7 @@ GEM aws-sdk-v1 (1.67.0) json (~> 1.4) nokogiri (~> 1) - backports (3.11.0) + backports (3.11.1) bcrypt (3.1.11) bcrypt-ruby (3.0.1) binding_of_caller (0.8.0) @@ -100,7 +100,7 @@ GEM bootstrap-sass (2.0.4.0) bootstrap-will_paginate (0.0.6) will_paginate - bugsnag (6.6.1) + bugsnag (6.6.3) concurrent-ruby (~> 1.0) builder (3.2.3) cabin (0.9.0) @@ -147,7 +147,7 @@ GEM crass (1.0.3) database_cleaner (1.6.2) debug_inspector (0.0.3) - devise (4.4.0) + devise (4.4.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0, < 5.2) @@ -160,7 +160,7 @@ GEM email_validator (1.6.0) activemodel erubis (2.7.0) - et-orbi (1.0.8) + et-orbi (1.0.9) tzinfo eventmachine (1.2.3) excon (0.60.0) @@ -173,7 +173,7 @@ GEM railties (>= 3.0.0) faker (1.3.0) i18n (~> 0.5) - faraday (0.13.1) + faraday (0.14.0) multipart-post (>= 1.2, < 3) ffi (1.9.18) fission (0.5.0) @@ -314,10 +314,10 @@ GEM domain_name (~> 0.5) httparty (0.15.6) multi_xml (>= 0.5.2) - i18n (0.9.1) + i18n (0.9.3) concurrent-ruby (~> 1.0) inflecto (0.0.2) - influxdb (0.5.2) + influxdb (0.5.3) influxdb-rails (0.4.3) influxdb (~> 0.5.0) railties (> 3) @@ -374,7 +374,7 @@ GEM mimemagic (0.3.2) mini_mime (1.0.0) mini_portile2 (2.3.0) - minitest (5.11.1) + minitest (5.11.2) mono_logger (1.1.0) multi_json (1.13.1) multi_xml (0.6.0) @@ -404,7 +404,7 @@ GEM capybara (~> 2.1) cliver (~> 0.3.1) websocket-driver (>= 0.2.0) - polyamorous (1.3.2) + polyamorous (1.3.3) activerecord (>= 3.0) postgres-copy (0.6.0) activerecord (>= 3.0.0) @@ -428,7 +428,7 @@ GEM binding_of_caller (>= 0.7) pry (>= 0.9.11) public_suffix (3.0.1) - puma (3.11.0) + puma (3.11.2) rack (1.6.8) rack-protection (1.5.3) rack @@ -464,16 +464,16 @@ GEM thor (>= 0.18.1, < 2.0) raindrops (0.19.0) rake (12.3.0) - ransack (1.8.4) + ransack (1.8.6) actionpack (>= 3.0) activerecord (>= 3.0) activesupport (>= 3.0) i18n - polyamorous (~> 1.3) + polyamorous (~> 1.3.2) rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - recurly (2.12.0) + recurly (2.12.1) redis (4.0.1) redis-namespace (1.6.0) redis (>= 3.0.4) @@ -507,22 +507,27 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - 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) - rspec-rails (2.14.2) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.1) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-rails (3.7.2) actionpack (>= 3.0) - activemodel (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.0) ruby-protocol-buffers (1.2.2) ruby-xz (0.2.3) ffi (~> 1.9) @@ -589,7 +594,7 @@ GEM tilt (2.0.8) tzinfo (1.2.4) thread_safe (~> 0.1) - uglifier (4.1.3) + uglifier (4.1.4) execjs (>= 0.3.0, < 3) unf (0.1.3) unf_ext @@ -679,7 +684,7 @@ DEPENDENCIES resque-retry resque_mailer rest-client - rspec-rails (= 2.14.2) + rspec-rails ruby-protocol-buffers (= 1.2.2) rubyzip sanitize diff --git a/admin/app/admin/dashboard.rb b/admin/app/admin/dashboard.rb index a22643621..51903b216 100644 --- a/admin/app/admin/dashboard.rb +++ b/admin/app/admin/dashboard.rb @@ -5,11 +5,14 @@ ActiveAdmin.register_page "Dashboard" do content :title => proc{ I18n.t("active_admin.dashboard") } do div :class => "blank_slate_container", :id => "dashboard_default_message" do span :class => "blank_slate" do - span "JamKazam Data Administration Portal" + span "JamKazam Administration Portal" small ul do - li "Admin users are users with the admin boolean set to true" - li "Invite JamKazam users using the 'Users > Invite' menu in header" - li "Admin users are created/deleted when toggling the 'admin' flag for JamKazam users" + li link_to "Users", admin_users_path + li link_to "Teachers", admin_teachers_path + li link_to "Onboarding Management", admin_currently_onboardings_path + li link_to "Lesson Sessions", admin_lesson_sessions_path + li link_to "Slow Lesson Responses", admin_slow_responses_path + end end end diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb index 2c4029549..cce181faf 100644 --- a/admin/app/admin/jam_ruby_users.rb +++ b/admin/app/admin/jam_ruby_users.rb @@ -12,6 +12,8 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do filter :created_at filter :updated_at + includes :purchased_jam_tracks, :jam_track_rights => :jam_track, :taken_lessons => :music_session, :taught_lessons => :music_session + form :partial => "form" show do |user| @@ -79,7 +81,9 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do else end end - row "Signup" do user.created_at.to_date end + row "Signup" do + user.created_at.to_date + end row "Assigned", :onboarder_assigned_at row "Email 1", :onboarding_email_1_sent_at row "Email 2", :onboarding_email_2_sent_at @@ -97,6 +101,14 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do end end + + panel "Teacher Setting" do + attributes_table do + row :is_searchable + end + end if user.teacher + + panel "Lessons" do attributes_table do row "Taken Lessons" do @@ -154,6 +166,24 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do end + panel "JamTracks" do + div do + link_to "Give JamTrack", "../jam_track_rights/new" + end + + attributes_table do + row "Purchased JamTracks" do + table_for user.purchased_jam_tracks.unscope(:order).order('original_artist asc', 'name asc') do + column "Artist", :original_artist + column "Name", :name + column "Can Download", :can_download, as: :boolean, hint: 'Can download is the 2.99 value (have download rights)' + column "Version", :version + column "ID", :id + end + end + end + end + active_admin_comments end @@ -174,22 +204,14 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do link_to user.email, resource_path(user) end column :admin - column :updated_at column :created_at column :musician do |user| user.musician? ? true : false end column :city column :state - column :country column :first_name column :last_name - column :birth_date - column :gender - column :email_confirmed - column :photo_url - column :session_settings - column :can_invite end controller do diff --git a/admin/app/admin/jam_track_right.rb b/admin/app/admin/jam_track_right.rb index 0ea648dcb..6892b6aaf 100644 --- a/admin/app/admin/jam_track_right.rb +++ b/admin/app/admin/jam_track_right.rb @@ -49,6 +49,7 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do f.inputs 'New Jam Track Right' do f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false f.input :user, :required=>true, collection: User.all, include_blank: false + f.input :can_download, :required => true, as: :boolean end f.actions end diff --git a/admin/app/admin/slow_responses.rb b/admin/app/admin/slow_responses.rb index 46bb3f919..32f3f3045 100644 --- a/admin/app/admin/slow_responses.rb +++ b/admin/app/admin/slow_responses.rb @@ -6,45 +6,101 @@ ActiveAdmin.register JamRuby::LessonSession, :as => 'SlowResponses' do config.batch_actions = false config.per_page = 100 config.paginate = true - config.filters = false + config.filters = true - scope("Slow Responses", default: true) { |scope| scope.unscoped.slow_responses } - scope("Least Time Left") { |scope| scope.unscoped.least_time_left } + filter :intervened_at_null, label: 'Not Yet Intervened', as: :boolean + + scope("72 hours since last request/counter", default: true) { |scope| scope.slow_responses } + + member_action :cancel_lesson, :method => :get do + result = resource.cancel_by_admin + + if result.errors.any? + redirect_to :back, notice: resource.errors.inspect + else + redirect_to :back, notice: 'Canceled lesson' + end + end + + member_action :intervened, :method => :get do + resource.intervened + redirect_to :back + end index do - column "Teacher" do |lesson_session| - teacher = lesson_session.teacher - span do - link_to "#{teacher.name} (#{teacher.email})", "#{Rails.application.config.external_root_url}/client#/profile/teacher/#{teacher.id}" + column "Not Responded" do |lesson_session| + div do + if lesson_session.student_last_proposed? + "Teacher" + else + "Student" + end + end + div do + late_responder = lesson_session.late_responder + span do + link_to "#{late_responder.name}", late_responder.admin_url + end + end + div do + lesson_session.late_responder.email end end - column "Student" do |lesson_session| - student = lesson_session.student - span do - link_to "#{student.name} (#{student.email})", "#{Rails.application.config.external_root_url}/client#/profile/#{student.id}" + column "Proposer" do |lesson_session| + div do + if lesson_session.student_last_proposed? + "Student" + else + "Teacher" + end + end + div do + last_proposer = lesson_session.last_proposer + span do + link_to "#{last_proposer.name}", last_proposer.admin_url + end + end + div do + lesson_session.last_proposer.email end end - column "Type" do |lesson_session| - link_to lesson_session.display_type + column "Lesson Type" do |lesson_session| + lesson_session.display_type end - column "Start Time" do |lesson_session| + column "Proposed Start Time" do |lesson_session| span do if lesson_session.music_session.nil? raise "Lessonsesison with no id #{lesson_session.id}" else - lesson_session.music_session.pretty_scheduled_start(true) + link_to lesson_session.music_session.pretty_scheduled_start(true), lesson_session.admin_url + end end end - column "Last student comms date" do |lesson_session| - lesson_session.last_student_comm_date - end column "Days with no response" do |lesson_session| - "#{lesson_session.days_no_response} days" + if lesson_session.last_response_time.nil? + 'N/A' + else + "#{((Time.now - lesson_session.last_response_time) / 24 / 3600).floor} days" + end end + column "Num Reqs/Counters" do |lesson_session| + ChatMessage.unscoped.where(lesson_session_id: lesson_session.id).where('purpose = ? OR purpose = ?', "Lesson Requested", "New Time Proposed").count(:purpose) + end + column "Intervene" do |lesson_session| + span do + if lesson_session.intervened_at + lesson_session.intervened_at.to_date + else + link_to("intervened", intervened_admin_slow_response_path(lesson_session.id), {confirm: "Mark Lesson as 'Intervened' (i.e., email sent to by hand)?"}) + end - - + end + end + column "Cancel Lesson" do |lesson_session| + span do + link_to("cancel", cancel_lesson_admin_slow_response_path(lesson_session.id), {confirm: "Cancel Lesson?"}) + end + end end - end \ No newline at end of file diff --git a/admin/app/admin/teachers.rb b/admin/app/admin/teachers.rb index c79cd5bfb..7976a7cd3 100644 --- a/admin/app/admin/teachers.rb +++ b/admin/app/admin/teachers.rb @@ -18,7 +18,7 @@ ActiveAdmin.register JamRuby::Teacher, :as => 'Teachers' do end filter :teacher_full_name_or_user_email_cont, label: 'Name', as: :string - filter :user_phantom, label: 'Phantom', as: :boolean, selected: 'false' + filter :user_phantom, label: 'Phantom ', as: :boolean, selected: 'false' #filter :by_search_user_name, label: "Name", as: :string diff --git a/db/manifest b/db/manifest index 61df1be7d..19a9059d2 100755 --- a/db/manifest +++ b/db/manifest @@ -382,4 +382,5 @@ amazon_signup.sql age_out_sessions.sql alter_crash_dumps.sql onboarding.sql -better_lesson_notices.sql \ No newline at end of file +better_lesson_notices.sql +teacher_search_control.sql \ No newline at end of file diff --git a/db/up/teacher_search_control.sql b/db/up/teacher_search_control.sql new file mode 100644 index 000000000..60a017ac0 --- /dev/null +++ b/db/up/teacher_search_control.sql @@ -0,0 +1,4 @@ +ALTER TABLE teachers ADD COLUMN is_searchable BOOLEAN NOT NULL DEFAULT true; +ALTER TABLE lesson_sessions ADD COLUMN canceled_by_admin TIMESTAMP without time zone; +ALTER TABLE lesson_bookings ADD COLUMN canceled_by_admin TIMESTAMP without time zone; +ALTER TABLE lesson_sessions ADD COLUMN intervened_at TIMESTAMP without time zone; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_canceled.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_canceled.html.erb index 7151dbcfb..a0420f3ef 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_canceled.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_canceled.html.erb @@ -1,6 +1,9 @@ <% provide(:title, @subject) %> -<% provide(:photo_url, @lesson_booking.canceler.resolved_photo_url) %> - +<% if @lesson_booking.canceler %> + <% provide(:photo_url, @lesson_booking.canceler.resolved_photo_url) %> +<% else %> + <% provide(:photo_url, "#{APP_CONFIG.external_root_url}/assets/shared/avatar_generic.png") %> +<% end %> <% content_for :note do %>

<% if @lesson_booking.recurring %> diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_declined.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_declined.html.erb index 410621edb..b394a33cc 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_declined.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_booking_declined.html.erb @@ -4,14 +4,27 @@ <% content_for :note do %>

- This teacher has declined your lesson request. + <% if @lesson_booking.canceled_by_admin %> + This lesson was canceled by a JamKazam administrator. + <% else %> + This teacher has declined your lesson request. + <% end %> <% if @message.present? %> -

<%= @teacher.name %> says: + <% if @lesson_booking.canceled_by_admin %> +

System says: + <% else %> +

<%= @teacher.name %> says: + <% end %>
<%= @message %>
<% end %> -

Click the button below to view the teacher's response.

+ <% if @lesson_booking.canceled_by_admin %> +

Click the button below to view the administrator's message.

+ <% else %> +

Click the button below to view the teacher's response.

+ <% end %> +

VIEW RESPONSE

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_canceled.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_canceled.html.erb index 484bccb78..9c8d1ebb9 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_canceled.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_canceled.html.erb @@ -1,5 +1,9 @@ <% provide(:title, @subject) %> -<% provide(:photo_url, @lesson_session.canceler.resolved_photo_url) %> +<% if @lesson_booking.canceler %> + <% provide(:photo_url, @lesson_session.canceler.resolved_photo_url) %> +<% else %> + <% provide(:photo_url, "#{APP_CONFIG.external_root_url}/assets/shared/avatar_generic.png") %> +<% end %> <% content_for :note do %>

@@ -10,7 +14,11 @@ <%= @session_date %> <% if @message.present? %> -

<%= @lesson_session.canceler.name %> says: + <% if @lesson_session.canceled_by_admin %> +

System says: + <% else %> +

<%= @lesson_session.canceler.name %> says: + <% end %>
<%= @message %>
<% end %> diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_booking_canceled.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_booking_canceled.html.erb index 8de563ccf..a5fd6749a 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_booking_canceled.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_booking_canceled.html.erb @@ -1,5 +1,9 @@ <% provide(:title, @subject) %> -<% provide(:photo_url, @lesson_booking.canceler.resolved_photo_url) %> +<% if @lesson_booking.canceler %> + <% provide(:photo_url, @lesson_booking.canceler.resolved_photo_url) %> +<% else %> + <% provide(:photo_url, "#{APP_CONFIG.external_root_url}/assets/shared/avatar_generic.png") %> +<% end %> <% content_for :note do %>

@@ -16,7 +20,12 @@ <% end %> <% if @message.present? %> -

<%= @lesson_booking.canceler.name %> says: + <% if @lesson_booking.canceled_by_admin %> +

System says: + <% else %> +

<%= @lesson_booking.canceler.name %> says: + <% end %> +
<%= @message %>
<% end %> diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_canceled.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_canceled.html.erb index 4a60fe695..a96d48b59 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_canceled.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_canceled.html.erb @@ -1,5 +1,9 @@ <% provide(:title, @subject) %> -<% provide(:photo_url, @lesson_session.canceler.resolved_photo_url) %> +<% if @lesson_booking.canceler %> + <% provide(:photo_url, @lesson_session.canceler.resolved_photo_url) %> +<% else %> + <% provide(:photo_url, "#{APP_CONFIG.external_root_url}/assets/shared/avatar_generic.png") %> +<% end %> <% content_for :note do %>

@@ -11,7 +15,11 @@ <%= @session_date %> <% if @message.present? %> -

<%= @lesson_session.canceler.name %> says: + <% if @lesson_session.canceled_by_admin %> +

System says: + <% else %> +

<%= @lesson_session.canceler.name %> says: + <% end %>
<%= @message %>
<% end %> diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index b9af3eaf8..e02399663 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -7,7 +7,7 @@ module JamRuby @@log = Logging.logger[JamTrackRight] attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count - attr_accessible :user_id, :jam_track_id, as: :admin + attr_accessible :user_id, :jam_track_id, :can_download, as: :admin attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44 belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" @@ -38,6 +38,8 @@ module JamRuby key = rsa_key.to_pem() self.private_key_44 = key self.private_key_48 = key + + self.version = jam_track.version end def after_save # try to catch major transitions: diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index 2280ad086..ddd30c2b1 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -741,14 +741,16 @@ module JamRuby self end - def cancel_tracking(canceler, message) + def cancel_tracking(canceler, message, canceled_by_admin = false) canceled_by_student = canceler == student self.status = STATUS_CANCELED self.cancel_message = message - self.canceler = canceler + self.canceler = canceler if !canceled_by_admin self.canceling = true - if canceled_by_student + if canceled_by_admin + self.canceled_by_admin = Time.now + elsif canceled_by_student self.student_canceled = true self.student_canceled_at = Time.now self.student_canceled_reason = message @@ -759,16 +761,16 @@ module JamRuby end end - def cancel(canceler, other, message) + def cancel(canceler, other, message, canceled_by_admin = false) - cancel_tracking(canceler, message) + cancel_tracking(canceler, message, canceled_by_admin) self.active = false success = save if success lesson_sessions.upcoming.each do |lesson_session| lesson_session = LessonSession.find(lesson_session.id) # because .upcoming creates ReadOnly records - lesson_session.cancel_lesson(canceler, message) + lesson_session.cancel_lesson(canceler, message, canceled_by_admin) if !lesson_session.save return lesson_session end @@ -781,7 +783,12 @@ module JamRuby UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now purpose = "Lesson Canceled" else - if canceler == student + if canceled_by_admin + # meh. no two way comm here. just bail + #Notification.send_lesson_message('canceled', next_lesson, true) + UserMailer.student_lesson_booking_declined(self, message).deliver_now + UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now + elsif canceler == student # if it's the first time acceptance student canceling, we call it a 'cancel' Notification.send_lesson_message('canceled', next_lesson, false) UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now @@ -795,7 +802,9 @@ module JamRuby end message = '' if message.nil? - msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, next_lesson, purpose) + if !canceled_by_admin + msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, next_lesson, purpose) + end else end diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index 2fa288174..3251695d8 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -79,11 +79,8 @@ module JamRuby scope :past_cancel_window, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', 24.hours.from_now) } # show all requested/countered sessions where the student was the last to communicate scope :slow_responses, -> { joins(:lesson_booking).where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED) - .where('lesson_sessions.counterer_id IS NULL OR lesson_sessions.user_id = lesson_sessions.counterer_id') + .where("(? - (COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at))) > INTERVAL '72 hours'", Time.now) .order('(COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at)) ASC') } - scope :least_time_left, -> { joins(:lesson_booking, :music_session).where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED) - .where('lesson_sessions.counterer_id IS NULL OR lesson_sessions.user_id = lesson_sessions.counterer_id') - .order('music_sessions.scheduled_start DESC') } def create_charge if payment_if_school_on_school? && !is_test_drive? && !is_monthly_payment? @@ -222,6 +219,22 @@ module JamRuby counterer_id.nil? || counterer_id == student_id end + def late_responder + if student_last_proposed? + teacher + else + student + end + end + + def last_proposer + if student_last_proposed? + student + else + teacher + end + end + def lesson_is_free? lesson_booking.booked_price == 0.00 end @@ -999,15 +1012,17 @@ module JamRuby response end - def cancel_lesson(canceler, message) + def cancel_lesson(canceler, message, canceled_by_admin = false) canceled_by_student = canceler == student self.status = STATUS_CANCELED self.cancel_message = message - self.canceler = canceler + self.canceler = canceler if !canceled_by_admin self.canceling = true - if canceled_by_student + if canceled_by_admin + + elsif canceled_by_student self.student_canceled = true self.student_canceled_at = Time.now self.student_canceled_reason = message @@ -1059,6 +1074,10 @@ module JamRuby other = canceled_by_student ? teacher : student message = params[:message] message = '' if message.nil? + if params[:canceled_by_admin] + self.canceled_by_admin = Time.now + end + if lesson_booking.recurring update_all = params[:update_all] @@ -1071,7 +1090,7 @@ module JamRuby end if update_all - response = lesson_booking.cancel(canceler, other, message) + response = lesson_booking.cancel(canceler, other, message, params[:canceled_by_admin]) if response.errors.any? raise ActiveRecord::Rollback end @@ -1081,13 +1100,20 @@ module JamRuby raise ActiveRecord::Rollback end else - cancel_lesson(canceler, message) + cancel_lesson(canceler, message, params[:canceled_by_admin]) if !save response = self raise ActiveRecord::Rollback end - msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, self, "Lesson Canceled") + if canceled_by_admin + msg = ChatMessage.create(student, nil, message, ChatMessage::CHANNEL_LESSON, nil, teacher, self, "Lesson Canceled") + msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Canceled") + else + msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, self, "Lesson Canceled") + end + + Notification.send_lesson_message('canceled', self, false) Notification.send_lesson_message('canceled', self, true) UserMailer.student_lesson_canceled(self, message).deliver_now @@ -1108,8 +1134,6 @@ module JamRuby else 'TBD' end - - end def timed_description if is_test_drive? @@ -1123,6 +1147,20 @@ module JamRuby end end + def cancel_by_admin + self.cancel({ + canceler: nil, + canceled_by_admin: true, + message: 'Canceled by JamKazam administrator', + update_all: false + }) + end + + def intervened + self.intervened_at = Time.now + self.save + end + def display_type if is_test_drive? 'TestDrive' @@ -1137,15 +1175,8 @@ module JamRuby counterer_id == user_id ? countered_at : sent_notices_at end - def days_no_response - date = last_student_comm_date - if date.nil? - '?' - else - (Time.now - last_student_comm_date) / (60 * 60 * 24) - end - - + def last_response_time + [countered_at, lesson_booking.sent_notices_at].find{|x|!x.nil?} end def stripe_description(lesson_booking) diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 9061dc5a0..09515a4ba 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -64,7 +64,7 @@ module JamRuby query = User.unscoped.joins(:teacher) # only show teachers with ready for session set to true - query = query.where('teachers.ready_for_session_at IS NOT NULL') + query = query.where('teachers.ready_for_session_at IS NOT NULL').where('teachers.is_searchable = TRUE') # always force GuitarCenter users to see only their school's teachers, regardless of what they picked if user && (user.is_guitar_center_student? || (params[:onlyMySchool] && params[:onlyMySchool] != 'false' && user.school_id)) @@ -222,6 +222,7 @@ module JamRuby teacher.price_per_month_120_cents = params[:price_per_month_120_cents] if params.key?(:price_per_month_120_cents) teacher.teaches_test_drive = params[:teaches_test_drive] if params.key?(:teaches_test_drive) teacher.test_drives_per_week = params[:test_drives_per_week] if params.key?(:test_drives_per_week) + teacher.is_searchable = params[:is_searchable] if params.key?(:is_searchable) teacher.test_drives_per_week = 10 if !params.key?(:test_drives_per_week) # default to 10 in absence of others if params.key?(:school_id) teacher.school_id = params[:school_id] diff --git a/web/app/assets/javascripts/profile.js b/web/app/assets/javascripts/profile.js index c83fc68e9..7c8b905b3 100644 --- a/web/app/assets/javascripts/profile.js +++ b/web/app/assets/javascripts/profile.js @@ -100,6 +100,7 @@ var $btnEdit = $screen.find('.edit-profile-btn'); var $btnTeacherProfileEdit = $screen.find('.edit-teacher-profile-btn'); var $btnTeacherProfileView = $screen.find('.view-teacher-profile-btn'); + var $toggleTeacherSearchEnabled = $screen.find('.teacher-search-enabled') var $btnAddFriend = $screen.find('#btn-add-friend'); var $btnFollowUser = $screen.find('#btn-follow-user'); var $btnMessageUser = $screen.find('#btn-message-user'); @@ -232,9 +233,11 @@ $btnTeacherProfileEdit.show(); if (isTeacher()) { $btnTeacherProfileView.show(); + $toggleTeacherSearchEnabled.show() } else { $btnTeacherProfileView.hide(); + $toggleTeacherSearchEnabled.hide() } $btnAddFriend.hide(); $btnFollowUser.hide(); @@ -332,6 +335,11 @@ window.ProfileActions.startProfileEdit('experience', true) return false; }) + $toggleTeacherSearchEnabled.find('input').change(function(e) { + var $check = $(this) + rest.updateTeacher({id: user.teacher.id, is_searchable: $check.is(':checked')}) + .fail(app.ajaxError); + }) } function playSoundCloudFile(e) { @@ -520,6 +528,7 @@ renderPerformanceSamples(); renderOnlinePresence(); renderInterests(); + renderTeacherSearch(); } } @@ -565,6 +574,23 @@ } } + function renderTeacherSearch() + { + if(isTeacher()) + { + if(user.teacher.is_searchable ) + { + $toggleTeacherSearchEnabled.find('input').attr('checked', 'checked') + } + else + { + $toggleTeacherSearchEnabled.find('input').removeAttr('checked') + } + + + } + + } function renderMusicalExperience() { profileUtils.renderMusicalExperience(user, $screen, isCurrentUser()) } diff --git a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee index a110ae8e0..0171cf344 100644 --- a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee @@ -401,11 +401,14 @@ UserStore = context.UserStore otherCountered: () -> @myself().id == @counterer().id + adminCanceled: () -> + this.state.booking?.canceled_by_admin? + studentCanceled: () -> - @canceler().id == @student().id + @canceler()?.id == @student().id selfCanceled: () -> - @canceler().id == @myself().id + @canceler()?.id == @myself().id studentMadeDefaultSlot: () -> @student()?.id == @defaultSlot()?.proposer_id @@ -513,7 +516,7 @@ UserStore = context.UserStore else if @isCounter() @counterer().id == @myself().id else if @isCanceled() - @canceler().id == @myself().id + @canceler()?.id == @myself().id else if @isSuspended() @studentViewing() else @@ -544,7 +547,9 @@ UserStore = context.UserStore messageBlock: (selfWroteMessage, message) -> - if selfWroteMessage + if typeof selfWroteMessage == 'string' + whoSaid = selfWroteMessage + ' said:' + else if selfWroteMessage whoSaid = "You said:" else whoSaid = "Message from #{this.other().first_name}:" @@ -660,12 +665,16 @@ UserStore = context.UserStore if !photo_url? photo_url = '/assets/shared/avatar_generic.png' + if user? + name = user.name + else + name = 'System' `

- {user.name} + {name}
` @@ -1049,7 +1058,9 @@ UserStore = context.UserStore initial = @neverAccepted() if initial - if @studentViewing() + if @adminCanceled() + action = `

A JamKazam administrator canceled this lesson request.

` + else if @studentViewing() if @studentCanceled() action = `

You canceled this lesson request.

` else @@ -1062,7 +1073,7 @@ UserStore = context.UserStore if @studentViewing() - if @studentCanceled() + if @adminCanceled() || @studentCanceled() blurb = `

We're sorry this scheduling attempt did not working out for you. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request

` else blurb = `

We're sorry this instructor has declined your request. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request

` @@ -1073,11 +1084,16 @@ UserStore = context.UserStore {blurb} SEARCH INSTRUCTORS NOW ` + cancelerUser = null + if this.adminCanceled() + cancelerUser = 'System' + else + cancelerUser = this.selfCanceled() `
{this.userHeader(canceler)} {action} - {this.messageBlock(this.selfCanceled(), this.state.booking.cancel_message)} + {this.messageBlock(cancelerUser, this.state.booking.cancel_message)}
{whatNow}
` diff --git a/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee index 9cca947cb..3cc2db13d 100644 --- a/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee +++ b/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee @@ -40,7 +40,9 @@ teacherActions = window.JK.Actions.Teacher lesson.displayStatus = 'Unconfirmed' else if lesson.status == 'canceled' lesson.displayStatus = 'Canceled' - if lesson.student_canceled + if lesson.canceled_by_admin + lesson.displayStatus = 'Canceled (by Admin)' + else if lesson.student_canceled lesson.displayStatus = 'Canceled (by Student)' else if lesson.teacher_canceled lesson.displayStatus = 'Canceled (by Teacher)' diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 7dee21c07..8dd471be3 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -18,6 +18,7 @@ *= require easydropdown_jk *= require ./jamkazam *= require ./content + *= require ./toggle *= require ./paginator *= require ./faders *= require ./header diff --git a/web/app/assets/stylesheets/client/jamkazam.scss b/web/app/assets/stylesheets/client/jamkazam.scss index 719e97fd0..43fecaf3a 100644 --- a/web/app/assets/stylesheets/client/jamkazam.scss +++ b/web/app/assets/stylesheets/client/jamkazam.scss @@ -795,4 +795,4 @@ button.stripe-connect { vertical-align: middle; height: 100%; } -} \ No newline at end of file +} diff --git a/web/app/assets/stylesheets/client/profile.scss b/web/app/assets/stylesheets/client/profile.scss index 9a49022c5..856f993a5 100644 --- a/web/app/assets/stylesheets/client/profile.scss +++ b/web/app/assets/stylesheets/client/profile.scss @@ -76,6 +76,18 @@ .band-actions { margin-top:4px; } + + .teacher-search-enabled { + padding-top:8px; + margin-bottom:20px; + } + + .list-search-label { + margin-right:10px; + padding-top:4px; + vertical-align:top; + display:inline-block; + } } .profile-head { diff --git a/web/app/assets/stylesheets/client/toggle.scss b/web/app/assets/stylesheets/client/toggle.scss new file mode 100644 index 000000000..3d355f80b --- /dev/null +++ b/web/app/assets/stylesheets/client/toggle.scss @@ -0,0 +1,59 @@ +@import "client/common.scss";@charset "UTF-8"; + +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 24px; +} + +/* Hide default HTML checkbox */ +.switch input {display:none;} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 16px; + width: 25px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: $ColorScreenPrimary; +} + +input:focus + .slider { + box-shadow: 0 0 1px $ColorScreenPrimary; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} diff --git a/web/app/views/api_lesson_bookings/show.rabl b/web/app/views/api_lesson_bookings/show.rabl index 5c3aecb2c..3ec3a3765 100644 --- a/web/app/views/api_lesson_bookings/show.rabl +++ b/web/app/views/api_lesson_bookings/show.rabl @@ -1,6 +1,6 @@ object @lesson_booking -attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message, :booked_price, :card_presumed_ok, :no_slots, :posa_card_id, :posa_card_purchased, :remaining_roll_forward_amount_in_cents +attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message, :booked_price, :card_presumed_ok, :no_slots, :posa_card_id, :posa_card_purchased, :remaining_roll_forward_amount_in_cents, :canceled_by_admin child(:lesson_booking_slots => :slots) { attributes :id, :preferred_day, :day_of_week, :hour, :minute, :slot_type, :pretty_scheduled_start, :message, :pretty_start_time, :proposer_id, :is_student_created?, :is_teacher_created?, :timezone, :pretty_timezone, :from_package diff --git a/web/app/views/api_lesson_sessions/show.rabl b/web/app/views/api_lesson_sessions/show.rabl index 7fe33e8e0..04598f623 100644 --- a/web/app/views/api_lesson_sessions/show.rabl +++ b/web/app/views/api_lesson_sessions/show.rabl @@ -4,7 +4,7 @@ object @lesson_session :status, :student_canceled, :teacher_canceled, :student_canceled_at, :teacher_canceled_at, :student_canceled_reason, :teacher_canceled_reason, :status, :success, :teacher_unread_messages, :student_unread_messages, :is_active?, :recurring, :analysed, :school_on_school?, :no_school_on_school_payment?, :payment_if_school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled, - :best_display_time, :remaining_roll_forward_amount_in_cents + :best_display_time, :remaining_roll_forward_amount_in_cents, :canceled_by_admin node do |lesson_session| { diff --git a/web/app/views/api_teachers/detail.rabl b/web/app/views/api_teachers/detail.rabl index 3f0ca2f6e..eaddfa965 100644 --- a/web/app/views/api_teachers/detail.rabl +++ b/web/app/views/api_teachers/detail.rabl @@ -37,7 +37,8 @@ attributes :id, :errors, :profile_pct, :school_id, - :background_check_at + :background_check_at, + :is_searchable child :review_summary => :review_summary do diff --git a/web/app/views/clients/_profile.html.erb b/web/app/views/clients/_profile.html.erb index f77a39786..90c1cefcd 100644 --- a/web/app/views/clients/_profile.html.erb +++ b/web/app/views/clients/_profile.html.erb @@ -66,6 +66,19 @@
+ +
+
Availability Settings
+
+ Let new students find me by search: + +
+
+
+
Bio (edit)