-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #711 from bitzesty/nomination-statistics-report
[KAVS0824] Nomination statistics report
- Loading branch information
Showing
22 changed files
with
559 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
class Admin::ProtectedFilesController < Admin::BaseController | ||
include ProtectedFileMixin | ||
end |
101 changes: 101 additions & 0 deletions
101
app/controllers/admin/statistics/nominations_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
class Admin::Statistics::NominationsController < Admin::BaseController | ||
def index | ||
authorize :statistics, :index? | ||
|
||
@search = NominationStatsSearch.new(FormAnswer.all).search(permitted_params) | ||
end | ||
|
||
def create | ||
authorize :statistics, :send? | ||
|
||
@search = NominationStatsSearch.new(FormAnswer.all).search(permitted_params) | ||
|
||
data = generate_csv(@search.results) | ||
file = current_admin.protected_files.create_from_raw_data(data, "nomination-statistics-export.csv") | ||
|
||
Admin::Statistics::NominationMailer.notify(current_admin.id, file.id).deliver_now | ||
|
||
redirect_to admin_statistics_nominations_path(search: permitted_params), success: "CSV with nomination statistics has been sent to #{current_admin.email}." | ||
end | ||
|
||
private | ||
|
||
def permitted_params | ||
params.fetch(:search, NominationStatsSearch.default_search).permit! | ||
end | ||
|
||
def generate_csv(data) | ||
CSV.generate(encoding: "UTF-8", force_quotes: true) do |csv| | ||
csv << csv_mapping.map { |m| m[:label] } | ||
data.each do |row| | ||
csv << csv_mapping.map do |m| | ||
func = m[:method] | ||
row[func] | ||
end | ||
end | ||
|
||
csv << csv_mapping.map do |m| | ||
func = m[:method] | ||
|
||
if func == :ceremonial_county_name | ||
"Total" | ||
else | ||
data.sum(&func) | ||
end | ||
end | ||
end | ||
end | ||
|
||
def csv_mapping | ||
[ | ||
{ | ||
label: "Lieutenancy", | ||
method: :ceremonial_county_name | ||
}, | ||
{ | ||
label: "Nominations submitted", | ||
method: :submitted_count | ||
}, | ||
{ | ||
label: "Eligiblity - Admin eligible", | ||
method: :admin_eligible_count | ||
}, | ||
{ | ||
label: "Eligiblity - Not eligible nominator", | ||
method: :admin_not_eligible_nominator_count | ||
}, | ||
{ | ||
label: "Eligiblity - Not eligible group", | ||
method: :admin_not_eligible_group_count | ||
}, | ||
{ | ||
label: "Eligiblity - Withdrawn", | ||
method: :withdrawn_count | ||
}, | ||
{ | ||
label: "Local Assessment - Not recommended", | ||
method: :local_assessment_not_recommended_count | ||
}, | ||
{ | ||
label: "Local Assessment - Recommended", | ||
method: :local_assessment_recommended_count | ||
}, | ||
{ | ||
label: "National Assessment - Not recommended", | ||
method: :not_recommended_count | ||
}, | ||
{ | ||
label: "National Assessment - Recommended", | ||
method: :shortlisted_count | ||
}, | ||
{ | ||
label: "Royal Approval - Awarded", | ||
method: :awarded_count | ||
}, | ||
{ | ||
label: "Total", | ||
method: :total_count | ||
} | ||
] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module ProtectedFileMixin | ||
extend ActiveSupport::Concern | ||
|
||
def self.included(base) | ||
base.skip_after_action :verify_authorized | ||
end | ||
|
||
def show | ||
file = current_subject.protected_files.find(params[:id]) | ||
file.mark_as_downloaded! | ||
redirect_to file.file.url, allow_other_host: true | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
class Admin::Statistics::NominationMailer < ApplicationMailer | ||
layout "mailer" | ||
|
||
def notify(identifier, file_identifier) | ||
@admin = Admin.find(identifier) | ||
@file = @admin.protected_files.find(file_identifier) | ||
|
||
subject = "Nomination statistics export - King's Award for Voluntary Service" | ||
|
||
view_mail ENV["GOV_UK_NOTIFY_API_TEMPLATE_ID"], to: @admin.email, subject: subject_with_env_prefix(subject) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
class ProtectedFile < ApplicationRecord | ||
mount_uploader :file, ProtectedFileUploader | ||
|
||
belongs_to :entity, polymorphic: true | ||
|
||
validates :entity_type, inclusion: { in: %w(Admin Assessor GroupLeader Lieutenant User) }, presence: true | ||
validates :entity_id, presence: true | ||
|
||
after_create :cleanup_tempfile | ||
|
||
def mark_as_downloaded! | ||
touch(:last_downloaded_at) | ||
end | ||
|
||
def self.build_from_raw_data(data, filename, **attrs) | ||
@file = Tempfile.new([get_basename(filename), get_extname(filename)]) | ||
@file.write(data) | ||
@file.close | ||
|
||
self.build( | ||
file: @file, | ||
**attrs | ||
) | ||
end | ||
|
||
def self.create_from_raw_data(data, filename, **attrs) | ||
build_from_raw_data(data, filename, **attrs).tap do |record| | ||
record.save | ||
end | ||
end | ||
|
||
private | ||
|
||
def cleanup_tempfile | ||
@file.unlink if @file | ||
end | ||
|
||
def self.get_basename(filename) | ||
File.basename(filename, get_extname(filename)) | ||
end | ||
private_class_method :get_basename | ||
|
||
|
||
def self.get_extname(filename) | ||
File.extname(filename) | ||
end | ||
private_class_method :get_extname | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class StatisticsPolicy < ApplicationPolicy | ||
def index? | ||
admin? | ||
end | ||
|
||
def send? | ||
admin? | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
class NominationStatsSearch < Search | ||
TRACKED_STATES = %i[ | ||
submitted | ||
admin_eligible | ||
admin_not_eligible_nominator | ||
admin_not_eligible_group | ||
withdrawn | ||
local_assessment_not_recommended | ||
local_assessment_recommended | ||
not_recommended | ||
shortlisted | ||
awarded | ||
] | ||
|
||
FETCH_QUERY = %Q{ | ||
ceremonial_counties.name AS ceremonial_county_name, | ||
#{TRACKED_STATES.map { |s| "COUNT(CASE WHEN form_answers.state = '#{s}' THEN 1 END) AS #{s}_count" }.join(',')}, | ||
COUNT(CASE WHEN form_answers.state IN (#{TRACKED_STATES.map { |s| "'#{s}'" }.join(',')}) THEN 1 END) AS total_count | ||
}.squish.freeze | ||
|
||
def self.default_search | ||
{ | ||
sort: "ceremonial_county_name", | ||
search_filter: { | ||
year: "all_years", | ||
assigned_ceremonial_county: ceremonial_county_options.map(&:second) | ||
} | ||
} | ||
end | ||
|
||
def results | ||
super | ||
|
||
@search_results = @search_results | ||
.select(FETCH_QUERY) | ||
.joins(:ceremonial_county) | ||
.group("ceremonial_counties.name") | ||
|
||
@search_results | ||
end | ||
|
||
def filter_by_year(scoped_results, value) | ||
if value == "all_years" | ||
scoped_results | ||
else | ||
(year = AwardYear.find_by(year: value)) ? scoped_results.where(award_year: year) : scoped_results.none | ||
end | ||
end | ||
|
||
def filter_by_assigned_ceremonial_county(scoped_results, value) | ||
value = value.map do |v| | ||
v == "not_assigned" ? nil : v | ||
end | ||
scoped_results.where(ceremonial_county_id: value) | ||
end | ||
|
||
def sort_by_ceremonial_county_name(scoped_results, desc = false) | ||
scoped_results.order("ceremonial_counties.name #{sort_order(desc)}") | ||
end | ||
|
||
class << self | ||
def ceremonial_county_options | ||
collection_mapping(county_options) | ||
end | ||
|
||
private | ||
|
||
def collection_mapping(options) | ||
options.map do |k, v| | ||
[v[:label], k] | ||
end | ||
end | ||
|
||
def county_options | ||
options = Hash[not_assigned: { label: "Not assigned" }] | ||
|
||
CeremonialCounty.ordered.collect do |county| | ||
options[county.id] = { label: county.name } | ||
end | ||
|
||
options | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class ProtectedFileUploader < FileUploader | ||
def extension_allowlist | ||
super + %w(csv) | ||
end | ||
end |
18 changes: 18 additions & 0 deletions
18
app/views/admin/statistics/nomination_mailer/notify.html.slim
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
p.govuk-body | ||
= "Dear #{@admin.first_name}," | ||
|
||
p.govuk-body | ||
' You requested an export of nomination statistics. | ||
|
||
p.govuk-body | ||
' Please find this report on the link below: | ||
|
||
p.govuk-body | ||
= link_to admin_protected_file_url(@file), admin_protected_file_url(@file) | ||
|
||
p.govuk-body | ||
' Kind Regards, | ||
|
||
br | ||
|
||
' The King's Awards Office |
Oops, something went wrong.