feat(exports): implement admin export

This commit is contained in:
Martin 2022-07-04 16:13:15 +02:00 committed by mfo
parent 18eb241e1a
commit 87af7f3261
14 changed files with 218 additions and 12 deletions

View file

@ -1,9 +1,10 @@
class Dossiers::ExportComponent < ApplicationComponent
def initialize(procedure:, exports:, statut: nil, count: nil)
def initialize(procedure:, exports:, statut: nil, count: nil, export_url: nil)
@procedure = procedure
@exports = exports
@statut = statut
@count = count
@export_url = export_url
end
def exports
@ -11,7 +12,7 @@ class Dossiers::ExportComponent < ApplicationComponent
end
def download_export_path(export_format:, force_export: false, no_progress_notification: nil)
download_export_instructeur_procedure_path(@procedure,
@export_url.call(@procedure,
export_format: export_format,
statut: @statut,
force_export: force_export,

View file

@ -4,6 +4,7 @@ module Administrateurs
helper_method :create_archive_url
def index
@exports = Export.find_for_groupe_instructeurs(all_groupe_instructeurs.map(&:id), nil)
@average_dossier_weight = @procedure.average_dossier_weight
@count_dossiers_termines_by_month = Traitement.count_dossiers_termines_by_month(all_groupe_instructeurs)
@archives = Archive.for_groupe_instructeur(all_groupe_instructeurs).to_a

View file

@ -0,0 +1,70 @@
module Administrateurs
class ExportsController < AdministrateurController
before_action :retrieve_procedure, only: [:download]
def download
export = Export.find_or_create_export(export_format, all_groupe_instructeurs, **export_options)
if export.ready? && export.old? && force_export?
export.destroy
export = Export.find_or_create_export(export_format, all_groupe_instructeurs, **export_options)
end
if export.ready?
respond_to do |format|
format.turbo_stream do
@dossiers_count = export.count
assign_exports
flash.notice = "Lexport au format \"#{export_format}\" est prêt. Vous pouvez le <a href=\"#{export.file.service_url}\">télécharger</a>"
end
format.html do
redirect_to export.file.service_url
end
end
else
respond_to do |format|
notice_message = "Nous générons cet export. Veuillez revenir dans quelques minutes pour le télécharger."
format.turbo_stream do
@dossiers_count = export.count
assign_exports
if !params[:no_progress_notification]
flash.notice = notice_message
end
end
format.html do
redirect_to admin_procedure_archives_url(@procedure), notice: notice_message
end
end
end
end
private
def export_format
@export_format ||= params[:export_format]
end
def force_export?
@force_export ||= params[:force_export].present?
end
def export_options
@export_options ||= {
time_span_type: params[:time_span_type],
statut: params[:statut],
procedure_presentation: nil
}.compact
end
def all_groupe_instructeurs
@procedure.groupe_instructeurs
end
def assign_exports
@exports = Export.find_for_groupe_instructeurs(all_groupe_instructeurs.map(&:id), nil)
end
end
end

View file

@ -7,6 +7,7 @@
.container
%h1.mb-2
Archives
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, export_url: method(:download_admin_procedure_exports_path))
= render partial: "shared/archives/notice"
= render partial: "shared/archives/table", locals: {count_dossiers_termines_by_month: @count_dossiers_termines_by_month, archives: @archives, average_dossier_weight: @average_dossier_weight, procedure: @procedure }

View file

@ -0,0 +1,3 @@
- if @can_download_dossiers
= turbo_stream.update_all '.procedure-actions' do
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, count: @dossiers_count, export_url: method(:admin_procedure_exports_path))

View file

@ -11,7 +11,7 @@
.procedure-actions
- if @can_download_dossiers
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports)
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, export_url: method(:download_export_instructeur_procedure_path))
.container.flex= render partial: "tabs", locals: { procedure: @procedure,
statut: @statut,

View file

@ -1,7 +1,7 @@
- if @can_download_dossiers
- if @statut.nil?
= turbo_stream.update_all '.procedure-actions' do
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports)
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, export_url: method(:download_export_instructeur_procedure_path))
- else
= turbo_stream.update_all '.dossiers-export' do
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count)
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, export_url: method(:download_export_instructeur_procedure_path))

View file

@ -11,7 +11,7 @@
.procedure-actions
- if @can_download_dossiers
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports)
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, export_url: method(:download_export_instructeur_procedure_path))
.container.flex= render partial: "tabs", locals: { procedure: @procedure,
statut: @statut,
@ -65,7 +65,7 @@
= render partial: "dossiers_filter", locals: { procedure: @procedure, procedure_presentation: @procedure_presentation, current_filters: @current_filters, statut: @statut, filterable_fields_for_select: @filterable_fields_for_select }
- if @dossiers_count > 0
.dossiers-export
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count)
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, export_url: method(:download_export_instructeur_procedure_path))
%table.table.dossiers-table.hoverable
%thead

View file

@ -1,5 +1,25 @@
{
"ignored_warnings": [
{
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "170b506bd3d25eb50f464703f9d993bf2e124d5cbc8478ce9d9d06a15b4bc55e",
"check_name": "Redirect",
"message": "Possible unprotected redirect",
"file": "app/controllers/instructeurs/exports_controller.rb",
"line": 26,
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
"code": "redirect_to(Export.find_or_create_export(export_format, groupe_instructeurs, **export_options).file.service_url)",
"render_path": null,
"location": {
"type": "method",
"class": "Instructeurs::ExportsController",
"method": "download"
},
"user_input": "Export.find_or_create_export(export_format, groupe_instructeurs, **export_options).file.service_url",
"confidence": "High",
"note": ""
},
{
"warning_type": "Cross-Site Scripting",
"warning_code": 2,
@ -112,6 +132,26 @@
"confidence": "High",
"note": ""
},
{
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "8f8133f0679f6301e098c4b9c61c2217224126856963abd02cdc5e54efb4e818",
"check_name": "Redirect",
"message": "Possible unprotected redirect",
"file": "app/controllers/administrateurs/exports_controller.rb",
"line": 22,
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
"code": "redirect_to(Export.find_or_create_export(export_format, all_groupe_instructeurs, **export_options).file.service_url)",
"render_path": null,
"location": {
"type": "method",
"class": "Administrateurs::ExportsController",
"method": "download"
},
"user_input": "Export.find_or_create_export(export_format, all_groupe_instructeurs, **export_options).file.service_url",
"confidence": "High",
"note": ""
},
{
"warning_type": "SQL Injection",
"warning_code": 0,
@ -153,6 +193,6 @@
"note": ""
}
],
"updated": "2022-06-15 16:15:28 +0200",
"updated": "2022-07-08 13:14:19 +0200",
"brakeman_version": "5.2.2"
}

View file

@ -0,0 +1,7 @@
class CustomRoutes
def self.load
Rails.application.routes.draw do
get "/provider_url" => redirect("https://numerique.gouv.fr/"), as: :provider
end
end
end

View file

@ -339,6 +339,8 @@ Rails.application.routes.draw do
scope module: 'instructeurs', as: 'instructeur' do
resources :procedures, only: [:index, :show], param: :procedure_id do
member do
resources :archives, only: [:index, :create]
resources :groupes, only: [:index, :show], controller: 'groupe_instructeurs' do
member do
post 'add_instructeur'
@ -396,8 +398,6 @@ Rails.application.routes.draw do
get 'telecharger_pjs' => 'dossiers#telecharger_pjs'
end
end
resources :archives, only: [:index, :create]
end
end
end
@ -409,6 +409,13 @@ Rails.application.routes.draw do
scope module: 'administrateurs', path: 'admin', as: 'admin' do
resources :procedures do
resources :archives, only: [:index, :create]
resources :exports, only: [] do
collection do
get 'download'
post 'download'
end
end
collection do
get 'new_from_existing'
end

View file

@ -0,0 +1,67 @@
describe Administrateurs::ExportsController, type: :controller do
describe '#download' do
let(:administrateur) { create(:administrateur) }
before { sign_in(administrateur.user) }
subject do
get :download, params: { export_format: :csv, procedure_id: procedure.id }
end
context 'when the procedure does not belongs to admin' do
let!(:procedure) { create(:procedure, administrateurs: [create(:administrateur)]) }
it 'blocks' do
is_expected.to have_http_status(:not_found)
end
end
context 'when admin is allowed' do
let!(:procedure) { create(:procedure, administrateurs: [administrateur]) }
context 'when the export is does not exist' do
it 'displays an notice' do
is_expected.to redirect_to(admin_procedure_archives_url(procedure))
expect(flash.notice).to be_present
end
it { expect { subject }.to change(Export, :count).by(1) }
end
context 'when the export is not ready' do
before do
create(:export, groupe_instructeurs: procedure.groupe_instructeurs)
end
it 'displays an notice' do
is_expected.to redirect_to(admin_procedure_archives_url(procedure))
expect(flash.notice).to be_present
end
end
context 'when the export is ready' do
let(:export) { create(:export, groupe_instructeurs: procedure.groupe_instructeurs) }
before do
export.file.attach(io: StringIO.new('export'), filename: 'file.csv')
end
it 'displays the download link' do
subject
expect(response.headers['Location']).to start_with("http://test.host/rails/active_storage/disk")
end
end
context 'when the turbo_stream format is used' do
before do
post :download,
params: { export_format: :csv, procedure_id: procedure.id },
format: :turbo_stream
end
it 'responds in the correct format' do
expect(response.media_type).to eq('text/vnd.turbo-stream.html')
expect(response).to have_http_status(:ok)
end
end
end
end
end

View file

@ -32,5 +32,13 @@ describe 'Creating a new procedure', js: true do
page.first(".archive-table .button").click
}.to have_enqueued_job(ArchiveCreationJob).with(procedure, an_instance_of(Archive), administrateur)
expect(page).to have_content("Votre demande a été prise en compte. Selon le nombre de dossiers, cela peut prendre de quelques minutes a plusieurs heures. Vous recevrez un courriel lorsque le fichier sera disponible.")
# check exports
click_on "Télécharger tous les dossiers"
expect {
click_on "Demander un export au format .xlsx"
expect(page).to have_content("Nous générons cet export. Veuillez revenir dans quelques minutes pour le télécharger.")
}.to have_enqueued_job(ExportJob).with(an_instance_of(Export))
end
end

View file

@ -120,9 +120,10 @@ describe 'Instructing a dossier:', js: true do
within(:css, '.dossiers-export') do
click_on "Demander un export au format .csv"
end
expect(page).to have_text('Nous générons cet export.')
expect(page).to have_text('Un export au format .csv est en train dêtre généré')
expect(page).to have_text('Nous générons cet export.')
click_on "Télécharger un dossier"
expect(page).to have_text('Un export au format .csv est en train dêtre généré')
perform_enqueued_jobs(only: ExportJob)
assert_performed_jobs 2
page.driver.browser.navigate.refresh