Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# typed: false
# typed: strict
# frozen_string_literal: true

class ApplicationController < ActionController::Base
extend T::Sig
include SessionsHelper
include ImageProcessable

Expand Down Expand Up @@ -49,32 +50,38 @@ class ApplicationController < ActionController::Base

private

sig { returns(T::Boolean) }
def skip_authentication?
false
end

sig { params(table: Symbol, key: Symbol, args: T.untyped).returns(String) }
def app_i18n(table, key, **args)
I18n.t("application.#{table}.#{key}", **args)
end

sig { params(form: Symbol, key: T.any(Symbol, String), args: T.untyped).returns(String) }
def form_i18n(form, key, **args)
I18n.t("forms.#{form}.#{key}", **args)
end

sig { void }
def require_login
return if logged_in?

flash[:alert] = form_i18n(:session_new, "status.login_required")
flash[:alert] = form_i18n(:session_new, :"status.login_required")
redirect_to login_path
end

sig { void }
def require_logged_out
return unless logged_in?

flash[:alert] = form_i18n(:session_new, "status.already_logged_in")
flash[:alert] = form_i18n(:session_new, :"status.already_logged_in")
redirect_to inspections_path
end

sig { void }
def update_last_active_at
return unless current_user.is_a?(User)

Expand All @@ -86,26 +93,31 @@ def update_last_active_at
end
end

sig { void }
def require_admin
return if current_user&.admin?

flash[:alert] = I18n.t("forms.session_new.status.admin_required")
redirect_to root_path
end

sig { returns(T::Boolean) }
def admin_debug_enabled?
Rails.env.development?
end

sig { returns(T::Boolean) }
def seed_data_action?
seed_actions = %w[add_seeds delete_seeds]
controller_name == "users" && seed_actions.include?(action_name)
end

sig { returns(T::Boolean) }
def impersonating?
session[:original_admin_id].present?
end

sig { void }
def start_debug_timer
@debug_start_time = Time.current
@debug_sql_queries = []
Expand All @@ -130,23 +142,27 @@ def start_debug_timer
:debug_render_time,
:debug_sql_queries

sig { returns(T.nilable(Float)) }
def debug_render_time
return unless @debug_start_time

((Time.current - @debug_start_time) * 1000).round(2)
end

sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def debug_sql_queries
@debug_sql_queries || []
end

sig { void }
def n_plus_one_detection
Prosopite.scan
yield
ensure
Prosopite.finish
end

sig { returns(T::Boolean) }
def processing_image_upload?
case controller_name
when "users"
Expand All @@ -159,13 +175,15 @@ def processing_image_upload?
end
end

sig { void }
def cleanup_debug_subscription
return unless @debug_subscription

ActiveSupport::Notifications.unsubscribe(@debug_subscription)
@debug_subscription = nil
end

sig { params(exception: StandardError).returns(T::Boolean) }
def should_notify_error?(exception)
if exception.is_a?(ActionController::InvalidAuthenticityToken)
csrf_ignored_actions = [
Expand All @@ -184,6 +202,7 @@ def should_notify_error?(exception)
true
end

sig { params(result: T.untyped, filename: String).void }
def handle_pdf_response(result, filename)
case result.type
when :redirect
Expand Down
39 changes: 29 additions & 10 deletions app/services/seed_data_service.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# typed: false
# typed: strict

class SeedDataService
CASTLE_IMAGE_COUNT = 5
UNIT_COUNT = 20
INSPECTION_COUNT = 5
INSPECTION_INTERVAL_DAYS = 364
INSPECTION_OFFSET_RANGE = 0..365
INSPECTION_DURATION_RANGE = 1..4
HIGH_PASS_RATE = 0.95
NORMAL_PASS_RATE = 0.85
extend T::Sig
CASTLE_IMAGE_COUNT = T.let(5, Integer)
UNIT_COUNT = T.let(20, Integer)
INSPECTION_COUNT = T.let(5, Integer)
INSPECTION_INTERVAL_DAYS = T.let(364, Integer)
INSPECTION_OFFSET_RANGE = T.let(0..365, T::Range[Integer])
INSPECTION_DURATION_RANGE = T.let(1..4, T::Range[Integer])
HIGH_PASS_RATE = T.let(0.95, Float)
NORMAL_PASS_RATE = T.let(0.85, Float)

# Stefan-variant owner names as per existing seeds
STEFAN_OWNER_NAMES = [
Expand All @@ -24,6 +25,9 @@ class SeedDataService
].freeze

class << self
extend T::Sig

sig { params(user: User, unit_count: Integer, inspection_count: Integer).returns(T::Boolean) }
def add_seeds_for_user(user, unit_count: UNIT_COUNT, inspection_count: INSPECTION_COUNT)
raise "User already has seed data" if user.has_seed_data?

Expand All @@ -37,6 +41,7 @@ def add_seeds_for_user(user, unit_count: UNIT_COUNT, inspection_count: INSPECTIO
true
end

sig { params(user: User).returns(T::Boolean) }
def delete_seeds_for_user(user)
ActiveRecord::Base.transaction do
# Delete inspections first (due to foreign key constraints)
Expand All @@ -49,8 +54,9 @@ def delete_seeds_for_user(user)

private

sig { void }
def ensure_castle_blobs_exist
@castle_images = []
@castle_images = T.let([], T::Array[T::Hash[Symbol, T.untyped]])

(1..CASTLE_IMAGE_COUNT).each do |i|
filename = "castle-#{i}.jpg"
Expand All @@ -69,6 +75,7 @@ def ensure_castle_blobs_exist
Rails.logger.warn I18n.t("seed_data.logging.no_castle_images") if @castle_images.empty?
end

sig { params(user: User, unit_count: Integer, inspection_count: Integer).void }
def create_seed_units_for_user(user, unit_count, inspection_count)
# Mix of unit types similar to existing seeds
unit_configs = [
Expand Down Expand Up @@ -97,6 +104,7 @@ def create_seed_units_for_user(user, unit_count, inspection_count)
end
end

sig { params(user: User, count: Integer).returns(T::Array[String]) }
def generate_unit_ids_batch(user, count)
ids = []
existing_ids = user.units.pluck(:id).to_set
Expand All @@ -115,6 +123,7 @@ def generate_unit_ids_batch(user, count)
ids
end

sig { params(user: User, config: T::Hash[Symbol, T.untyped], index: Integer, unit_id: String, existing_ids: T::Set[String]).returns(Unit) }
def create_unit_from_config(user, config, index, unit_id, existing_ids)
unit = user.units.build(
id: unit_id,
Expand Down Expand Up @@ -142,6 +151,7 @@ def create_unit_from_config(user, config, index, unit_id, existing_ids)
unit
end

sig { params(name: String).returns(String) }
def generate_description(name)
case name
when /Castle/
Expand All @@ -161,6 +171,7 @@ def generate_description(name)
end
end

sig { params(unit: Unit, user: User, config: T::Hash[Symbol, T.untyped], inspection_count: Integer, has_incomplete_recent: T::Boolean).void }
def create_inspections_for_unit(unit, user, config, inspection_count, has_incomplete_recent: false)
offset_days = rand(INSPECTION_OFFSET_RANGE)

Expand All @@ -169,6 +180,7 @@ def create_inspections_for_unit(unit, user, config, inspection_count, has_incomp
end
end

sig { params(unit: Unit, user: User, config: T::Hash[Symbol, T.untyped], offset_days: Integer, index: Integer, has_incomplete_recent: T::Boolean).void }
def create_single_inspection(unit, user, config, offset_days, index, has_incomplete_recent)
inspection_date = calculate_inspection_date(offset_days, index)
passed = determine_pass_status(index)
Expand All @@ -181,15 +193,18 @@ def create_single_inspection(unit, user, config, offset_days, index, has_incompl
create_assessments_for_inspection(inspection, unit, config, passed: passed)
end

sig { params(offset_days: Integer, index: Integer).returns(Date) }
def calculate_inspection_date(offset_days, index)
days_ago = offset_days + (index * INSPECTION_INTERVAL_DAYS)
Date.current - days_ago.days
end

sig { params(index: Integer).returns(T::Boolean) }
def determine_pass_status(index)
(index == 0) ? (rand < HIGH_PASS_RATE) : (rand < NORMAL_PASS_RATE)
end

sig { params(unit: Unit, user: User, config: T::Hash[Symbol, T.untyped], inspection_date: Date, passed: T::Boolean, is_complete: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
def build_inspection_attributes(unit, user, config, inspection_date, passed, is_complete)
{
unit: unit,
Expand All @@ -211,6 +226,7 @@ def build_inspection_attributes(unit, user, config, inspection_date, passed, is_
}
end

sig { params(passed: T::Boolean).returns(String) }
def generate_risk_assessment(passed)
if passed
[
Expand All @@ -236,6 +252,7 @@ def generate_risk_assessment(passed)
end
end

sig { params(inspection: Inspection, unit: Unit, config: T::Hash[Symbol, T.untyped], passed: T::Boolean).void }
def create_assessments_for_inspection(inspection, unit, config, passed: true)
is_incomplete = inspection.complete_date.nil?

Expand All @@ -252,6 +269,7 @@ def create_assessments_for_inspection(inspection, unit, config, passed: true)
end
end

sig { params(inspection: Inspection, assessment_key: Symbol, assessment_type: String, passed: T::Boolean, is_incomplete: T::Boolean).void }
def create_assessment(
inspection,
assessment_key,
Expand All @@ -270,6 +288,7 @@ def create_assessment(
inspection.send(assessment_key).update!(fields)
end

sig { params(fields: T::Hash[Symbol, T.untyped], is_incomplete: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
def randomly_remove_fields(fields, is_incomplete)
return fields unless is_incomplete
return fields unless rand(0..1) == 0 # empty 50% of assessments
Expand Down
Loading