Merge pull request #6246 from betagouv/6207-optim-poids-archives

6207 optime la page qui liste les archives
This commit is contained in:
krichtof 2021-06-09 11:01:27 +02:00 committed by GitHub
commit 7fb86c34e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 195 additions and 59 deletions

View file

@ -0,0 +1,15 @@
@import "constants";
table.archive-table {
.text-right {
text-align: right;
}
.center {
text-align: center;
}
td {
padding: 3 * $default-spacer $default-spacer;
}
}

View file

@ -4,12 +4,14 @@ module Instructeurs
def index def index
@procedure = procedure @procedure = procedure
@average_dossier_weight = procedure.average_dossier_weight
@archivable_months = archivable_months @count_dossiers_termines_by_month = Traitement.count_dossiers_termines_by_month(groupe_instructeurs)
@dossiers_termines = @procedure.dossiers.state_termine @nb_dossiers_termines = @count_dossiers_termines_by_month.sum { |count_by_month| count_by_month["count"] }
@poids_total = ProcedureArchiveService.procedure_files_size(@procedure)
groupe_instructeur = current_instructeur.groupe_instructeurs.where(procedure: @procedure.id).first @archives = Archive
@archives = Archive.for_groupe_instructeur(groupe_instructeur) .for_groupe_instructeur(groupe_instructeurs)
.to_a
end end
def create def create
@ -35,21 +37,20 @@ module Instructeurs
end end
end end
def archivable_months def procedure_id
start_date = procedure.published_at.to_date params[:procedure_id]
end_date = Time.zone.now.to_date end
(start_date...end_date) def groupe_instructeurs
.map(&:beginning_of_month) current_instructeur
.uniq .groupe_instructeurs
.reverse .where(procedure_id: procedure_id)
end end
def procedure def procedure
current_instructeur current_instructeur
.procedures .procedures
.for_download .find(procedure_id)
.find(params[:procedure_id])
end end
end end
end end

View file

@ -2,4 +2,12 @@ module ArchiveHelper
def can_generate_archive?(dossiers_termines, poids_total) def can_generate_archive?(dossiers_termines, poids_total)
dossiers_termines.count < 100 && poids_total < 1.gigabyte dossiers_termines.count < 100 && poids_total < 1.gigabyte
end end
def estimate_weight(archive, nb_dossiers_termines, average_dossier_weight)
if archive.present? && archive.available?
archive.file.byte_size
else
nb_dossiers_termines * average_dossier_weight
end
end
end end

View file

@ -688,6 +688,20 @@ class Procedure < ApplicationRecord
draft_revision.deep_clone(include: [:revision_types_de_champ, :revision_types_de_champ_private]) draft_revision.deep_clone(include: [:revision_types_de_champ, :revision_types_de_champ_private])
end end
def average_dossier_weight
if dossiers.termine.any?
dossiers_sample = dossiers.termine.limit(100)
total_size = Champ
.includes(piece_justificative_file_attachment: :blob)
.where(type: Champs::PieceJustificativeChamp.to_s, dossier: dossiers_sample)
.sum('active_storage_blobs.byte_size')
total_size / dossiers_sample.length
else
nil
end
end
private private
def before_publish def before_publish

View file

@ -18,4 +18,21 @@ class Traitement < ApplicationRecord
.where('dossiers.state' => Dossier::TERMINE) .where('dossiers.state' => Dossier::TERMINE)
.where("traitements.processed_at + (procedures.duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: Dossier::INTERVAL_BEFORE_EXPIRATION }) .where("traitements.processed_at + (procedures.duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: Dossier::INTERVAL_BEFORE_EXPIRATION })
end end
def self.count_dossiers_termines_by_month(groupe_instructeurs)
last_traitements_per_dossier = Traitement
.select('max(traitements.processed_at) as processed_at')
.where(dossier: Dossier.termine.where(groupe_instructeur: groupe_instructeurs))
.group(:dossier_id)
.to_sql
sql = <<~EOF
select date_trunc('month', r1.processed_at) as month, count(r1.processed_at)
from (#{last_traitements_per_dossier}) as r1
group by date_trunc('month', r1.processed_at)
order by month desc
EOF
ActiveRecord::Base.connection.execute(sql)
end
end end

View file

@ -5,7 +5,7 @@
'Archives'] } 'Archives'] }
.container .container
%h1 Archives %h1.mb-2 Archives
.card.featured .card.featured
.card-title Gestion de vos archives .card-title Gestion de vos archives
@ -21,29 +21,25 @@
= link_to 'la documentation', ARCHIVAGE_DOC_URL = link_to 'la documentation', ARCHIVAGE_DOC_URL
afin de voir les options à votre disposition pour mettre en place un système darchive. afin de voir les options à votre disposition pour mettre en place un système darchive.
%table.table.hoverable %table.table.hoverable.archive-table
%thead %thead
%tr %tr
%th &nbsp; %th &nbsp;
%th Nombre de dossiers terminés %th.text-right Nombre de dossiers terminés
%th Poids estimé %th.text-right Poids estimé
%th Télécharger %th.center Télécharger
%tbody %tbody
- if can_generate_archive?(@dossiers_termines, @poids_total)
%tr %tr
- matching_archive = @archives.find_by(time_span_type: 'everything') - matching_archive = @archives.find { |archive| archive.time_span_type == 'everything' }
- weight = estimate_weight(matching_archive, @nb_dossiers_termines, @average_dossier_weight)
%td %td
Tous les dossiers Tous les dossiers
%td %td.text-right
= @dossiers_termines.count = @nb_dossiers_termines
%td %td.text-right
- if matching_archive.present? && matching_archive.available?
- weight = matching_archive.file.byte_size
- else
- weight = @poids_total
= number_to_human_size(weight) = number_to_human_size(weight)
%td %td.center
- if matching_archive.try(&:available?) - if matching_archive.try(&:available?)
= link_to url_for(matching_archive.file), class: 'button primary' do = link_to url_for(matching_archive.file), class: 'button primary' do
%span.icon.download-white %span.icon.download-white
@ -51,29 +47,28 @@
- elsif matching_archive.try(&:pending?) - elsif matching_archive.try(&:pending?)
%span.icon.retry %span.icon.retry
= t(:archive_pending_html, created_period: time_ago_in_words(matching_archive.created_at), scope: [:instructeurs, :procedure]) = t(:archive_pending_html, created_period: time_ago_in_words(matching_archive.created_at), scope: [:instructeurs, :procedure])
- elsif @dossiers_termines.count > 0 - elsif @nb_dossiers_termines == 0
Rien à télécharger !
- elsif weight < 1.gigabyte
= link_to instructeur_archives_path(@procedure, type: 'everything'), method: :post, class: "button" do = link_to instructeur_archives_path(@procedure, type: 'everything'), method: :post, class: "button" do
%span.icon.new-folder %span.icon.new-folder
Demander la création Demander la création
- else - else
Rien à télécharger ! Archive trop volumineuse
- @archivable_months.each do |month| - @count_dossiers_termines_by_month.each do |count_by_month|
- dossiers_termines = @procedure.dossiers.processed_in_month(month) - month = count_by_month["month"].to_date
- nb_dossiers_termines = dossiers_termines.count - nb_dossiers_termines = count_by_month["count"]
- matching_archive = @archives.find_by(time_span_type: 'monthly', month: month) - matching_archive = @archives.find { |archive| archive.time_span_type == 'monthly' && archive.month == month }
- weight = estimate_weight(matching_archive, nb_dossiers_termines, @average_dossier_weight)
%tr %tr
%td %td
= I18n.l(month, format: "%B %Y") = I18n.l(month, format: "%B %Y").capitalize
%td %td.text-right
= nb_dossiers_termines = nb_dossiers_termines
%td %td.text-right
- if matching_archive.present? && matching_archive.available?
- weight = matching_archive.file.byte_size
- else
- weight = ProcedureArchiveService::dossiers_files_size(dossiers_termines)
= number_to_human_size(weight) = number_to_human_size(weight)
%td %td.center
- if nb_dossiers_termines > 0
- if matching_archive.present? - if matching_archive.present?
- if matching_archive.status == 'generated' && matching_archive.file.attached? - if matching_archive.status == 'generated' && matching_archive.file.attached?
= link_to url_for(matching_archive.file), class: 'button primary' do = link_to url_for(matching_archive.file), class: 'button primary' do
@ -82,10 +77,10 @@
- else - else
%span.icon.retry %span.icon.retry
= t(:archive_pending_html, created_period: time_ago_in_words(matching_archive.created_at), scope: [:instructeurs, :procedure]) = t(:archive_pending_html, created_period: time_ago_in_words(matching_archive.created_at), scope: [:instructeurs, :procedure])
- else - elsif weight < 1.gigabyte
= link_to instructeur_archives_path(@procedure, type:'monthly', month: month.strftime('%Y-%m')), method: :post, class: "button" do = link_to instructeur_archives_path(@procedure, type:'monthly', month: month.strftime('%Y-%m')), method: :post, class: "button" do
%span.icon.new-folder %span.icon.new-folder
Démander la création Démander la création
- else - else
Rien à télécharger ! Archive trop volumineuse

View file

@ -25,7 +25,7 @@ describe Instructeurs::ArchivesController, type: :controller do
it 'displays archives' do it 'displays archives' do
get :index, { params: { procedure_id: procedure1.id } } get :index, { params: { procedure_id: procedure1.id } }
expect(assigns(:dossiers_termines).size).to eq(3) expect(assigns(:nb_dossiers_termines).size).to eq(8)
expect(assigns(:archives)).to eq([archive1]) expect(assigns(:archives)).to eq([archive1])
end end
end end

View file

@ -138,8 +138,12 @@ FactoryBot.define do
factory :champ_piece_justificative, class: 'Champs::PieceJustificativeChamp' do factory :champ_piece_justificative, class: 'Champs::PieceJustificativeChamp' do
type_de_champ { association :type_de_champ_piece_justificative, procedure: dossier.procedure } type_de_champ { association :type_de_champ_piece_justificative, procedure: dossier.procedure }
after(:build) do |champ, _evaluator| transient do
champ.piece_justificative_file.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain") size { 4 }
end
after(:build) do |champ, evaluator|
champ.piece_justificative_file.attach(io: StringIO.new("x" * evaluator.size), filename: "toto.txt", content_type: "text/plain")
end end
end end

View file

@ -0,0 +1,24 @@
describe ArchiveHelper, type: :helper do
describe ".estimate_weight" do
let(:nb_dossiers_termines) { 5 }
let(:average_dossier_weight) { 2 }
context 'when archive exist and available' do
let(:archive) { build(:archive, :generated) }
before do
allow_any_instance_of(Archive).to receive(:available?).and_return(true)
end
it 'returns real archive weight' do
expect(estimate_weight(archive, nb_dossiers_termines, average_dossier_weight)).to eq nil
end
end
context 'when archive has not been created' do
let(:archive) { nil }
it 'returns estimation' do
expect(estimate_weight(archive, nb_dossiers_termines, average_dossier_weight)).to eq 10
end
end
end
end

View file

@ -1069,4 +1069,25 @@ describe Procedure do
expect(procedure.destroy).to be_truthy expect(procedure.destroy).to be_truthy
end end
end end
describe '#average_dossier_weight' do
let(:procedure) { create(:procedure, :published) }
before do
create_dossier_with_pj_of_size(4, procedure)
create_dossier_with_pj_of_size(5, procedure)
create_dossier_with_pj_of_size(6, procedure)
end
it 'estimates average dossier weight' do
expect(procedure.reload.average_dossier_weight).to eq 5
end
private
def create_dossier_with_pj_of_size(size, procedure)
dossier = create(:dossier, :accepte, procedure: procedure)
create(:champ_piece_justificative, size: size, dossier: dossier)
end
end
end end

View file

@ -0,0 +1,37 @@
describe Traitement do
describe '#count_dossiers_termines_by_month' do
let(:procedure) { create(:procedure, :published, groupe_instructeurs: [groupe_instructeurs]) }
let(:groupe_instructeurs) { create(:groupe_instructeur) }
let(:result) { Traitement.count_dossiers_termines_by_month(groupe_instructeurs) }
before do
create_dossier_for_month(procedure, 2021, 3)
create_dossier_for_month(procedure, 2021, 3)
create_dossier_for_month(procedure, 2021, 2)
Timecop.freeze(Time.zone.local(2021, 3, 5))
end
it 'count dossiers_termines by month' do
expect(count_for_month(result, 3)).to eq 2
expect(count_for_month(result, 2)).to eq 1
end
it 'returns descending order by month' do
expect(result[0]["month"].to_date.month).to eq 3
expect(result[1]["month"].to_date.month).to eq 2
end
end
private
def count_for_month(count_dossiers_termines_by_month, month)
count_dossiers_termines_by_month.find do |count_by_month|
count_by_month["month"].to_date.month == month
end["count"]
end
def create_dossier_for_month(procedure, year, month)
Timecop.freeze(Time.zone.local(year, month, 5))
create(:dossier, :accepte, :with_attestation, procedure: procedure)
end
end