Skip to content

Commit

Permalink
270 Dashboard Filters. (#381)
Browse files Browse the repository at this point in the history
* 270 Dashboard Filters.

Refactor multiple queries used to generate KPIs, allowing for more flexible date range scopes. Update the system admin dashboard UI to add simple links to filter KPIs based on predefined date ranges aligned with the application windows.

Additionally to help with local ssl testing, add cert.pem, key.pem to .gitignore.
  • Loading branch information
eddieleeper authored Feb 6, 2024
1 parent 9ec2ffb commit d9a5dfc
Show file tree
Hide file tree
Showing 30 changed files with 1,015 additions and 142 deletions.
2 changes: 2 additions & 0 deletions .bundler-audit.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
---
ignore:
- CVE-2023-26141
- CVE-2024-21636
- GHSA-xc9x-jj77-9p9j
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ bin/fetch_config.rb

/app/assets/builds/*
!/app/assets/builds/.keep

./nginx/key.pem
./nginx/cert.pem
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ gem "foreman"
gem "jbuilder"
gem "okcomputer"
gem "pg", "~> 1.5"
gem "puma", "~> 6.4"
gem "puma", ">= 6.4.2", "< 7"
gem "rails", "~> 7.1"
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]

Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ GEM
psych (5.1.1.1)
stringio
public_suffix (5.0.1)
puma (6.4.0)
puma (6.4.2)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.7.3)
Expand Down Expand Up @@ -536,7 +536,7 @@ DEPENDENCIES
phonelib
propshaft (~> 0.8.0)
pry-byebug
puma (~> 6.4)
puma (>= 6.4.2, < 7)
rails (~> 7.1)
rolify
rspec-rails
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/system_admin/dashboard_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module SystemAdmin
class DashboardController < AdminController
def show
@kpis = Kpis.new(**kpi_params)
@kpis = Kpis.new(**kpi_params.merge(window_params))
rescue ArgumentError => e
flash[:alert] = e.message
redirect_to(dashboard_path)
Expand All @@ -16,5 +16,9 @@ def kpi_params
.to_hash
.symbolize_keys
end

def window_params
{ window: params[:window] }
end
end
end
9 changes: 9 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,13 @@ def application_statuses_options(selected: nil, all_statuses: false)

options_for_select(statuses, selected:)
end

def dashboard_link(window_param, label)
current_window = params[:window] || "all"
if current_window == window_param
content_tag(:strong, label)
else
link_to(label, dashboard_path(window: window_param))
end
end
end
15 changes: 15 additions & 0 deletions app/helpers/inflection_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Initially created to help display the correct form of day or days in the dashboard.
module InflectionHelper
def pluralize_word(count, word)
"#{count} #{word.pluralize(count)}"
end

def calculate_day_stats(durations)
min_days = pluralize_word(durations.min.abs, "day") if durations.min
max_days = pluralize_word(durations.max.abs, "day") if durations.max
average_duration = durations.size.positive? ? (durations.sum / durations.size.to_f).round.abs : 0
average_days = pluralize_word(average_duration, "day")

{ min: min_days, max: max_days, average: average_days }
end
end
60 changes: 42 additions & 18 deletions app/models/kpis.rb
Original file line number Diff line number Diff line change
@@ -1,73 +1,74 @@
class Kpis
def initialize(unit: "hours", range_start: "24", range_end: "0")
def initialize(unit: "hours", range_start: "24", range_end: "0", window: "all")
@date_range_params = parse_date_range(unit:, range_start:, range_end:)
@date_range = to_date_range(**@date_range_params)
@window = window
end

attr_reader :date_range, :date_range_params
attr_reader :date_range, :date_range_params, :window

def total_applications
Application.count
filtered_applications_by_date.count
end

def total_rejections
ApplicationProgress.where.not(rejection_completed_at: nil).count
filter_by_date_range(ApplicationProgress.where.not(rejection_completed_at: nil)).count
end

def average_age
AverageAgeQuery.new.call
AverageAgeQuery.new(filtered_applications_by_date).call
end

def total_paid
ApplicationProgress.where.not(banking_approval_completed_at: nil).count
filter_by_date_range(ApplicationProgress.where.not(banking_approval_completed_at: nil)).count
end

def route_breakdown
RouteBreakdownQuery.new.call
RouteBreakdownQuery.new(filtered_applications_by_date).call
end

def subject_breakdown
SubjectBreakdownQuery.new.call
SubjectBreakdownQuery.new(filtered_applications_by_date).call
end

def visa_breakdown
VisaBreakdownQuery.new.call.first(3)
VisaBreakdownQuery.new(filtered_applications_by_date).call.first(3)
end

def nationality_breakdown
NationalityBreakdownQuery.new.call.first(5)
NationalityBreakdownQuery.new(filtered_applications_by_date).call.first(5)
end

def gender_breakdown
GenderBreakdownQuery.new.call
GenderBreakdownQuery.new(filtered_applications_by_date).call
end

def rejection_reason_breakdown
RejectionReasonBreakdownQuery.new.call
RejectionReasonBreakdownQuery.new(filtered_applications_by_date).call
end

def time_to_initial_checks
TimeToInitialChecksQuery.new.call
TimeToInitialChecksQuery.new(filtered_progress_by_date).call
end

def time_to_home_office_checks
TimeToHomeOfficeChecksQuery.new.call
TimeToHomeOfficeChecksQuery.new(filtered_progress_by_date).call
end

def time_to_school_checks
TimeToSchoolChecksQuery.new.call
TimeToSchoolChecksQuery.new(filtered_progress_by_date).call
end

def time_to_banking_approval
TimeToBankingApprovalQuery.new.call
TimeToBankingApprovalQuery.new(filtered_progress_by_date).call
end

def time_to_payment_confirmation
TimeToPaymentConfirmationQuery.new.call
TimeToPaymentConfirmationQuery.new(filtered_progress_by_date).call
end

def status_breakdown
StatusBreakdownQuery.call
StatusBreakdownQuery.call(filtered_progress_by_date)
end

def forms_funnel
Expand Down Expand Up @@ -119,4 +120,27 @@ def to_date_range(unit:, range_start:, range_end:)
def forms_funnel_query
@forms_funnel_query ||= FormsFunnelQuery.call(date_range:)
end

def date_ranges
{
"sept_oct" => Date.new(2023, 9, 1).beginning_of_day..Date.new(2023, 10, 31).end_of_day,
"jan_feb" => Date.new(2024, 1, 1).beginning_of_day..Date.new(2024, 3, 1).end_of_day,
}
end

def filter_by_date_range(scope)
if date_ranges.key?(window)
scope.where(created_at: date_ranges[window])
else
scope
end
end

def filtered_applications_by_date
filter_by_date_range(Application.all)
end

def filtered_progress_by_date
filter_by_date_range(ApplicationProgress.all)
end
end
4 changes: 2 additions & 2 deletions app/queries/average_age_query.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class AverageAgeQuery
def initialize(relation = Applicant.all)
@relation = relation.joins(:application).merge(Application.all)
def initialize(applications = Application.all)
@relation = Applicant.joins(:application).merge(applications)
end

def call
Expand Down
4 changes: 2 additions & 2 deletions app/queries/gender_breakdown_query.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class GenderBreakdownQuery
def initialize(relation = Applicant.all)
@relation = relation.joins(:application).merge(Application.all)
def initialize(applications = Application.all)
@relation = Applicant.joins(:application).merge(applications)
end

def call
Expand Down
4 changes: 2 additions & 2 deletions app/queries/nationality_breakdown_query.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class NationalityBreakdownQuery
def initialize(relation = Applicant.all)
@relation = relation.joins(:application).merge(Application.all)
def initialize(applications = Application.all)
@relation = Applicant.joins(:application).merge(applications)
end

def call
Expand Down
4 changes: 2 additions & 2 deletions app/queries/rejection_reason_breakdown_query.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class RejectionReasonBreakdownQuery
def initialize(relation = ApplicationProgress.all)
@relation = relation.joins(:application).merge(Application.all).where.not(rejection_reason: nil)
def initialize(applications = Application.all)
@relation = ApplicationProgress.joins(:application).merge(applications).where.not(rejection_reason: nil)
end

def call
Expand Down
10 changes: 7 additions & 3 deletions app/queries/status_breakdown_query.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class StatusBreakdownQuery
def self.call
new.execute
def self.call(scope = ApplicationProgress.all)
new(scope).execute
end

def initialize(scope)
@scope = scope
end

def execute
Expand All @@ -10,7 +14,7 @@ def execute
private

def status_breakdown
ApplicationProgress.group(:status).count
@scope.group(:status).count
end

def ordered_with_defaults(breakdown)
Expand Down
9 changes: 2 additions & 7 deletions app/queries/time_to_banking_approval_query.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
class TimeToBankingApprovalQuery
include InflectionHelper
def initialize(relation = ApplicationProgress.all)
@relation = relation
end

def call
applications_list = @relation.where.not(school_checks_completed_at: nil).where.not(banking_approval_completed_at: nil)

durations = applications_list.map { |app| (app.banking_approval_completed_at.to_date - app.school_checks_completed_at.to_date).to_i }

min_days = "#{durations.min.abs} days" if durations.min
max_days = "#{durations.max.abs} days" if durations.max
average_days = durations.size.positive? ? "#{(durations.sum / durations.size.to_f).round.abs} days" : "0 days"

{ min: min_days, max: max_days, average: average_days }
calculate_day_stats(durations)
end
end
10 changes: 3 additions & 7 deletions app/queries/time_to_home_office_checks_query.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
class TimeToHomeOfficeChecksQuery
include InflectionHelper

def initialize(relation = ApplicationProgress.all)
@relation = relation
end

def call
applications_list = @relation.where.not(initial_checks_completed_at: nil).where.not(home_office_checks_completed_at: nil)

durations = applications_list.map { |app| (app.home_office_checks_completed_at.to_date - app.initial_checks_completed_at.to_date).to_i }

min_days = "#{durations.min.abs} days" if durations.min
max_days = "#{durations.max.abs} days" if durations.max
average_days = durations.size.positive? ? "#{(durations.sum / durations.size.to_f).round.abs} days" : "0 days"

{ min: min_days, max: max_days, average: average_days }
calculate_day_stats(durations)
end
end
10 changes: 3 additions & 7 deletions app/queries/time_to_initial_checks_query.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
class TimeToInitialChecksQuery
include InflectionHelper

def initialize(relation = ApplicationProgress.all)
@relation = relation
end

def call
applications_list = @relation.where.not(created_at: nil).where.not(initial_checks_completed_at: nil)

durations = applications_list.map { |app| (app.initial_checks_completed_at.to_date - app.created_at.to_date).to_i }

min_days = "#{durations.min.abs} days" if durations.min
max_days = "#{durations.max.abs} days" if durations.max
average_days = durations.size.positive? ? "#{(durations.sum / durations.size.to_f).round.abs} days" : "0 days"

{ min: min_days, max: max_days, average: average_days }
calculate_day_stats(durations)
end
end
9 changes: 2 additions & 7 deletions app/queries/time_to_payment_confirmation_query.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
class TimeToPaymentConfirmationQuery
include InflectionHelper
def initialize(relation = ApplicationProgress.all)
@relation = relation
end

def call
applications_list = @relation.where.not(payment_confirmation_completed_at: nil).where.not(banking_approval_completed_at: nil)

durations = applications_list.map { |app| (app.payment_confirmation_completed_at.to_date - app.banking_approval_completed_at.to_date).to_i }

min_days = "#{durations.min.abs} days" if durations.min
max_days = "#{durations.max.abs} days" if durations.max
average_days = durations.size.positive? ? "#{(durations.sum / durations.size.to_f).round.abs} days" : "0 days"

{ min: min_days, max: max_days, average: average_days }
calculate_day_stats(durations)
end
end
9 changes: 2 additions & 7 deletions app/queries/time_to_school_checks_query.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
class TimeToSchoolChecksQuery
include InflectionHelper
def initialize(relation = ApplicationProgress.all)
@relation = relation
end

def call
applications_list = @relation.where.not(home_office_checks_completed_at: nil).where.not(school_checks_completed_at: nil)

durations = applications_list.map { |app| (app.school_checks_completed_at.to_date - app.home_office_checks_completed_at.to_date).to_i }

min_days = "#{durations.min.abs} days" if durations.min
max_days = "#{durations.max.abs} days" if durations.max
average_days = durations.size.positive? ? "#{(durations.sum / durations.size.to_f).round.abs} days" : "0 days"

{ min: min_days, max: max_days, average: average_days }
calculate_day_stats(durations)
end
end
8 changes: 8 additions & 0 deletions app/views/system_admin/dashboard/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
<div class="govuk-body date-range">
<%= dashboard_link('all', 'All') %>
|
<%= dashboard_link('sept_oct', 'Sept/Oct 2023') %>
|
<%= dashboard_link('jan_feb', 'Jan/Feb 2024') %>
</div>

<div class="dashboard govuk-body">
<div class="kpi-widget applications">
<h3 class="title">Applications</h3>
Expand Down
2 changes: 2 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@

# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true

config.hosts << "itrp.local"
end
2 changes: 2 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@

# config/initializers/dartsass.rb
config.dartsass.build_options << " --quiet-deps"

config.hosts << "www.example.com"
end
Loading

0 comments on commit d9a5dfc

Please sign in to comment.