Managing loaner chromebooks for students and teachers in the HUUSD school district.

full statsd

+487 -317
+28 -19
app/controllers/authentications_controller.rb
··· 4 4 before_action :ensure_not_authenticated, only: [:new, :create] 5 5 6 6 def new 7 - @user = User.new 8 7 StatsD.increment("login_page_viewed") 9 8 10 - if session[:user_id] 11 - StatsD.increment("already_logged_in") 12 - redirect_to overview_path 13 - else 14 - StatsD.increment("login_page_viewed") 15 - render "new" 9 + # Measure the time taken to process the rendering of the new page 10 + StatsD.measure('view.render_login_page') do 11 + if session[:user_id] 12 + StatsD.increment("already_logged_in") 13 + redirect_to overview_path 14 + else 15 + render "new" 16 + end 16 17 end 17 18 end 18 19 19 20 def create 20 - @user = User.find_by(email: auth_params[:email]) 21 21 StatsD.increment("login_attempt") 22 22 23 - if @user&.authenticate(auth_params[:password]) 24 - StatsD.increment("login_successful") 25 - session[:user_id] = @user.id 26 - redirect_to overview_path 27 - else 28 - StatsD.increment("login_failed") 29 - flash[:danger] = "Login failed. Please try again." 30 - render "new" 23 + # Measure the time taken for the authentication process 24 + StatsD.measure('auth.authenticate_user') do 25 + @user = User.find_by(email: auth_params[:email]) 26 + if @user&.authenticate(auth_params[:password]) 27 + StatsD.increment("login_successful") 28 + session[:user_id] = @user.id 29 + StatsD.set('users.unique_logged_in', @user.id) # Track unique users logging in 30 + redirect_to overview_path 31 + else 32 + StatsD.increment("login_failed") 33 + flash[:danger] = "Login failed. Please try again." 34 + render "new" 35 + end 31 36 end 32 37 end 33 38 34 39 def destroy 35 - session[:user_id] = nil 36 - StatsD.increment("logout") 37 - redirect_to login_path 40 + # Measure the time taken to process the logout action 41 + StatsD.measure('auth.process_logout') do 42 + StatsD.increment("logout") 43 + session[:user_id] = nil 44 + StatsD.event('User Logged Out', "User with ID #{session[:user_id]} logged out") # Log an event for logout 45 + redirect_to login_path 46 + end 38 47 end 39 48 40 49 private
+21 -8
app/controllers/borrowers_controller.rb
··· 3 3 4 4 before_action :ensure_authenticated 5 5 6 - def index 7 - @borrowers = Borrower.all 8 - StatsD.increment("borrowers_index_viewed") 9 - end 6 + def index 7 + # Measure the time taken to retrieve and display all borrowers 8 + StatsD.measure('borrowers.index_request') do 9 + @borrowers = Borrower.all 10 + 11 + # Example gauge for tracking the number of borrowers 12 + StatsD.gauge('borrowers.count', @borrowers.count) 13 + 14 + StatsD.increment("borrowers_index_viewed") 15 + end 16 + end 17 + 18 + def show 19 + # Measure the time taken to find and display a specific borrower 20 + StatsD.measure('borrowers.show_request') do 21 + @borrower = Borrower.find(params[:id]) 10 22 11 - def show 12 - @borrower = Borrower.find(params[:id]) 13 - StatsD.increment("borrower_page_viewed") 14 - end 23 + # Log an event when a specific borrower page is viewed 24 + StatsD.event('Borrower Page Viewed', "User viewed borrower page with ID #{params[:id]}") 15 25 26 + StatsD.increment("borrower_page_viewed") 27 + end 28 + end 16 29 end
+24 -15
app/controllers/concerns/authenticatable.rb
··· 20 20 end 21 21 22 22 def ensure_authenticated 23 - unless is_authenticated? 24 - flash[:warning] = "You need to login to view that page." 25 - redirect_to main_app.login_path 23 + StatsD.measure('auth.ensure_authenticated') do 24 + unless is_authenticated? 25 + flash[:warning] = "You need to login to view that page." 26 + StatsD.event('Authentication Failure', 'User not authenticated, redirecting to login') 27 + redirect_to main_app.login_path 28 + end 26 29 end 27 30 end 28 31 29 32 def ensure_not_authenticated 30 - if is_authenticated? 31 - flash[:info] = "You are already logged in." 32 - redirect_to root_path 33 + StatsD.measure('auth.ensure_not_authenticated') do 34 + if is_authenticated? 35 + flash[:info] = "You are already logged in." 36 + StatsD.event('Already Authenticated', 'User already logged in, redirecting to root') 37 + redirect_to root_path 38 + end 33 39 end 34 40 end 35 41 36 - 37 42 def ensure_admin 38 - unless current_user&.admin? 39 - flash[:danger] = "You do not have permission to view that page." 40 - redirect_to root_path 43 + StatsD.measure('auth.ensure_admin') do 44 + unless current_user&.admin? 45 + flash[:danger] = "You do not have permission to view that page." 46 + StatsD.event('Admin Access Denied', 'Non-admin user attempted to access admin page') 47 + redirect_to root_path 48 + end 41 49 end 42 50 end 43 51 44 52 def ensure_super_admin 45 - unless current_user&.super_admin? 46 - flash[:danger] = "You do not have permission to view that page." 47 - redirect_to root_path 53 + StatsD.measure('auth.ensure_super_admin') do 54 + unless current_user&.super_admin? 55 + flash[:danger] = "You do not have permission to view that page." 56 + StatsD.event('Super Admin Access Denied', 'Non-super-admin user attempted to access super admin page') 57 + redirect_to root_path 58 + end 48 59 end 49 60 end 50 - 51 - 52 61 end
+91 -73
app/controllers/loaners_controller.rb
··· 3 3 4 4 before_action :set_loaner, only: [:show, :return, :enable, :disable, :repair, :broken] 5 5 before_action :ensure_authenticated, only: [:show, :list, :return, :enable, :disable, :repair, :broken, :new, :create] 6 - before_action :ensure_super_admin, only: [:new, :create] 6 + before_action :ensure_super_admin, only: [:new, :create] 7 7 8 - def new 9 - @loaner = Loaner.new 10 - StatsD.increment("loaner_new_viewed") 11 - end 8 + def new 9 + StatsD.increment("loaner_new_viewed") 12 10 13 - def create 14 - @loaner = Loaner.new(loaner_params) 15 - StatsD.increment("loaner_create_attempt") 16 - 17 - if @loaner.save 18 - redirect_to @loaner, notice: 'Loaner was successfully created.' 19 - StatsD.increment("loaner_created") 20 - else 21 - StatsD.increment("loaner_create_failed") 22 - render :new 11 + # Measure the time taken to render the 'new' loaner form 12 + StatsD.measure('view.render_new_loaner') do 13 + @loaner = Loaner.new 14 + end 23 15 end 24 - end 25 16 17 + def create 18 + StatsD.increment("loaner_create_attempt") 26 19 27 - def list 28 - @loaners = Loaner.all 29 - StatsD.increment("loaners_list_viewed") 30 - end 20 + # Measure the time taken to create a new loaner 21 + StatsD.measure('loaner.create') do 22 + @loaner = Loaner.new(loaner_params) 23 + if @loaner.save 24 + StatsD.increment("loaner_created") 25 + redirect_to @loaner, notice: 'Loaner was successfully created.' 26 + else 27 + StatsD.increment("loaner_create_failed") 28 + render :new 29 + end 30 + end 31 + end 31 32 32 - def show 33 - @loaner 34 - StatsD.increment("loaner_page_viewed") 35 - end 33 + def list 34 + # Measure the time taken to retrieve and display all loaners 35 + StatsD.measure('loaners.list_request') do 36 + @loaners = Loaner.all 37 + StatsD.increment("loaners_list_viewed") 38 + end 39 + end 40 + 41 + def show 42 + StatsD.increment("loaner_page_viewed") 43 + # Measure the time taken to find and display the loaner 44 + StatsD.measure('loaner.show_request') do 45 + @loaner 46 + end 47 + end 36 48 37 - # GET /loaners/:id/return 38 49 def return 39 50 StatsD.increment("loaner_return_attempt") 40 - if @loaner.present? 41 - @loaner.mark_as_returned 42 - StatsD.increment("loaner_returned") 43 - redirect_to loaners_path, notice: "Asset checked in successfully." 44 - else 45 - StatsD.increment("loaner_return_failed") 46 - redirect_to loaners_path, alert: "Loaner not found." 51 + StatsD.measure('loaner.return_action') do 52 + if @loaner.present? 53 + @loaner.mark_as_returned 54 + StatsD.increment("loaner_returned") 55 + redirect_to loaners_path, notice: "Asset checked in successfully." 56 + else 57 + StatsD.increment("loaner_return_failed") 58 + redirect_to loaners_path, alert: "Loaner not found." 59 + end 47 60 end 48 61 end 49 62 50 - # GET /loaners/:id/enable 51 63 def enable 52 64 StatsD.increment("loaner_enable_attempt") 53 - if @loaner.present? 54 - @loaner.mark_as_enabled 55 - StatsD.increment("loaner_enabled") 56 - redirect_to loaners_path, notice: "Asset marked as enabled successfully." 57 - else 58 - StatsD.increment("loaner_enable_failed") 59 - redirect_to loaners_path, alert: "Loaner not found." 65 + StatsD.measure('loaner.enable_action') do 66 + if @loaner.present? 67 + @loaner.mark_as_enabled 68 + StatsD.increment("loaner_enabled") 69 + redirect_to loaners_path, notice: "Asset marked as enabled successfully." 70 + else 71 + StatsD.increment("loaner_enable_failed") 72 + redirect_to loaners_path, alert: "Loaner not found." 73 + end 60 74 end 61 75 end 62 76 63 - # GET /loaners/:id/disable 64 77 def disable 65 78 StatsD.increment("loaner_disable_attempt") 66 - if @loaner.present? 67 - @loaner.mark_as_disabled 68 - StatsD.increment("loaner_disabled") 69 - redirect_to loaners_path, notice: "Asset marked as disabled successfully." 70 - else 71 - StatsD.increment("loaner_disable_failed") 72 - redirect_to loaners_path, alert: "Loaner not found." 79 + StatsD.measure('loaner.disable_action') do 80 + if @loaner.present? 81 + @loaner.mark_as_disabled 82 + StatsD.increment("loaner_disabled") 83 + redirect_to loaners_path, notice: "Asset marked as disabled successfully." 84 + else 85 + StatsD.increment("loaner_disable_failed") 86 + redirect_to loaners_path, alert: "Loaner not found." 87 + end 73 88 end 74 89 end 75 90 76 91 def broken 77 92 StatsD.increment("loaner_broken_attempt") 78 - if @loaner.present? 79 - @loaner.mark_as_broken 80 - StatsD.increment("loaner_broken") 81 - redirect_to loaners_path, notice: "Asset marked as broken successfully." 82 - else 83 - StatsD.increment("loaner_broken_failed") 84 - redirect_to loaners_path, alert: "Loaner not found." 93 + StatsD.measure('loaner.broken_action') do 94 + if @loaner.present? 95 + @loaner.mark_as_broken 96 + StatsD.increment("loaner_broken") 97 + redirect_to loaners_path, notice: "Asset marked as broken successfully." 98 + else 99 + StatsD.increment("loaner_broken_failed") 100 + redirect_to loaners_path, alert: "Loaner not found." 101 + end 85 102 end 86 103 end 87 104 88 - # GET /loaners/:id/repair 89 105 def repair 90 106 StatsD.increment("loaner_repair_attempt") 91 - if @loaner.present? 92 - @loaner.mark_as_repaired 93 - StatsD.increment("loaner_repaired") 94 - redirect_to loaners_path, notice: "Asset marked as repaired successfully." 95 - else 96 - StatsD.increment("loaner_repair_failed") 97 - redirect_to loaners_path, alert: "Loaner not found." 107 + StatsD.measure('loaner.repair_action') do 108 + if @loaner.present? 109 + @loaner.mark_as_repaired 110 + StatsD.increment("loaner_repaired") 111 + redirect_to loaners_path, notice: "Asset marked as repaired successfully." 112 + else 113 + StatsD.increment("loaner_repair_failed") 114 + redirect_to loaners_path, alert: "Loaner not found." 115 + end 98 116 end 99 117 end 100 118 101 - 102 - 103 - private 104 - # Use callbacks to share common setup or constraints between actions. 105 - def set_loaner 106 - @loaner = Loaner.find(params[:id]) 107 - end 119 + private 108 120 109 - def loaner_params 110 - params.require(:loaner).permit(:asset_tag, :serial_number, :status) 111 - end 121 + # Use callbacks to share common setup or constraints between actions. 122 + def set_loaner 123 + StatsD.measure('loaner.find') do 124 + @loaner = Loaner.find(params[:id]) 125 + end 126 + end 112 127 128 + def loaner_params 129 + params.require(:loaner).permit(:asset_tag, :serial_number, :status) 130 + end 113 131 end
+83 -70
app/controllers/loans_controller.rb
··· 4 4 before_action :ensure_authenticated, only: [:create, :list, :pending, :out, :checkout, :checkin] 5 5 6 6 def new 7 - @loan = Loan.new 8 7 StatsD.increment("loan_new_viewed") 8 + 9 + # Measure the time taken to render the 'new' loan form 10 + StatsD.measure('view.render_new_loan') do 11 + @loan = Loan.new 12 + end 9 13 end 10 14 11 15 def create 12 16 borrower_email = loan_params[:borrower_email] 13 17 StatsD.increment("loan_create_attempt") 14 18 15 - # Find or create a Borrower based on the email 16 - @borrower = Borrower.find_or_create_by(email: borrower_email) 19 + # Measure the time taken to find or create a borrower and save a loan 20 + StatsD.measure('loan.create_process') do 21 + @borrower = Borrower.find_or_create_by(email: borrower_email) 22 + @loan = @borrower.loans.build(loan_params.except(:borrower_email)) 17 23 18 - # Build a new Loan associated with the found or created Borrower 19 - @loan = @borrower.loans.build(loan_params.except(:borrower_email)) 20 - 21 - if @loan.save 22 - StatsD.increment("loan_created") 23 - flash[:success] = "Submitted Successfully!" 24 - redirect_to loans_path 25 - else 26 - StatsD.increment("loan_create_failed") 27 - flash.now[:danger] = "FAILED TO SUBMIT! Please check with a tech." 28 - render :new 24 + if @loan.save 25 + StatsD.increment("loan_created") 26 + flash[:success] = "Submitted Successfully!" 27 + redirect_to loans_path 28 + else 29 + StatsD.increment("loan_create_failed") 30 + flash.now[:danger] = "FAILED TO SUBMIT! Please check with a tech." 31 + render :new 32 + end 29 33 end 30 34 end 31 35 32 36 def list 33 - @loans = Loan.all.order(created_at: :desc) 34 - StatsD.increment("loans_list_viewed") 37 + StatsD.measure('loan.list_request') do 38 + @loans = Loan.all.order(created_at: :desc) 39 + StatsD.increment("loans_list_viewed") 40 + end 35 41 end 36 42 37 43 def pending 38 - @loans = Loan.where(status: "pending").order(created_at: :desc) 39 - StatsD.increment("loans_pending_viewed") 44 + StatsD.measure('loan.pending_request') do 45 + @loans = Loan.where(status: "pending").order(created_at: :desc) 46 + StatsD.increment("loans_pending_viewed") 47 + end 40 48 end 41 49 42 50 def out 43 - @loans = Loan.where(status: "out").order(created_at: :desc) 44 - StatsD.increment("loans_out_viewed") 51 + StatsD.measure('loan.out_request') do 52 + @loans = Loan.where(status: "out").order(created_at: :desc) 53 + StatsD.increment("loans_out_viewed") 54 + end 45 55 end 46 56 47 57 def checkout 48 - @loan = Loan.find(params[:id]) 49 - loaner = Loaner.find_by(asset_tag: params[:asset_tag]) 50 - 51 58 StatsD.increment("loan_checkout_attempt") 52 59 53 - if loaner.nil? 54 - StatsD.increment("loan_checkout_failed") 55 - flash[:danger] = "Loaner not found." 56 - redirect_to overview_path and return 57 - end 60 + # Measure the time taken to process the checkout action 61 + StatsD.measure('loan.checkout_process') do 62 + @loan = Loan.find(params[:id]) 63 + loaner = Loaner.find_by(asset_tag: params[:asset_tag]) 58 64 59 - unless loaner.available? 60 - StatsD.increment("loan_checkout_failed") 61 - flash[:danger] = "Loaner is not available." 62 - redirect_to overview_path and return 63 - end 65 + if loaner.nil? 66 + StatsD.increment("loan_checkout_failed") 67 + flash[:danger] = "Loaner not found." 68 + redirect_to overview_path and return 69 + end 70 + 71 + unless loaner.available? 72 + StatsD.increment("loan_checkout_failed") 73 + flash[:danger] = "Loaner is not available." 74 + redirect_to overview_path and return 75 + end 76 + 77 + begin 78 + loaner.loan! 79 + @loan.update!(loaner_id: loaner.id) 80 + @loan.loan! 81 + StatsD.increment("loan_checked_out") 82 + flash[:success] = "Loan successful." 83 + rescue AASM::InvalidTransition => e 84 + StatsD.increment("loan_checkout_failed") 85 + flash[:danger] = "Loan transition failed: #{e.message}" 86 + end 64 87 65 - begin 66 - loaner.loan! 67 - @loan.update!(loaner_id: loaner.id) 68 - @loan.loan! 69 - StatsD.increment("loan_checked_out") 70 - flash[:success] = "Loan successful." 71 - rescue AASM::InvalidTransition => e 72 - StatsD.increment("loan_checkout_failed") 73 - flash[:danger] = "Loan transition failed: #{e.message}" 88 + redirect_to overview_path 74 89 end 75 - 76 - redirect_to overview_path 77 90 end 78 91 79 92 def checkin 80 - @loan = Loan.find(params[:id]) 81 - loaner = @loan.loaner 82 - 83 93 StatsD.increment("loan_checkin_attempt") 84 94 85 - if loaner.nil? 86 - flash[:danger] = "Loaner not found." 87 - StatsD.increment("loan_checkin_failed") 88 - redirect_to overview_path 89 - return 90 - end 95 + # Measure the time taken to process the check-in action 96 + StatsD.measure('loan.checkin_process') do 97 + @loan = Loan.find(params[:id]) 98 + loaner = @loan.loaner 91 99 92 - if loaner.available? 93 - flash[:danger] = "Loaner is already available." 94 - StatsD.increment("loan_checkin_failed") 95 - redirect_to overview_path 96 - return 97 - else 98 - loaner.return! 99 - @loan.return! 100 + if loaner.nil? 101 + StatsD.increment("loan_checkin_failed") 102 + flash[:danger] = "Loaner not found." 103 + redirect_to overview_path 104 + return 105 + end 100 106 101 - StatsD.increment("loan_checked_in") 102 - flash[:success] = "Check-in successful." 103 - redirect_to overview_path 107 + if loaner.available? 108 + StatsD.increment("loan_checkin_failed") 109 + flash[:danger] = "Loaner is already available." 110 + redirect_to overview_path 111 + return 112 + else 113 + loaner.return! 114 + @loan.return! 115 + StatsD.increment("loan_checked_in") 116 + flash[:success] = "Check-in successful." 117 + redirect_to overview_path 118 + end 104 119 end 105 120 end 106 121 122 + private 107 123 108 - private 109 - 110 - def loan_params 111 - params.require(:loan).permit(:reason, :borrower_email) 112 - end 113 - 124 + def loan_params 125 + params.require(:loan).permit(:reason, :borrower_email) 126 + end 114 127 end
+29 -27
app/controllers/main_controller.rb
··· 10 10 end 11 11 12 12 def overview 13 - @loans = Loan.all 14 - @loaners = Loaner.all 15 - @borrowers = Borrower.all 16 - @staff = User.all 17 - @pending_loans = Loan.where(status: 'pending') 18 - @loaners_out = Loaner.includes(:current_loan).where(status: 'loaned') 13 + StatsD.measure('overview.load_time') do 14 + @loans = Loan.all 15 + @loaners = Loaner.all 16 + @borrowers = Borrower.all 17 + @staff = User.all 18 + @pending_loans = Loan.where(status: 'pending') 19 + @loaners_out = Loaner.includes(:current_loan).where(status: 'loaned') 19 20 20 - StatsD.increment("overview_viewed") 21 + StatsD.increment("overview_viewed") 22 + end 21 23 end 22 24 23 25 def temp 24 26 @borrower = Borrower.find(1) 25 - BorrowerMailer.notify_repair_ready(@borrower).deliver_now 26 - BorrowerMailer.notify_loaner_disabled(@borrower).deliver_now 27 - BorrowerMailer.return_reminder(@borrower).deliver_now 28 - 29 - # StatsD.increment("temp") 27 + StatsD.increment("temp_action_triggered") 28 + StatsD.measure('temp.mail_sending') do 29 + BorrowerMailer.notify_repair_ready(@borrower).deliver_now 30 + BorrowerMailer.notify_loaner_disabled(@borrower).deliver_now 31 + BorrowerMailer.return_reminder(@borrower).deliver_now 32 + end 33 + # Consider adding an event to log this action in a more descriptive way if needed. 30 34 end 31 35 32 36 def import 33 - # This action will render the import form 34 37 StatsD.increment("import_viewed") 35 38 end 36 39 ··· 38 41 StatsD.increment("import_attempt") 39 42 if params[:csv_file].present? 40 43 csv_file = params[:csv_file].path 41 - process_csv(csv_file) 44 + StatsD.measure('csv_processing_time') do 45 + process_csv(csv_file) 46 + end 42 47 StatsD.increment("import_successful") 43 48 flash[:notice] = 'CSV file imported successfully.' 44 49 redirect_to overview_path ··· 53 58 54 59 def process_csv(file_path) 55 60 StatsD.increment("csv_processing_attempt") 56 - CSV.foreach(file_path, headers: true) do |row| 57 - puts "\n\n" 58 - puts "Processing row: #{row}" 59 - puts row.to_hash 60 - puts "\n\n" 61 - 62 - # Loaner.find_or_create_by!(asset_tag: row['asset_tag'], ) do |loaner| 63 - # loaner.serial_number = row['serial_number'] 64 - # loaner.status = row['status'] 65 - # end 61 + row_count = 0 62 + StatsD.measure('csv_processing_time') do 63 + CSV.foreach(file_path, headers: true) do |row| 64 + row_count += 1 66 65 67 - Loaner.new(asset_tag: row['asset_tag'], serial_number: row['serial_number'], loaner_id: 100).save! 68 - StatsD.increment("csv_row_processed") 66 + # Create a new loaner (this needs to be adjusted based on your actual model attributes) 67 + Loaner.create!(asset_tag: row['asset_tag'], serial_number: row['serial_number'], loaner_id: 100) 68 + StatsD.increment("csv_row_processed") 69 + end 69 70 end 71 + # Optionally set a gauge to reflect the number of rows processed 72 + StatsD.gauge('csv_row_count', row_count) 70 73 end 71 - 72 74 end
+40 -33
app/mailers/borrower_mailer.rb
··· 1 1 class BorrowerMailer < ApplicationMailer 2 + default from: 'ithelper@jaspermayone.com' 2 3 3 - default from: 'ithelper@jaspermayone.com' 4 + def notify_repair_ready(borrower) 5 + StatsD.increment("email.notify_repair_ready_sent") 6 + StatsD.measure('email.notify_repair_ready_delivery_time') do 7 + @borrower = borrower 8 + mail( 9 + to: @borrower.email, 10 + subject: 'Your device has been repaired.', 11 + track_opens: "true", 12 + track_clicks: "true", 13 + message_stream: "outbound" 14 + ) 15 + end 16 + end 4 17 5 - def notify_repair_ready(borrower) 6 - @borrower = borrower 7 - mail( 8 - to: @borrower.email, 9 - subject: 'Your device has been repaired.', 10 - track_opens: "true", 11 - track_clicks: "true", 12 - message_stream: "outbound" 13 - ) 14 - end 18 + def notify_loaner_disabled(borrower) 19 + StatsD.increment("email.notify_loaner_disabled_sent") 20 + StatsD.measure('email.notify_loaner_disabled_delivery_time') do 21 + @borrower = borrower 22 + mail( 23 + to: @borrower.email, 24 + subject: 'Your device has been disabled.', 25 + track_opens: "true", 26 + track_clicks: "true", 27 + message_stream: "outbound" 28 + ) 29 + end 30 + end 15 31 16 - def notify_loaner_disabled(borrower) 17 - @borrower = borrower 18 - mail( 19 - to: @borrower.email, 20 - subject: 'Your device has been disabled.', 21 - track_opens: "true", 22 - track_clicks: "true", 23 - message_stream: "outbound" 24 - ) 25 - end 26 - 27 - def return_reminder(borrower) 28 - @borrower = borrower 29 - mail( 30 - to: @borrower.email, 31 - subject: 'Please return your device.', 32 - track_opens: "true", 33 - track_clicks: "true", 34 - message_stream: "outbound" 35 - ) 36 - end 37 - 32 + def return_reminder(borrower) 33 + StatsD.increment("email.return_reminder_sent") 34 + StatsD.measure('email.return_reminder_delivery_time') do 35 + @borrower = borrower 36 + mail( 37 + to: @borrower.email, 38 + subject: 'Please return your device.', 39 + track_opens: "true", 40 + track_clicks: "true", 41 + message_stream: "outbound" 42 + ) 43 + end 44 + end 38 45 end
+26 -22
app/mailers/user_mailer.rb
··· 1 1 class UserMailer < ApplicationMailer 2 - 3 - default from: 'ithelper@jaspermayone.com' 4 - 5 - def notify_unreturned_after_seven_days(loan) 6 - @loan = loan 7 - @borrower = Borrower.find(@loan.borrower_id) 8 - mail( 9 - subject: "Student has not returned Loaner after seven days.", 10 - to: "jmayone2025@huusd.org", 11 - track_opens: "true", 12 - message_stream: "outbound" 13 - ) 14 - end 2 + default from: 'ithelper@jaspermayone.com' 15 3 16 - def hello() 17 - mail( 18 - subject: "Hello from Postmark", 19 - to: "me@jaspermayone.com", 20 - html_body: "<strong>Hello</strong> dear Postmark user.", 21 - track_opens: "true", 22 - message_stream: "outbound" 23 - ) 24 - end 4 + def notify_unreturned_after_seven_days(loan) 5 + StatsD.increment("email.notify_unreturned_after_seven_days_sent") 6 + StatsD.measure('email.notify_unreturned_after_seven_days_delivery_time') do 7 + @loan = loan 8 + @borrower = Borrower.find(@loan.borrower_id) 9 + mail( 10 + subject: "Student has not returned Loaner after seven days.", 11 + to: "jmayone2025@huusd.org", 12 + track_opens: "true", 13 + message_stream: "outbound" 14 + ) 15 + end 16 + end 25 17 18 + def hello 19 + StatsD.increment("email.hello_sent") 20 + StatsD.measure('email.hello_delivery_time') do 21 + mail( 22 + subject: "Hello from Postmark", 23 + to: "me@jaspermayone.com", 24 + html_body: "<strong>Hello</strong> dear Postmark user.", 25 + track_opens: "true", 26 + message_stream: "outbound" 27 + ) 28 + end 29 + end 26 30 end
+18
app/models/authentication.rb
··· 9 9 class Authentication < ApplicationRecord 10 10 # frozen_string_literal: true 11 11 12 + # Track creation of an Authentication instance 13 + after_create :track_creation 14 + 15 + # Track destruction of an Authentication instance 16 + after_destroy :track_destruction 17 + 12 18 def initialize(session, user) 19 + StatsD.increment("authentication.initialized") 13 20 session[:current_authentication] = self 14 21 @user_id = user.id 15 22 end 16 23 17 24 def destroy(session) 25 + StatsD.increment("authentication.destroyed") 18 26 session[:current_authentication] = nil 19 27 @user_id = nil 20 28 end 21 29 22 30 def user 31 + StatsD.increment("authentication.user_lookup") 23 32 User.find_by(id: @user_id) 24 33 end 25 34 26 35 def user=(user) 36 + StatsD.increment("authentication.user_set") 27 37 @user_id = user.id 28 38 end 29 39 40 + private 30 41 42 + def track_creation 43 + StatsD.increment("authentication.created") 44 + end 45 + 46 + def track_destruction 47 + StatsD.increment("authentication.deleted") 48 + end 31 49 end
+45 -21
app/models/borrower.rb
··· 12 12 # 13 13 class StudentEmailValidator < ActiveModel::Validator 14 14 def validate(record) 15 - unless record.email =~ /^[a-zA-Z][a-zA-Z]+[0-9]{4}@huusd\.org$/ 15 + # Increment a counter for validation attempts 16 + StatsD.increment("email_validation_attempts") 17 + 18 + if record.email.blank? 19 + # Log and increment counter for blank email errors 20 + StatsD.increment("email_validation_blank_errors") 21 + record.errors.add(:email, "can't be blank") 22 + elsif record.email =~ /^[a-zA-Z][a-zA-Z]+[0-9]{4}@huusd\.org$/ 23 + # Log successful validation 24 + StatsD.increment("email_validation_success") 25 + else 26 + # Log and increment counter for format errors 27 + StatsD.increment("email_validation_format_errors") 16 28 record.errors.add(:email, 'must be in the format: first initial, last name, and 4-digit graduation year (e.g., jdoe2024@huusd.org)') 17 29 end 18 30 end ··· 23 35 validates :email, presence: true, student_email: true 24 36 25 37 before_create :parse_email 38 + after_create :track_creation 39 + after_update :track_update 40 + after_destroy :track_destruction 26 41 27 42 def full_name 43 + StatsD.increment("borrower.full_name_accessed") 28 44 "#{first_name} #{last_name}" 29 45 end 30 46 31 47 def name 48 + StatsD.increment("borrower.name_accessed") 32 49 "#{full_name}" 33 50 end 34 51 35 52 def grade_level 53 + StatsD.increment("borrower.grade_level_calculated") 36 54 current_month = Time.now.month 37 - current_year = Time.now.year 38 - graduation_year = self.graduation_year 55 + current_year = Time.now.year 56 + graduation_year = self.graduation_year 39 57 40 58 if current_month <= 5 41 59 academic_year = current_year - 1 ··· 44 62 end 45 63 46 64 tmp_grade_level = academic_year - graduation_year 47 - # if tmp_grade_level.abs is = 1, then the student is in 12th grade 48 - # if tmp_grade_level.abs is = 2, then the student is in 11th grade 49 - # etc 50 - 51 - if tmp_grade_level.abs == 1 65 + case tmp_grade_level.abs 66 + when 1 52 67 grade_level = 12 53 - elsif tmp_grade_level.abs == 2 68 + when 2 54 69 grade_level = 11 55 - elsif tmp_grade_level.abs == 3 70 + when 3 56 71 grade_level = 10 57 - elsif tmp_grade_level.abs == 4 72 + when 4 58 73 grade_level = 9 59 - elsif tmp_grade_level.abs == 5 74 + when 5 60 75 grade_level = 8 61 - # add grade 7 62 76 else 63 77 grade_level = 0 # staff (should not error) 64 78 end ··· 66 80 grade_level 67 81 end 68 82 69 - 70 83 private 71 84 72 85 def parse_email 73 - 74 - # email comes in as "first initial, last name, and 4-digit graduation year @huusd.org", split out the parts 75 - # and assign them to the appropriate attributes 76 - 86 + StatsD.increment("borrower.parse_email_started") 77 87 # Split the email address before the "@" symbol 78 88 local_part = email.split('@').first 79 89 ··· 81 91 name_part, graduation_year = local_part.scan(/([a-zA-Z]+)(\d{4})/).flatten 82 92 83 93 # Assign the appropriate attributes 84 - self.first_name = name_part[0].capitalize 85 - self.last_name = name_part[1..-1].capitalize 86 - self.graduation_year = graduation_year 94 + self.first_name = name_part[0].capitalize 95 + self.last_name = name_part[1..-1].capitalize 96 + self.graduation_year = graduation_year 97 + 98 + StatsD.increment("borrower.parse_email_completed") 99 + end 100 + 101 + def track_creation 102 + StatsD.increment("borrower.created") 103 + end 104 + 105 + def track_update 106 + StatsD.increment("borrower.updated") 107 + end 108 + 109 + def track_destruction 110 + StatsD.increment("borrower.deleted") 87 111 end 88 112 end
+10 -11
app/models/loan.rb
··· 32 32 validates :reason, presence: true 33 33 34 34 aasm :column => 'status' do 35 - 36 35 state :pending, initial: true, display: "Pending" 37 36 state :out, display: "Out" 38 37 state :returned, display: "Returned" ··· 43 42 transitions from: :pending, to: :out 44 43 45 44 after do 45 + StatsD.increment("loan.loaned") 46 46 self.update(loaned_at: Time.now) 47 - # SHOWME: This is the time for the due date, confirm this with justin 48 47 self.update(due_date: Date.today + 1.day) 49 48 50 49 due_date_time = self.due_date.to_time 51 50 52 - RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + 1.day).perform_later(self.id) 53 - RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + 2.days).perform_later(self.id) 54 - RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + 3.days).perform_later(self.id) 55 - RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + 4.days).perform_later(self.id) 56 - RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + 5.days).perform_later(self.id) 57 - RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + 6.days).perform_later(self.id) 58 - RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + 7.days).perform_later(self.id) 51 + # Schedule reminder jobs 52 + (1..7).each do |day| 53 + StatsD.increment("loan.reminder_job_scheduled", tags: ["day:#{day}"]) 54 + RemindBorrowerToReturnLoanerJob.set(wait_until: due_date_time + day.days).perform_later(self.id) 55 + end 56 + StatsD.increment("loan.borrower_unreturned_after_seven_days_job_scheduled") 59 57 BorrowerUnreturnedAfterSevenDaysJob.set(wait_until: due_date_time + 8.days).perform_later(self.id) 60 58 end 61 59 end ··· 64 62 transitions from: :out, to: :returned 65 63 66 64 after do 65 + StatsD.increment("loan.returned") 67 66 self.update(returned_at: Time.now) 68 67 end 69 68 end 70 - 71 69 end 72 70 73 71 def log_status_change 74 - puts "changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})" 72 + StatsD.increment("loan.status_change", tags: ["from:#{aasm.from_state}", "to:#{aasm.to_state}", "event:#{aasm.current_event}"]) 73 + Rails.logger.info "Changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})" 75 74 end 76 75 77 76 enum reason: { charging: 1, device_repair: 2, forgot_at_home: 3 }
+48 -18
app/models/loaner.rb
··· 24 24 # 25 25 class AssetTagValidator < ActiveModel::Validator 26 26 def validate(record) 27 - unless record.asset_tag =~ /^[0-9]{6}$/ 28 - record.errors.add(:asset_tag, 'must be a 6-digit number') 27 + # Increment a counter for validation attempts 28 + StatsD.increment("asset_tag_validation_attempts") 29 + 30 + if record.asset_tag.blank? 31 + # Log and increment counter for blank asset tag errors 32 + StatsD.increment("asset_tag_validation_blank_errors") 33 + record.errors.add(:asset_tag, "can't be blank") 34 + elsif record.asset_tag =~ /^[0-9]{6}$/ 35 + # Log successful validation 36 + StatsD.increment("asset_tag_validation_success") 37 + else 38 + # Log and increment counter for format errors 39 + StatsD.increment("asset_tag_validation_format_errors") 40 + record.errors.add(:asset_tag, 'must be a 6-digit number') 41 + end 29 42 end 30 43 end 31 - end 32 44 33 45 class Loaner < ApplicationRecord 34 46 include AASM ··· 44 56 after_all_transitions :log_status_change 45 57 46 58 event :loan do 47 - # transitions from: :available, to: :loaned, guard: :pending_loan_present? 48 59 transitions from: :available, to: :loaned 49 60 50 61 after do 62 + StatsD.increment("loaner.loaned") 51 63 assign_current_loan 52 64 end 53 65 end 54 66 55 67 event :return do 56 68 transitions from: :loaned, to: :available 69 + 70 + after do 71 + StatsD.increment("loaner.returned") 72 + end 57 73 end 58 74 59 75 event :disable do 60 76 transitions from: [:available, :loaned], to: :disabled 77 + 78 + after do 79 + StatsD.increment("loaner.disabled") 80 + end 61 81 end 62 82 63 83 event :enable do 64 84 transitions from: :disabled, to: :available 85 + 86 + after do 87 + StatsD.increment("loaner.enabled") 88 + end 65 89 end 66 90 67 91 event :broken do 68 92 transitions from: [:available, :loaned], to: :maintenance 93 + 94 + after do 95 + StatsD.increment("loaner.broken") 96 + end 69 97 end 70 98 71 99 event :repair do 72 100 transitions from: :maintenance, to: :available 101 + 102 + after do 103 + StatsD.increment("loaner.repaired") 104 + end 73 105 end 74 106 end 75 107 76 108 def log_status_change 77 - puts "changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})" 78 - end 79 - 80 - # def pending_loan_present? 81 - # loans.pending.exists? 82 - # end 83 - 84 - def assign_current_loan 85 - current_loan = loans.pending.first 86 - if current_loan 87 - self.current_loan_id = current_loan.id 88 - save! 89 - current_loan.loan! 90 - end 109 + StatsD.increment("loaner.status_change", tags: ["from:#{aasm.from_state}", "to:#{aasm.to_state}", "event:#{aasm.current_event}"]) 110 + Rails.logger.info "Changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})" 91 111 end 92 112 93 113 validates :asset_tag, presence: true, uniqueness: true ··· 105 125 # Find the maximum current loaner_id 106 126 max_id = Loaner.maximum(:loaner_id) || 0 107 127 self.loaner_id = max_id + 1 128 + StatsD.increment("loaner.id_set") 108 129 end 109 130 131 + def assign_current_loan 132 + current_loan = loans.pending.first 133 + if current_loan 134 + self.current_loan_id = current_loan.id 135 + save! 136 + StatsD.increment("loaner.current_loan_assigned") 137 + current_loan.loan! 138 + end 139 + end 110 140 end
+20
app/models/user.rb
··· 21 21 super_admin: 3 22 22 } 23 23 24 + # Callbacks to track user creation and updates 25 + after_create :track_user_creation 26 + after_update :track_user_update 27 + 24 28 validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP } 29 + 30 + private 31 + 32 + def track_user_creation 33 + StatsD.increment("user.created") 34 + StatsD.gauge("user.count", User.count) 35 + Rails.logger.info "User created: #{email}" 36 + end 37 + 38 + def track_user_update 39 + StatsD.increment("user.updated") 40 + if saved_change_to_role? 41 + StatsD.increment("user.role_changed", tags: ["from:#{saved_change_to_role[0]}", "to:#{saved_change_to_role[1]}"]) 42 + end 43 + Rails.logger.info "User updated: #{email}" 44 + end 25 45 end
+4
config/routes.rb
··· 1 + # == Route Map 2 + # 3 + # D, [2024-06-29T22:50:42.483604 #96446] DEBUG -- : using default configuration 4 + 1 5 Rails.application.routes.draw do 2 6 # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 3 7