Skip to content

Commit

Permalink
Merge pull request #772 from DFE-Digital/bulk-search
Browse files Browse the repository at this point in the history
Enable searching for multiple records
  • Loading branch information
felixclack authored Aug 29, 2024
2 parents d016ddc + e31df66 commit 2991dc6
Show file tree
Hide file tree
Showing 31 changed files with 616 additions and 155 deletions.
1 change: 1 addition & 0 deletions app/assets/files/multiple_records.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TRN,Date of birth
4 changes: 4 additions & 0 deletions app/components/check_records/navigation_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
govuk_header(service_name: t("check_records_service.name"), service_url: check_records_root_path) do |header|
if request.path != check_records_not_authorised_path
if current_dsi_user
if FeatureFlags::FeatureFlag.active?(:bulk_search)
header.with_navigation_item(href: check_records_search_path, text: "Find a record")
header.with_navigation_item(href: new_check_records_bulk_search_path, text: "Find multiple records")
end
header.with_navigation_item(href: check_records_dsi_sign_out_path(id_token_hint: session[:id_token]), text: "Sign out")
elsif request.path != check_records_sign_in_path
header.with_navigation_item(href: check_records_sign_in_path, text: "Sign in")
Expand Down
18 changes: 4 additions & 14 deletions app/components/check_records/teacher_profile_summary_component.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# frozen_string_literal: true

class CheckRecords::TeacherProfileSummaryComponent < ViewComponent::Base
attr_reader :teacher, :sanctions
attr_reader :teacher
attr_accessor :tags

def initialize(teacher)
super
@teacher = teacher
@sanctions = teacher.sanctions
@tags = []
build_tags
end
Expand All @@ -20,10 +19,12 @@ def build_tags

private

delegate :no_restrictions?, :possible_restrictions?, to: :teacher

def append_restriction_tag
tags << if no_restrictions?
{ message: 'No restrictions', colour: 'green'}
elsif possible_restriction?
elsif possible_restrictions?
{ message: 'Possible restrictions', colour: 'blue'}
else
{ message: 'Restriction', colour: 'red'}
Expand Down Expand Up @@ -51,15 +52,4 @@ def append_induction_tag
{ message: 'No induction', colour: 'blue'}
end
end

def no_restrictions?
return true if sanctions.blank?
return false if sanctions.any?(&:possible_match_on_childrens_barred_list?)
return true if sanctions.all?(&:guilty_but_not_prohibited?)
false
end

def possible_restriction?
true if sanctions.any?(&:possible_match_on_childrens_barred_list?)
end
end
22 changes: 22 additions & 0 deletions app/controllers/check_records/bulk_searches_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module CheckRecords
class BulkSearchesController < CheckRecordsController
def new
@bulk_search = BulkSearch.new
@organisation_name = current_dsi_user.dsi_user_sessions.last&.organisation_name
end

def create
@bulk_search = BulkSearch.new(file: bulk_search_params[:file])
@total, @results, @not_found = @bulk_search.call
unless @results
render :new
end
end

private

def bulk_search_params
params.require(:bulk_search).permit(:file)
end
end
end
4 changes: 4 additions & 0 deletions app/javascript/check_records.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { initAll } from "govuk-frontend";
import BulkSearchController from "./controllers/bulk_search_controller";

initAll();

Expand All @@ -13,3 +14,6 @@ document.addEventListener("DOMContentLoaded", () => {
printButton.addEventListener("click", printPage);
}
});

bulkSearch = new BulkSearchController();
bulkSearch.start();
20 changes: 20 additions & 0 deletions app/javascript/controllers/bulk_search_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default class {
start() {
this.toggleSubmitButton({ context: document.querySelector("div#bulk_search") });
}

toggleSubmitButton({ context = document }) {
document.addEventListener("DOMContentLoaded", () => {
const fileInput = context.querySelector("input[type='file']");
const submitButton = context.querySelector("button[type='submit']");

fileInput.addEventListener("change", function () {
if (fileInput.files.length > 0) {
submitButton.removeAttribute("disabled");
} else {
submitButton.setAttribute("disabled", "disabled");
}
});
});
}
}
7 changes: 7 additions & 0 deletions app/lib/qualifications_api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ def teachers(date_of_birth:, last_name:)
[response.body["total"], results]
end

def bulk_teachers(queries: [])
response = client.post("v3/persons/find", { persons: queries }) do |request|
request.headers["X-Api-Version"] = "20240814"
end
response.body
end

def client
@client ||=
Faraday.new(
Expand Down
32 changes: 32 additions & 0 deletions app/lib/qualifications_api/teacher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,35 @@ def qualifications
@qualifications.flatten!
end

def restriction_status
return 'No restrictions' if no_restrictions?
return 'Possible restrictions' if possible_restrictions?

'Restriction'
end

def no_restrictions?
return true if sanctions.blank? || sanctions.all?(&:guilty_but_not_prohibited?)

false
end

def possible_restrictions?
sanctions.any?(&:possible_match_on_childrens_barred_list?)
end

def sanctions
api_data.sanctions&.map { |sanction| Sanction.new(sanction) }
end

def teaching_status
return 'QTS' if qts_awarded?
return 'EYTS' if eyts_awarded?
return 'EYPS' if eyps_awarded?

'No QTS or EYTS'
end

def qts_awarded?
api_data.qts&.awarded.present?
end
Expand All @@ -55,6 +80,13 @@ def eyps_awarded?
api_data.eyps&.awarded.present?
end

def induction_status
return 'Passed induction' if passed_induction?
return 'Exempt from induction' if exempt_from_induction?

'No induction'
end

def passed_induction?
api_data.induction&.status_description == "Pass"
end
Expand Down
86 changes: 86 additions & 0 deletions app/models/bulk_search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
require 'csv'

class BulkSearch
include ActiveModel::Model

attr_accessor :file

validates :file, presence: true
validate :header_row_present?, if: -> { file.present? }
validate :all_rows_have_data, if: -> { file.present? }

def call
return false if invalid?

[total, results, not_found]
end

def not_found
@not_found ||= csv.map { |row|
next if results.any? { |teacher| teacher.trn == row["TRN"] }

Hashie::Mash.new(trn: row["TRN"], date_of_birth: Date.parse(row["Date of birth"]))
}.compact
end

def results
@results ||= response["results"].map do |teacher|
QualificationsApi::Teacher.new(teacher)
end
end

def total
@total ||= response["total"]
end

private

def all_rows_have_data
csv.each_with_index do |row, index|
if row["Date of birth"].blank?
errors.add(:file, "The selected file does not have a date of birth in row #{index + 1}")
else
begin
Date.parse(row["Date of birth"])
rescue Date::Error
errors.add(
:file,
"The date of birth in row #{index + 1} must be in DD/MM/YYYY format"
)
end
end

if row["TRN"].blank?
errors.add(:file, "The selected file does not have a TRN in row #{index + 1}")
end
end
end

def csv
@csv ||= CSV.parse(file.read, headers: true)
end

def header_row_present?
expected_headers = ["TRN", "Date of birth"]
return if csv.headers == expected_headers

errors.add(:file, "The selected file must use the template")
end

def find_all(queries)
search_client.bulk_teachers(queries:)
end

def response
@response ||= begin
queries = csv.map { |row| { trn: row["TRN"], dateOfBirth: row["Date of birth"] } }.compact
find_all(queries)
end
end

def search_client
@search_client ||= QualificationsApi::Client.new(
token: ENV["QUALIFICATIONS_API_FIXED_TOKEN"]
)
end
end
5 changes: 5 additions & 0 deletions app/views/check_records/_organisation_name.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<% if @organisation_name %>
<div class="govuk-caption-m govuk-!-margin-bottom-4">
You're signed in as <%= @organisation_name %>
</div>
<% end %>
26 changes: 26 additions & 0 deletions app/views/check_records/bulk_searches/create.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<% content_for :page_title, "Find multiple records" %>
<% content_for :breadcrumbs do %>
<%= govuk_breadcrumbs(breadcrumbs: { "Home" => check_records_search_path, "Find multiple records" => new_check_records_bulk_search_path, "Results" => nil }) %>
<%= render ActionAtComponent.new(action: "searched") %>
<% end %>

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l"><%= pluralize(@total, 'teacher record') %> found</h1>
<%= govuk_tabs do |tabs|
if @results.any?
tabs.with_tab(label: "Records found", id: "records-found") do %>
<%= render "check_records/search/results_table", teachers: @results %>
<% end %>
<% end %>
<% if @not_found.any? %>
<% tabs.with_tab(label: "Not found", id: "records-not-found") do %>
<%= render "check_records/search/not_found", not_found: @not_found %>
<% end %>
<% end %>
<% end %>
<%= govuk_link_to "Search again", new_check_records_bulk_search_path %>
</div>
</div>
42 changes: 42 additions & 0 deletions app/views/check_records/bulk_searches/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<% content_for :page_title, "Find multiple records" %>

<div class="govuk-grid-row" id="bulk_search">
<div class="govuk-grid-column-two-thirds">
<%= render 'check_records/organisation_name' %>

<h1 class="govuk-heading-l">Find multiple teacher records</h1>

<%= form_with model: [:check_records, @bulk_search] do |f| %>
<%= f.govuk_error_summary %>
<%= govuk_list spaced: true, type: :number do %>
<li>
<h3>Download the CSV template.</h3>
<p class="govuk-body"><%= govuk_link_to "Download CSV template file", asset_path("multiple_records.csv") %></p>
</li>
<li>
<h3>Add each person's details</h3>
<p class="govuk-body">
Open the CSV file and add the teacher reference number (TRN) and date of birth for each
person you want to find a record for.
</p>
<p class="govuk-body">
The TRN must be 7 digits and the date of birth
must be in DD/MM/YYYY format.
</p>
<p class="govuk-body">
You can add a maximum of 20 people.
</p>
</li>
<li>
<h3>Upload file</h3>
<p class="govuk-body">We'll tell you if we find any teacher records.</p>
</li>
<% end %>
<%= f.govuk_file_field :file, class: "govuk-input--width-20", label: { class: "govuk-label--s", text: "Upload file" }, hint: { text: "File type must be a CSV" }, required: true %>
<%= f.govuk_submit "Find records" %>
<% end %>
</div>
</div>
21 changes: 21 additions & 0 deletions app/views/check_records/search/_not_found.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="no-results--table">
<%= govuk_table do |table|
table.with_caption(size: 'm', text: 'No records found')

table.with_head do |head|
head.with_row do |row|
row.with_cell(text: 'TRN')
row.with_cell(text: 'Date of birth')
end
end

table.with_body do |body|
not_found.each do |record|
body.with_row do |row|
row.with_cell(text: record.trn)
row.with_cell(text: record.date_of_birth.to_fs(:long_uk))
end
end
end
end %>
</div>
3 changes: 1 addition & 2 deletions app/views/check_records/search/_results.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,4 @@
end %>
</div>
<% end %>
</div>

</div>
Loading

0 comments on commit 2991dc6

Please sign in to comment.