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
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.4.5
3.4.6
6 changes: 4 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

source 'https://rubygems.org'

ruby '3.4.5'
ruby '3.4.6'

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem 'rails', '~> 8.0'
Expand Down Expand Up @@ -47,7 +47,7 @@ gem 'tzinfo-data', platforms: %i[windows jruby]
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false

gem 'ostruct'
gem 'ostruct' # TODO: Remove once we migrate away from it
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
gem 'image_processing', '~> 1.2'
gem 'shadcn-ui', '~> 0.0.13'
Expand Down Expand Up @@ -94,6 +94,8 @@ gem 'logidze'
gem 'appsignal'
gem 'rack-attack'

gem 'i18n-active_record', require: 'i18n/active_record'

group :development, :test do
gem 'dotenv'
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
Expand Down
121 changes: 63 additions & 58 deletions Gemfile.lock

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base # rubocop:disable Metrics/C
before_action :set_paper_trail_whodunnit
before_action :set_actionmailer_host
before_action :check_accepted_terms, if: :user_signed_in?
before_action :set_i18n

# protect_from_forgery with: :exception
protect_from_forgery prepend: true
Expand Down Expand Up @@ -161,4 +162,13 @@ def flash_resource_not_found(_exception)
flash[:error] = "Resource not found in workspace '#{current_workspace.name}'"
redirect_to(root_path)
end

def set_i18n # rubocop:disable Metrics/AbcSize
I18n.locale = current_account&.default_locale.presence || I18n.default_locale

return if I18n::Backend::ActiveRecord.config.scope == current_account&.i18n_scope.presence

I18n::Backend::ActiveRecord.config.scope = current_account&.i18n_scope.presence
I18n.backend.reload!
end
end
16 changes: 12 additions & 4 deletions app/controllers/impact_cards_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ def impact_card_params # rubocop:disable Metrics/MethodLength

def checklist_items_to_csv(checklist_items) # rubocop:disable Metrics/MethodLength
CSV.generate(headers: true) do |csv|
csv << %w[initiative_name characteristic_name status comment]
initiative_name_field = "#{Initiative.model_name.human.downcase}_name"
csv << [initiative_name_field, 'characteristic_name', 'status', 'comment']

checklist_items.each do |item|
csv << [
Expand All @@ -259,13 +260,20 @@ def import_checklist_items_from_csv(file, scorecard) # rubocop:disable Metrics/A
errors = []
skipped_count = 0
invalid_status_errors = 0
initiative_name_column_names = ["#{Initiative.model_name.human.downcase}_name", 'initiative_name'].uniq
initiative_name_column_name = nil

# Valid status values
valid_statuses = ChecklistItem.statuses.keys - ['no_comment']

CSV.foreach(file.path, headers: true, force_quotes: true) do |row| # rubocop:disable Metrics/BlockLength
# Skip empty rows
next if row['initiative_name'].blank? || row['characteristic_name'].blank?
if initiative_name_column_name.nil?
initiative_name_column_name = row.headers.find { |header| initiative_name_column_names.include?(header) }
end

next if initiative_name_column_name.nil?
next if row[initiative_name_column_name].blank?
next if row['characteristic_name'].blank?

# Skip rows with 'no_comment' status (nothing meaningful to import)
if row['status']&.strip&.downcase == 'no_comment'
Expand All @@ -275,7 +283,7 @@ def import_checklist_items_from_csv(file, scorecard) # rubocop:disable Metrics/A

# Find the checklist item by initiative name and characteristic name within this specific scorecard
checklist_item = find_checklist_item_by_names_in_scorecard(
row['initiative_name']&.strip,
row[initiative_name_column_name]&.strip,
row['characteristic_name']&.strip,
scorecard
)
Expand Down
25 changes: 17 additions & 8 deletions app/controllers/initiatives_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def linked
private

def export_filename
"initiatives-#{Time.zone.today.strftime('%Y-%m-%d')}"
"#{Initiative.model_name.human.pluralize.downcase}-#{Time.zone.today.strftime('%Y-%m-%d')}"
end

def initiatives_to_csv(initiatives) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
Expand All @@ -152,8 +152,8 @@ def initiatives_to_csv(initiatives) # rubocop:disable Metrics/AbcSize,Metrics/Cy
csv << (([
'Name',
'Description',
'Impact Card Name',
'Impact Card Type',
"#{ScorecardType.model_name.human} Name",
"#{ScorecardType.model_name.human} Type",
'Started At',
'Finished At',
'Contact Name',
Expand All @@ -162,9 +162,9 @@ def initiatives_to_csv(initiatives) # rubocop:disable Metrics/AbcSize,Metrics/Cy
'Contact Website',
'Contact Position'
] + 1.upto(max_organisation_index).map do |index|
"Stakeholder #{index} Name"
"#{Organisation.model_name.human.titleize} #{index} Name"
end + 1.upto(max_subsystem_tag_index).map do |index|
"Subsystem Tag #{index} Name"
"#{SubsystemTag.model_name.human.titleize} #{index} Name"
end) + ['Notes'])

initiatives.each do |initiative|
Expand Down Expand Up @@ -254,21 +254,30 @@ def import_initiatives_from_csv(file) # rubocop:disable Metrics/AbcSize,Metrics/
scorecard_errors = 0
scorecards_cache = {}

scorecard_name_column_names = ["#{Scorecard.model_name.human.titleize} Name", 'Impact Card Name'].uniq
scorecard_name_column_name = nil

# Build cache of scorecards for faster lookup
policy_scope(Scorecard).each do |sc|
scorecards_cache[sc.name.downcase] = sc
end

CSV.foreach(file.path, headers: true, force_quotes: true) do |row| # rubocop:disable Metrics/BlockLength
if scorecard_name_column_name.nil?
scorecard_name_column_name = scorecard_name_column_names.find { |col| row.headers.include?(col) }
end
# If we still don't have a scorecard name column, it means the CSV is invalid
# because it doesn't have the required columns
next if scorecard_name_column_name.nil?
# Skip empty rows
next if row['Name'].blank? || row['Name'].strip.empty?

scorecard = nil
if row['Impact Card Name'].present?
scorecard = scorecards_cache[row['Impact Card Name'].downcase]
if row[scorecard_name_column_name].present?
scorecard = scorecards_cache[row[scorecard_name_column_name].downcase]
unless scorecard
scorecard_errors += 1
errors << "Row #{$INPUT_LINE_NUMBER}: Impact card '#{row['Impact Card Name']}' not found"
errors << "Row #{$INPUT_LINE_NUMBER}: #{scorecard_name_column_name.downcase.upcase_first} '#{row[scorecard_name_column_name]}' not found" # rubocop:disable Layout/LineLength
next
end
end
Expand Down
4 changes: 4 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def brand_for_current_theme(logo_class: 'h-16 w-aut#o', title: nil, title_class:
render 'branding', brand_image_path:, brand_text:, brand_text_class:, logo_class:
end

def indefinite_article(string)
%w[a e i o u].include?(string.to_s[0].downcase) ? 'an' : 'a'
end

def link_to_registration(link_class: '')
link_class = merge_tailwind_class(
'text-lg font-semibold leading-6 text-white bg-teal-900 hover:bg-teal-700 px-4 py-2 rounded-sm', link_class
Expand Down
14 changes: 9 additions & 5 deletions app/helpers/custom_form_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def label(method, content_or_options = nil, options = nil, &block)
def multi_select(method, choices = nil, options = {}, html_options = {}, &block) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
placeholder = options.delete(:placeholder) || 'Select multiple options...'
# rubocop:disable Naming/VariableName
toggleCountText = multi_select_toggle_count_text(method)
toggleCountText = multi_select_toggle_count_text(method, options)
hs_select = MULTI_SELECT_DEFAULT_HS_SELECT.merge(placeholder:, toggleCountText:).to_json
# rubocop:enable Naming/VariableName

Expand Down Expand Up @@ -311,13 +311,17 @@ def merge_options(method:, options: {}, default_class: TEXT_FIELD_CLASS, error_c
base_options.merge(options)
end

def multi_select_toggle_count_text(method)
base_text = method.to_s.titleize.downcase.singularize
def multi_select_toggle_count_text(method, options = {})
base_text = options[:base_text] || method.to_s.titleize.downcase.singularize
pluralized_text = base_text.pluralize

diff = pluralized_text.gsub(base_text, '')
if pluralized_text.ends_with?('ies')
"#{base_text}/#{pluralized_text}"
else
diff = pluralized_text.gsub(base_text, '')

"#{base_text}(#{diff}) selected"
"#{base_text}(#{diff}) selected"
end
end

def wrap_field(method, classes: 'mt-2')
Expand Down
5 changes: 5 additions & 0 deletions app/helpers/labels_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,19 @@ def calculate_luminance(hex_color) # rubocop:disable Metrics/AbcSize
0.2126 * r + 0.7152 * g + 0.0722 * b
end

# SMELL: This method has general utility beyond labels and should be moved to ApplicationHelper or a
# new SidebarHelper.
# SMELL: The name 'label_class_human_title' is misleading; consider renaming it to 'class_human_title'.
def label_class_human_title(klass)
klass.model_name.human.pluralize.titleize
end

# SMELL: This method has general utility beyond labels and should be moved to ApplicationHelper or a new helper.
def label_class_search_placeholder(klass)
"Search #{klass.model_name.human.pluralize.downcase}..."
end

# SMELL: This method has general utility beyond labels and should be moved to ApplicationHelper or a new helper.
def label_class_button_name(klass)
"Create #{klass.model_name.human.titleize}"
end
Expand Down
6 changes: 5 additions & 1 deletion app/helpers/reports_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ def comments_report_name
end

def stakeholder_report_name
multiple_scorecards_types? ? 'Stakeholder Report' : "#{default_data_model_name} Stakeholder Report"
if multiple_scorecards_types?
"#{Organisation.model_name.human.titleize} Report"
else
"#{default_data_model_name} #{Organisation.model_name.human.titleize} Report"
end
end

def report_scorecard_label
Expand Down
9 changes: 9 additions & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Table name: accounts
#
# id :bigint not null, primary key
# default_locale :string default("en"), not null
# default_workspace_grid_mode :string default("classic"), not null
# deleted_at :datetime
# description :string
Expand Down Expand Up @@ -52,6 +53,9 @@ class Account < ApplicationRecord

after_create :create_default_workspace

# validate :default_i18n_scope, inclusion: { in: [nil] + I18n::Backend::ActiveRecord.config.available_scopes },
# allow_nil: true

# TODO: Fix this to use the reminder days for each account (subtract expiry_final_reminder_days)
scope :expiring_soon,
lambda {
Expand All @@ -68,6 +72,11 @@ def expiry_reminder_sent_on
[expiry_initial_reminder_sent_on, expiry_final_reminder_sent_on].compact_blank.max
end

# Returns a string that can be used as the i18n scope for this account
def i18n_scope
id.to_s
end

def max_users_reached?
return false if max_users.zero? || max_users.blank?

Expand Down
2 changes: 1 addition & 1 deletion app/models/community.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Table name: communities
#
# id :integer not null, primary key
# color :string default("#82e150"), not null
# color :string default("#d444db"), not null
# deleted_at :datetime
# description :string
# name :string
Expand Down
20 changes: 20 additions & 0 deletions app/models/reports/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,25 @@ def header_styles(package)
h3: package.workbook.styles.add_style(bg_color: 'dce6f1', fg_color: '386190', sz: 12, b: false)
}
end

def with_i18n_scope(locale: 'en', scope: nil) # rubocop:disable Metrics/MethodLength
current_locale = I18n.locale
current_scope = I18n::Backend::ActiveRecord.config.scope

I18n.locale = locale

if current_scope != scope
I18n::Backend::ActiveRecord.config.scope = scope
I18n.backend.reload!
end

yield if block_given?
ensure
I18n.locale = current_locale
if current_scope != scope
I18n::Backend::ActiveRecord.config.scope = current_scope
I18n.backend.reload!
end
end
end
end
Loading
Loading