Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spike downloads with check-boxes #7156

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ gem "rails_semantic_logger"
gem "recaptcha"
gem "redis"
gem "rgeo-geojson"
gem "rubyzip"
gem "sanitize"
gem "sentry-rails"
gem "sentry-ruby"
Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,7 @@ DEPENDENCIES
rubocop-govuk
rubocop-performance
rubocop-rspec_rails
rubyzip
sanitize
selenium-webdriver
sentry-rails
Expand Down
10 changes: 8 additions & 2 deletions app/assets/javascript/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import * as Sentry from '@sentry/browser';
import 'core-js/modules/es.weak-map';
import 'core-js/modules/es.weak-set';
import '@stimulus/polyfills';
import { initAll } from 'govuk-frontend';
import { initAll as govukInit } from 'govuk-frontend';
import $ from 'jquery';
import { initAll as mojInit } from '@ministryofjustice/frontend';

import { Application } from '@hotwired/stimulus';
import Rails from 'rails-ujs';

Expand Down Expand Up @@ -61,4 +64,7 @@ application.register('tracked-link', TrackedLinkController);
application.register('utils', UtilsController);

Rails.start();
initAll();
govukInit();
window.$ = $;
mojInit();
// window.MOJFrontend = MOJFrontend;
3 changes: 3 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ $govuk-assets-path: "/";
@import 'leaflet/dist/leaflet';
@import 'leaflet.markercluster/dist/MarkerCluster';
@import 'leaflet-gesture-handling/dist/leaflet-gesture-handling';
// oh dear this file is broken as it tries to import from node_modules
//@import "@ministryofjustice/frontend/moj/all";
@import "@ministryofjustice/frontend/moj/components/multi-select/multi-select";

@import 'base/global';
@import 'base/guide_card';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class Publishers::Vacancies::JobApplicationsController < Publishers::Vacancies::
include Jobseekers::QualificationFormConcerns
include DatesHelper

include ActionController::Live

helper_method :employments, :form, :job_applications, :qualification_form_param_key, :sort, :sorted_job_applications

def reject
Expand Down Expand Up @@ -42,8 +44,79 @@ def update_status
redirect_to organisation_job_job_applications_path(vacancy.id), success: t(".#{status}", name: job_application.name)
end

require "zip"

def download_selected
# This eliminates all the N+1 issues, but PDF generation still takes ~1.5 seconds per application
# writing a text string to the PDF seems to take 200ms, and 'closing' the document ~500ms
downloads = Vacancy
.includes(:organisations, :publisher_organisation)
.includes(job_applications: [:qualifications, :employments, :training_and_cpds, :references, { jobseeker: :jobseeker_profile }])
.find(vacancy.id)
.job_applications.select { |job_application| params[:applications].include?(job_application.id) }

stringio = Zip::OutputStream.write_buffer do |zio|
downloads.each do |job_application|
zio.put_next_entry "job_application_#{job_application.id}.pdf"
logger.debug "generate #{job_application.id}.pdf"
pdf = JobApplicationPdfGenerator.new(job_application, vacancy).generate
logger.debug "render #{job_application.id}.pdf"
zio.write pdf.render
logger.debug "finished #{job_application.id}.pdf"
end
end
send_data(
stringio.string,
filename: "applications_#{vacancy.id}.zip",
type: "application/zip",
disposition: "inline",
)
# This would seem to do streaming, but the User experience seems very similar
# and also it doesn't produce a valid Zip file
# send_stream(
# filename: "applications_#{vacancy.id}.zip",
# type: "application/zip",
# disposition: "inline",
# ) do |stream|
# io = StringIO.new
# pos = 0
# Zip::OutputStream.write_buffer(io) do |zio|
# downloads.each do |job_application|
# zio.put_next_entry "job_application_#{job_application.id}.pdf"
# zio.write JobApplicationPdfGenerator.new(job_application, vacancy).generate.render
#
# io.seek pos
# stream.write io.read
# pos = io.size
# io.seek pos
# end
# end
# io.seek pos
# stream.write io.read
# end
end

private

def generate_zip(downloads)
Enumerator.new { |yielder|
io = StringIO.new
pos = 0
Zip::OutputStream.write_buffer(io) do |zio|
downloads.each do |job_application|
zio.put_next_entry "job_application_#{job_application.id}.pdf"
zio.write JobApplicationPdfGenerator.new(job_application, vacancy).generate.render

io.seek pos
yielder << io.read
pos = io.size
end
end
io.seek pos
yielder << io.read
}.lazy
end

def job_applications
@job_applications ||= vacancy.job_applications.not_draft
end
Expand Down
11 changes: 7 additions & 4 deletions app/helpers/pdf_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ def add_section_title(pdf, title)
end

def render_table(pdf, data)
pdf.table(data, cell_style: { borders: [] }) do
cells.padding = 12
cells.borders = []
pdf.table(data, cell_style: { borders: [], padding: 12 }) do
columns(0).font_style = :bold
columns(0).align = :left
columns(1).align = :left
columns(0).width = 150
end
# data.each do |description, value|
# pdf.text description, style: :bold
# pdf.draw_text value, at: [150, pdf.cursor]
# pdf.move_down 30
# end
end

def add_headers(pdf)
Expand Down Expand Up @@ -153,7 +156,7 @@ def add_employment_history(pdf)
if job_application.employments.none?
render_no_employment_message(pdf)
else
job_application.employments.sort_by { |r| r[:started_on] }.reverse.each_with_index do |employment, _index|
job_application.employments.sort_by { |r| r[:started_on] }.reverse_each do |employment|
render_employment_entry(pdf, employment)
render_employment_break(pdf, employment)
render_unexplained_gap(pdf, employment)
Expand Down
8 changes: 5 additions & 3 deletions app/models/vacancy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@ def external?
end

def organisation
return organisations.first if organisations.one?

organisations.find(&:trust?) || publisher_organisation || organisations.first&.school_groups&.first
if organisations.size == 1
organisations.first
else
organisations.detect(&:trust?) || publisher_organisation || organisations.first&.school_groups&.first
end
end

def location
Expand Down
15 changes: 15 additions & 0 deletions app/services/job_application_pdf_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,36 @@ def initialize(job_application, vacancy)
end

def generate
logger = Rails.logger
Prawn::Document.new do |pdf|
logger.debug("start - update_font_family")
update_font_family(pdf)
logger.debug("add_image_to_first_page")
add_image_to_first_page(pdf)
logger.debug("add_headers")
add_headers(pdf)
pdf.stroke_horizontal_rule
logger.debug("add_personal_details")
add_personal_details(pdf)
logger.debug("add_professional_status")
add_professional_status(pdf)
logger.debug("add_qualifications")
add_qualifications(pdf)
logger.debug("add_training_and_cpds")
add_training_and_cpds(pdf)
logger.debug("add_employment_history")
add_employment_history(pdf)
logger.debug("add_personal_statement")
add_personal_statement(pdf)
logger.debug("add_references")
add_references(pdf)
logger.debug("add_ask_for_support")
add_ask_for_support(pdf)
logger.debug("add_declarations")
add_declarations(pdf)
logger.debug("add_footers")
add_footers(pdf)
logger.debug("done")
end
end

Expand Down
64 changes: 47 additions & 17 deletions app/views/publishers/vacancies/job_applications/index.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,56 @@
.govuk-grid-row
.govuk-grid-column-full
- if job_applications.any? && vacancy.within_data_access_period?
= govuk_summary_list do |summary_list|
- sorted_job_applications.each do |application|
- summary_list.with_row classes: "application-#{application.status}" do |row|
- row.with_key do
= job_application_view_applicant(vacancy, application)

- row.with_value do
= publisher_job_application_status_tag(application.status)
dl
dt = "#{t('.received')}:"
dd = application.submitted_at.strftime("%d %B %Y at %H:%M")

- if application.status.in?(%w[reviewed shortlisted submitted])
- if application.reviewed? || application.submitted?
- row.with_action text: t("buttons.shortlist"), href: organisation_job_job_application_shortlist_path(vacancy.id, application.id), visually_hidden_text: " #{application.name}"
- row.with_action text: t("buttons.reject"), href: organisation_job_job_application_reject_path(vacancy.id, application.id), visually_hidden_text: " #{application.name}"
= form_with url: download_selected_organisation_job_job_applications_path(vacancy.id) do |f|
div data-module="moj-multi-select" data-multi-select-checkbox="#select-everything"
= govuk_summary_list(html_attributes: { id: "select-everything"}) do |summary_list|
- summary_list.with_row do |row|
- row.with_key do
= f.govuk_check_box :ignore,
:ignore,
class: "moj-multi-select__checkbox",
label: { text: "Name" }

/ - lunch_options = [ \
/ OpenStruct.new(id: 1, name: 'Salad', description: 'Lettuce, tomato and cucumber'), \
/ OpenStruct.new(id: 2, name: 'Jacket potato', description: 'With cheese and baked beans') \
/ ]
/ = f.govuk_collection_check_boxes :wednesday_lunch_ids,
/ lunch_options,
/ :id,
/ :name,
/ :description,
/ small: true,
/ legend: { text: "What would you like for lunch on Wednesday?" }

- sorted_job_applications.each do |application|
- summary_list.with_row classes: "application-#{application.status}" do |row|

- row.with_key do
div
= f.govuk_check_box(:applications,
application.id.to_sym,
class: "moj-multi-select__checkbox",
label: { text: application.name })

/ = job_application_view_applicant(vacancy, application)

- row.with_value do
= publisher_job_application_status_tag(application.status)
dl
dt = "#{t('.received')}:"
dd = application.submitted_at.strftime("%d %B %Y at %H:%M")

- if application.status.in?(%w[reviewed shortlisted submitted])
- if application.reviewed? || application.submitted?
- row.with_action text: t("buttons.shortlist"), href: organisation_job_job_application_shortlist_path(vacancy.id, application.id), visually_hidden_text: " #{application.name}"
- row.with_action text: t("buttons.reject"), href: organisation_job_job_application_reject_path(vacancy.id, application.id), visually_hidden_text: " #{application.name}"

= f.govuk_submit "Download Selected"

- elsif !vacancy.within_data_access_period?
= govuk_inset_text do
p = t(".expired_more_than_year")

- else
= render EmptySectionComponent.new title: t(".no_applicants")
= render EmptySectionComponent.new title: t(".no_applicants")
2 changes: 2 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@
config.i18n.raise

config.log_file_size = 100.megabytes

config.log_level = :debug
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@
get :reject
get :withdrawn
post :update_status
post :download_selected, on: :collection
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
],
"dependencies": {
"@hotwired/stimulus": "^3.2.2",
"@ministryofjustice/frontend": "^2.2.4",
"@sentry/browser": "8.35.0",
"@stimulus/polyfills": "^2.0.0",
"accessible-autocomplete": "^3.0.1",
Expand All @@ -21,6 +22,7 @@
"dfe-frontend": "^2.0.1",
"dompurify": "^3.1.7",
"govuk-frontend": "^5.7.1",
"jquery": "^3.6.0",
"leaflet": "^1.9.4",
"leaflet-gesture-handling": "^1.2.2",
"leaflet.markercluster": "^1.5.3",
Expand Down
Loading