Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-feature-flags-via-flipper
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticspoon committed Jun 26, 2024
2 parents 0ca1abd + b37476e commit 9ca9eed
Show file tree
Hide file tree
Showing 27 changed files with 378 additions and 83 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ jobs:
strategy:
fail-fast: false
matrix:
groups: ["[0, 1, 2]", "[3, 4, 5]", "[6, 7, 8]", "[9, 10, 11]"]
groups: ["[0, 1, 2, 3]", "[4, 5, 6, 7]", "[8, 9, 10, 11]"]
uses: ./.github/workflows/rspec_parallel.yml
secrets: inherit
with:
groups: ${{ matrix.groups }}
group_count: 12 # the total number of test groups, must match the groups listed in the matrix.groups
parallel_processes_count: 3 # the number of parallel processes to run tests in worker, must match the size of the
parallel_processes_count: 4 # the number of parallel processes to run tests in worker, must match the size of the
# inner arrays in the matrix.groups
combine_and_report:
if: ${{ !cancelled() }}
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ group :development, :test do
gem "rspec-rails"
gem "rswag-specs"
gem "shoulda-matchers"
gem "standard", "~> 1.37.0"
gem "standard", "~> 1.39.0"
end

group :development do
Expand Down
24 changes: 13 additions & 11 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,15 @@ GEM
json (2.7.2)
json-schema (4.1.1)
addressable (>= 2.8)
jwt (2.8.1)
jwt (2.8.2)
base64
language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (~> 2.8)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
lint_roller (1.1.0)
logger (1.6.0)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
Expand All @@ -306,11 +307,11 @@ GEM
minitest (5.23.1)
multi_xml (0.7.1)
bigdecimal (~> 3.1)
multipart-post (2.4.0)
multipart-post (2.4.1)
mutex_m (0.2.0)
net-http-persistent (4.0.1)
connection_pool (~> 2.2)
net-imap (0.4.13)
net-imap (0.4.14)
date
net-protocol
net-pop (0.1.2)
Expand All @@ -320,14 +321,14 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.5)
nokogiri (1.16.6)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.5-arm64-darwin)
nokogiri (1.16.6-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.5-x86_64-darwin)
nokogiri (1.16.6-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.5-x86_64-linux)
nokogiri (1.16.6-x86_64-linux)
racc (~> 1.4)
noticed (2.3.2)
rails (>= 6.1.0)
Expand Down Expand Up @@ -485,8 +486,9 @@ GEM
nokogiri (>= 1.12.0)
scout_apm (5.3.8)
parser
selenium-webdriver (4.21.1)
selenium-webdriver (4.22.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
Expand All @@ -509,7 +511,7 @@ GEM
actionpack (>= 6.1)
activesupport (>= 6.1)
sprockets (>= 3.0.0)
standard (1.37.0)
standard (1.39.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.64.0)
Expand All @@ -531,7 +533,7 @@ GEM
timeout (0.4.1)
traceroute (0.8.1)
rails (>= 3.0.0)
twilio-ruby (7.0.2)
twilio-ruby (7.2.0)
faraday (>= 0.9, < 3.0)
jwt (>= 1.5, < 3.0)
nokogiri (>= 1.6, < 2.0)
Expand Down Expand Up @@ -646,7 +648,7 @@ DEPENDENCIES
spring
spring-commands-rspec
sprockets-rails
standard (~> 1.37.0)
standard (~> 1.39.0)
stimulus-rails
strong_migrations
traceroute
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@use "shared/noscript";
@use "shared/select2_additional_styles";
@use "shared/sidebar";
@use "shared/tomselect_additional_styles.scss";
@use "shared/typography";
@use "shared/utilities";
@use "shared/validated_form_component";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.ts-dropdown [data-selectable] .highlight {
display: inline-block;
}
2 changes: 1 addition & 1 deletion app/controllers/banners_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def set_banner
end

def banner_params
BannerParameters.new(params, current_user, cookies[:browser_time_zone])
BannerParameters.new(params, current_user, browser_time_zone)
end

def deactivate_alternate_active_banner
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/casa_cases_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def show
format.html {}
format.csv do
case_contacts = @casa_case.decorate.case_contacts_ordered_by_occurred_at
csv = CaseContactsExportCsvService.new(case_contacts).perform
csv = CaseContactsExportCsvService.new(case_contacts, CaseContactReport::COLUMNS).perform
send_data csv, filename: case_contact_csv_name(case_contacts)
end
format.xlsx do
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/case_contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def edit
end

def destroy
authorize CaseContact
authorize @case_contact

@case_contact.destroy
flash[:notice] = "Contact is successfully deleted."
Expand Down
4 changes: 2 additions & 2 deletions app/helpers/banner_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ def conditionally_add_hidden_class(current_banner_is_active)

def banner_expiration_time_in_words(banner)
if banner.expired?
"Yes"
"Expired"
elsif banner.expires_at
"in #{distance_of_time_in_words(Time.now, banner.expires_at)}"
else
"No"
"No Expiration"
end
end
end
14 changes: 13 additions & 1 deletion app/models/banner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ class Banner < ApplicationRecord
scope :active, -> { where(active: true) }

validates_presence_of :name
validates_presence_of :content
validate :only_one_banner_is_active_per_organization
validate :expires_at_must_be_in_future

def expired?
expires_at && Time.current > expires_at
expired = expires_at && Time.current > expires_at
update(active: false) if active && expired
expired
end

# `expires_at` is stored in the database as UTC, but timezone information will be stripped before displaying on frontend
Expand All @@ -27,6 +31,14 @@ def only_one_banner_is_active_per_organization
errors.add(:base, "Only one banner can be active at a time. Mark the other banners as not active before marking this banner as active.")
end
end

# Validation using line below doesn't work with `travel_to` in specs. Must use detailed method
# validates_comparison_of :expires_at, greater_than: Time.current, message: "must take place in the future (after #{Time.current})", allow_blank: true
def expires_at_must_be_in_future
if expires_at.present? && expires_at < Time.current
errors.add(:expires_at, "must take place in the future (after #{Time.current})")
end
end
end

# == Schema Information
Expand Down
20 changes: 20 additions & 0 deletions app/models/case_contact_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ def filtered_case_contacts(args)
def filtered_columns(args)
if args[:filtered_csv_cols].present?
args[:filtered_csv_cols].select { |_key, value| value == "true" }.keys.map(&:to_sym)
else
COLUMNS
end
end

COLUMNS = [
:internal_contact_number,
:duration_minutes,
:contact_types,
:contact_made,
:contact_medium,
:occurred_at,
:added_to_system_at,
:miles_driven,
:wants_driving_reimbursement,
:casa_case_number,
:creator_email,
:creator_name,
:supervisor_name,
:case_contact_notes,
:court_topics
]
end
10 changes: 10 additions & 0 deletions app/models/contact_topic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ class ContactTopic < ApplicationRecord

scope :active, -> { where(active: true, soft_delete: false) }

scope :with_answers_in, ->(case_contacts_scope) do
# unscope order in case it collides with distinct
case_contact_ids = case_contacts_scope.unscope(:order).select(:id).distinct

# AR will use the query above as a subquery within this one's where clause, ie:
ContactTopic # select …
.joins(contact_topic_answers: :case_contact) # from contact_topics inner join contact_topics …
.where(case_contact: {id: case_contact_ids}) # where case_contact.id in (select distinct case_contacts.id from …
end

class << self
def generate_for_org!(casa_org)
default_contact_topics.each do |topic|
Expand Down
10 changes: 8 additions & 2 deletions app/policies/case_contact_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ def is_creator_or_supervisor_or_casa_admin?
is_creator? || admin_or_supervisor?
end

def destroy?
admin_or_supervisor? || (is_creator? && is_draft?)
end

def additional_expenses_allowed?
FeatureFlagService.is_enabled?(FeatureFlagService::SHOW_ADDITIONAL_EXPENSES_FLAG) &&
current_organization.additional_expenses_enabled
Expand All @@ -16,8 +20,6 @@ def additional_expenses_allowed?
alias_method :new?, :admin_or_supervisor_or_volunteer?
alias_method :show?, :is_creator_or_casa_admin?
alias_method :drafts?, :admin_or_supervisor_or_volunteer?
alias_method :create?, :is_creator_or_casa_admin?
alias_method :destroy?, :admin_or_supervisor?
alias_method :update?, :is_creator_or_supervisor_or_casa_admin?
alias_method :edit?, :is_creator_or_supervisor_or_casa_admin?
alias_method :restore?, :is_admin?
Expand All @@ -44,6 +46,10 @@ def resolve

private

def is_draft?
!record.active?
end

def is_creator?
record.creator == user
end
Expand Down
82 changes: 58 additions & 24 deletions app/services/case_contacts_export_csv_service.rb
Original file line number Diff line number Diff line change
@@ -1,43 +1,77 @@
require "csv"

class CaseContactsExportCsvService
attr_reader :case_contacts, :filtered_columns
attr_reader :case_contacts_scope, :filtered_columns

def initialize(case_contacts, filtered_columns = nil)
@filtered_columns = filtered_columns || CaseContactsExportCsvService.DATA_COLUMNS.keys

@case_contacts = case_contacts.preload({creator: :supervisor}, :contact_types, :casa_case)
def initialize(case_contacts_scope, filtered_columns)
@filtered_columns = filtered_columns
@case_contacts_scope = case_contacts_scope
end

def perform
CSV.generate(headers: true) do |csv|
csv << filtered_columns.map(&:to_s).map(&:titleize)
case_contacts = case_contacts_scope.preload({creator: :supervisor}, :contact_types, :casa_case, :contact_topic_answers)

CSV.generate do |csv|
csv << fixed_column_headers + court_topics.values

if case_contacts.present?
case_contacts.decorate.each do |case_contact|
csv << CaseContactsExportCsvService.DATA_COLUMNS(case_contact).slice(*filtered_columns).values
csv << fixed_column_values(case_contact) + court_topic_answers(case_contact)
end
end
end
end

def self.DATA_COLUMNS(case_contact = nil)
def fixed_column_values(case_contact)
# Note: these header labels are for stakeholders and do not match the
# Rails DB names in all cases, e.g. added_to_system_at header is case_contact.created_at
{
internal_contact_number: case_contact&.id,
duration_minutes: case_contact&.report_duration_minutes,
contact_types: case_contact&.report_contact_types,
contact_made: case_contact&.report_contact_made,
contact_medium: case_contact&.medium_type,
occurred_at: I18n.l(case_contact&.occurred_at, format: :full, default: nil),
added_to_system_at: case_contact&.created_at,
miles_driven: case_contact&.miles_driven,
wants_driving_reimbursement: case_contact&.want_driving_reimbursement,
casa_case_number: case_contact&.casa_case&.case_number,
creator_email: case_contact&.creator&.email,
creator_name: case_contact&.creator&.display_name,
supervisor_name: case_contact&.creator&.supervisor&.display_name,
case_contact_notes: case_contact&.notes
mappings = {
internal_contact_number: case_contact.id,
duration_minutes: case_contact.report_duration_minutes,
contact_types: case_contact.report_contact_types,
contact_made: case_contact.report_contact_made,
contact_medium: case_contact.medium_type,
occurred_at: I18n.l(case_contact.occurred_at, format: :full, default: nil),
added_to_system_at: case_contact.created_at,
miles_driven: case_contact.miles_driven,
wants_driving_reimbursement: case_contact.want_driving_reimbursement,
casa_case_number: case_contact.casa_case&.case_number,
creator_email: case_contact.creator&.email,
creator_name: case_contact.creator&.display_name,
supervisor_name: case_contact.creator&.supervisor&.display_name,
case_contact_notes: case_contact.notes
}

mappings.slice(*filtered_columns).values
end

def fixed_column_headers
filtered_columns.excluding(:court_topics).map(&:to_s).map(&:titleize)
end

def court_topic_answers(case_contact)
return [] if court_topics_filtered?

# index_by so we don't loop through answers multiple times
answers_by_topic_id = case_contact.contact_topic_answers.index_by(&:contact_topic_id)

# we have to map values for all topics so we 'skip' unanswered ones (with a blank cell)
court_topics.keys.map { |topic_id| answers_by_topic_id[topic_id]&.value }
end

def court_topics
return {} if court_topics_filtered?

@court_topics ||= ContactTopic
.with_answers_in(case_contacts_scope)
.order(:id)
.select(:id, :question)
.distinct
.to_h { |topic| [topic.id, topic.question] }
end

def court_topics_filtered?
return @court_topics_filtered if defined? @court_topics_filtered
@court_topics_filtered = filtered_columns.exclude?(:court_topics)
end
end
6 changes: 5 additions & 1 deletion app/views/banners/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
</div>
<span class="input-style-1">
<%= form.label :expires_at, "Expires at (optional)" %>
<%= form.datetime_field :expires_at, value: banner.expires_at_in_time_zone(cookies[:browser_time]), required: false %>
<%= form.datetime_field :expires_at,
value: banner.expires_at_in_time_zone(browser_time_zone),
required: false,
include_seconds: false,
min: Time.current.in_time_zone(browser_time_zone) %>
</span>
<div class="input-style-1">
<%= form.label :content %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/banners/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<tr>
<th>Name</th>
<th>Active?</th>
<th>Expires At</th>
<th>Expiration</th>
<th>Last Updated By</th>
<th>Updated At</th>
<th>Actions</th>
Expand Down
Loading

0 comments on commit 9ca9eed

Please sign in to comment.