diff --git a/app/controllers/recherche_controller.rb b/app/controllers/recherche_controller.rb index 00626af60..f2d96ffb6 100644 --- a/app/controllers/recherche_controller.rb +++ b/app/controllers/recherche_controller.rb @@ -4,8 +4,7 @@ class RechercheController < ApplicationController PROJECTIONS = [ { "table" => 'procedure', "column" => 'libelle' }, { "table" => 'user', "column" => 'email' }, - { "table" => 'procedure', "column" => 'procedure_id' }, - { "table" => 'dossier', "column" => 'hidden_by_administration_at' } + { "table" => 'procedure', "column" => 'procedure_id' } ] def index diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 1202df9c8..3032e67c7 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -31,7 +31,7 @@ class StatsController < ApplicationController @procedures_in_the_last_4_months = last_four_months_serie(procedures, :published_at) @dossiers_cumulative = stat.dossiers_cumulative - @dossiers_in_the_last_4_months = stat.dossiers_in_the_last_4_months + @dossiers_in_the_last_4_months = format_keys_as_months(stat.dossiers_in_the_last_4_months) end def download @@ -136,11 +136,18 @@ class StatsController < ApplicationController end end + def format_keys_as_months(series) + series.transform_keys do |k| + date = k.is_a?(Date) ? k : (Date.parse(k) rescue k) + l(date, format: "%B %Y") + end + end + def last_four_months_serie(association, date_attribute) - association + series = association .group_by_month(date_attribute, last: 4, current: super_admin_signed_in?) .count - .transform_keys { |date| l(date, format: "%B %Y") } + format_keys_as_months(series) end def cumulative_month_serie(association, date_attribute) diff --git a/app/helpers/string_to_html_helper.rb b/app/helpers/string_to_html_helper.rb index 8806863f6..8fc99684f 100644 --- a/app/helpers/string_to_html_helper.rb +++ b/app/helpers/string_to_html_helper.rb @@ -1,5 +1,6 @@ module StringToHtmlHelper def string_to_html(str, wrapper_tag = 'p') + return nil if str.blank? html_formatted = simple_format(str, {}, { wrapper_tag: wrapper_tag }) with_links = Anchored::Linker.auto_link(html_formatted, target: '_blank', rel: 'noopener') sanitize(with_links, attributes: ['target', 'rel', 'href']) diff --git a/app/models/stat.rb b/app/models/stat.rb index ce0109c5b..097c0c592 100644 --- a/app/models/stat.rb +++ b/app/models/stat.rb @@ -80,11 +80,7 @@ class Stat < ApplicationRecord association.group_by_month(date_attribute, last: 4, current: false).count end - month_serie(sum_hashes(*timeseries)) - end - - def month_serie(date_serie) - date_serie.keys.sort.each_with_object({}) { |date, h| h[I18n.l(date, format: "%B %Y")] = date_serie[date] } + sum_hashes(*timeseries).sort.to_h end def cumulative_month_serie(associations_with_date_attribute) diff --git a/app/services/dossier_projection_service.rb b/app/services/dossier_projection_service.rb index bb03493ba..52865c6ca 100644 --- a/app/services/dossier_projection_service.rb +++ b/app/services/dossier_projection_service.rb @@ -1,5 +1,5 @@ class DossierProjectionService - class DossierProjection < Struct.new(:dossier_id, :state, :archived, :hidden_by_user_at, :columns) + class DossierProjection < Struct.new(:dossier_id, :state, :archived, :hidden_by_user_at, :hidden_by_administration_at, :columns) end TABLE = 'table' @@ -21,8 +21,8 @@ class DossierProjectionService state_field = { TABLE => 'self', COLUMN => 'state' } archived_field = { TABLE => 'self', COLUMN => 'archived' } hidden_by_user_at_field = { TABLE => 'self', COLUMN => 'hidden_by_user_at' } - - ([state_field, archived_field, hidden_by_user_at_field] + fields) # the view needs state and archived dossier attributes + hidden_by_administration_at_field = { TABLE => 'self', COLUMN => 'hidden_by_administration_at' } + ([state_field, archived_field, hidden_by_user_at_field, hidden_by_administration_at_field] + fields) # the view needs state and archived dossier attributes .each { |f| f[:id_value_h] = {} } .group_by { |f| f[TABLE] } # one query per table .each do |table, fields| @@ -46,7 +46,7 @@ class DossierProjectionService .pluck(:id, *fields.map { |f| f[COLUMN].to_sym }) .each do |id, *columns| fields.zip(columns).each do |field, value| - if [state_field, archived_field, hidden_by_user_at_field].include?(field) + if [state_field, archived_field, hidden_by_user_at_field, hidden_by_administration_at_field].include?(field) field[:id_value_h][id] = value else field[:id_value_h][id] = value&.strftime('%d/%m/%Y') # other fields are datetime @@ -100,6 +100,7 @@ class DossierProjectionService state_field[:id_value_h][dossier_id], archived_field[:id_value_h][dossier_id], hidden_by_user_at_field[:id_value_h][dossier_id], + hidden_by_administration_at_field[:id_value_h][dossier_id], fields.map { |f| f[:id_value_h][dossier_id] } ) end diff --git a/app/views/instructeurs/dossiers/_header_actions.html.haml b/app/views/instructeurs/dossiers/_header_actions.html.haml index 9bfb488fa..47f7b4893 100644 --- a/app/views/instructeurs/dossiers/_header_actions.html.haml +++ b/app/views/instructeurs/dossiers/_header_actions.html.haml @@ -28,8 +28,7 @@ state: dossier.state, archived: dossier.archived, dossier_is_followed: current_instructeur&.follow?(dossier), - close_to_expiration: dossier.close_to_expiration?, - recently_deleted: dossier.hidden_by_administration? } + close_to_expiration: dossier.close_to_expiration? } .state-button diff --git a/app/views/instructeurs/procedures/_dossier_actions.html.haml b/app/views/instructeurs/procedures/_dossier_actions.html.haml index 49a142173..aab58893a 100644 --- a/app/views/instructeurs/procedures/_dossier_actions.html.haml +++ b/app/views/instructeurs/procedures/_dossier_actions.html.haml @@ -21,13 +21,7 @@ %span.icon.archive .dropdown-description Archiver le dossier - - if recently_deleted - %li.danger - = link_to restore_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment restaurer le dossier #{dossier_id}" } do - %span.icon.reply - .dropdown-description - = t('views.instructeurs.dossiers.restore') - - else + %li.danger = link_to supprimer_dossier_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment supprimer le dossier #{dossier_id} ? Cette action est irréversible. \nNous vous suggérons de télécharger le dossier au format PDF au préalable." } do %span.icon.delete diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index edfda99b3..6734bd8de 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -132,14 +132,19 @@ %td.status-col %a.cell-link{ href: path }= status_badge(p.state) - %td.action-col.follow-col= render partial: 'dossier_actions', - locals: { procedure_id: @procedure.id, - dossier_id: p.dossier_id, - state: p.state, - archived: p.archived, - dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id), - close_to_expiration: @statut == 'expirant', - recently_deleted: @statut == 'supprimes_recemment' } + - if @statut == 'supprimes_recemment' + %td.action-col.follow-col + = link_to restore_instructeur_dossier_path(@procedure, p.dossier_id), method: :patch, class: "button primary" do + = t('views.instructeurs.dossiers.restore') + + - else + %td.action-col.follow-col= render partial: 'dossier_actions', + locals: { procedure_id: @procedure.id, + dossier_id: p.dossier_id, + state: p.state, + archived: p.archived, + dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id), + close_to_expiration: @statut == 'expirant' } = pagination - else diff --git a/app/views/recherche/_hidden_dossier.html.haml b/app/views/recherche/_hidden_dossier.html.haml new file mode 100644 index 000000000..7a5de7011 --- /dev/null +++ b/app/views/recherche/_hidden_dossier.html.haml @@ -0,0 +1,17 @@ +%td.folder-col + %p.cell-link + %span.icon.folder + +%td.number-col + %p.cell-link= p.dossier_id + +%td + %p.cell-link= procedure_libelle + +%td + %p.cell-link + = user_email + = "- #{t('views.instructeurs.dossiers.deleted_by_administration')}" if p.hidden_by_administration_at.present? + +%td.status-col + %p.cell-link= status_badge(p.state) diff --git a/app/views/recherche/index.html.haml b/app/views/recherche/index.html.haml index d84be0712..72c91916e 100644 --- a/app/views/recherche/index.html.haml +++ b/app/views/recherche/index.html.haml @@ -20,13 +20,14 @@ %th.action-col.follow-col %tbody - @projected_dossiers.each do |p| - - procedure_libelle, user_email, procedure_id, hidden_by_administration = p.columns + - procedure_libelle, user_email, procedure_id = p.columns - instructeur_dossier = @instructeur_dossiers_ids.include?(p.dossier_id) - expert_dossier = @dossier_avis_ids_h[p.dossier_id].present? + - hidden_by_administration = p.hidden_by_administration_at.present? - instructeur_and_expert_dossier = instructeur_dossier && expert_dossier - path = instructeur_dossier ? instructeur_dossier_path(procedure_id, p.dossier_id) : expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id]) - %tr + %tr{ class: [p.hidden_by_administration_at.present? && "file-hidden-by-user"] } - if instructeur_and_expert_dossier %td.folder-col.cell-link %span.icon.folder @@ -39,7 +40,11 @@ %td.status-col .cell-link= status_badge(p.state) + - elsif hidden_by_administration + = render partial: "recherche/hidden_dossier", locals: {p: p, procedure_libelle: procedure_libelle, user_email: user_email} + - else + %td.folder-col %a.cell-link{ href: path } %span.icon.folder @@ -76,14 +81,19 @@ Donner mon avis - elsif instructeur_dossier - %td.action-col.follow-col= render partial: "instructeurs/procedures/dossier_actions", + - if hidden_by_administration + %td.action-col.follow-col + = link_to restore_instructeur_dossier_path(procedure_id, p.dossier_id), method: :patch, class: "button primary" do + = t('views.instructeurs.dossiers.restore') + + - else + %td.action-col.follow-col= render partial: "instructeurs/procedures/dossier_actions", locals: { procedure_id: procedure_id, dossier_id: p.dossier_id, state: p.state, archived: p.archived, dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id), - close_to_expiration: nil, - recently_deleted: hidden_by_administration.blank? } + close_to_expiration: nil } - else %td diff --git a/app/views/shared/attachment/_edit.html.haml b/app/views/shared/attachment/_edit.html.haml index d61f555b9..124680296 100644 --- a/app/views/shared/attachment/_edit.html.haml +++ b/app/views/shared/attachment/_edit.html.haml @@ -29,6 +29,7 @@ %p.attachment-error-title Une erreur s’est produite pendant l’envoi du fichier. %p.attachment-error-description + Une erreur inconnue s'est produite pendant l'envoi du fichier = button_tag type: 'button', class: 'button attachment-error-retry', data: { 'input-target': ".attachment-input-#{attachment_id}" } do %span.icon.retry Ré-essayer diff --git a/app/views/shared/dossiers/_champ_row.html.haml b/app/views/shared/dossiers/_champ_row.html.haml index 4392289d8..edb7f0788 100644 --- a/app/views/shared/dossiers/_champ_row.html.haml +++ b/app/views/shared/dossiers/_champ_row.html.haml @@ -57,7 +57,7 @@ - when TypeDeChamp.type_champs.fetch(:number) = number_with_html_delimiter(c.to_s) - else - = format_text_value(c.to_s) + = format_text_value(c.to_s) unless c.blank? - if c.type_champ != TypeDeChamp.type_champs.fetch(:header_section) %td.updated-at diff --git a/config/locales/en.yml b/config/locales/en.yml index e6668fac8..4a482a470 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -140,7 +140,8 @@ en: archived_dossier: "This file will be kept for an additional month" delete_dossier: "Delete file" deleted_by_user: "File deleted by user" - restore: "Restore the file" + deleted_by_administration: "File deleted by administration" + restore: "Restore" avis: introduction_file_explaination: "File attached to the request for advice" users: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4c230ae37..b66c9e56f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -137,7 +137,8 @@ fr: archived_dossier: "Le dossier sera conservé 1 mois supplémentaire" delete_dossier: "Supprimer le dossier" deleted_by_user: "Dossier supprimé par l'usager" - restore: "Restaurer le dossier" + deleted_by_administration: "Dossier supprimé par l'administration" + restore: "Restaurer" avis: introduction_file_explaination: "Fichier joint à la demande d’avis" users: diff --git a/spec/helpers/string_to_html_helper_spec.rb b/spec/helpers/string_to_html_helper_spec.rb index 847b6def7..545bc0cbb 100644 --- a/spec/helpers/string_to_html_helper_spec.rb +++ b/spec/helpers/string_to_html_helper_spec.rb @@ -28,7 +28,7 @@ RSpec.describe StringToHtmlHelper, type: :helper do context "with empty decription" do let(:description) { nil } - it { is_expected.to eq('

') } + it { is_expected.to eq nil } end context "with a bad script" do diff --git a/spec/models/stat_spec.rb b/spec/models/stat_spec.rb index e9fc2edc2..41138d59a 100644 --- a/spec/models/stat_spec.rb +++ b/spec/models/stat_spec.rb @@ -57,24 +57,30 @@ describe Stat do create(:dossier, state: :en_construction, depose_at: i.months.ago) create(:deleted_dossier, dossier_id: i + 100, state: :en_construction, deleted_at: i.month.ago) end - rs = Stat.send(:cumulative_month_serie, [ - [Dossier.state_not_brouillon, :depose_at], - [DeletedDossier.where.not(state: :brouillon), :deleted_at] + s = Stat.new({ + dossiers_cumulative: + Stat.send(:cumulative_month_serie, [ + [Dossier.state_not_brouillon, :depose_at], + [DeletedDossier.where.not(state: :brouillon), :deleted_at] + ]) + }) + s.save! + s.reload + # Use `Hash#to_a` to also test the key ordering + expect(s.dossiers_cumulative.to_a).to eq([ + [formatted_n_months_ago(12), 2], + [formatted_n_months_ago(11), 4], + [formatted_n_months_ago(10), 6], + [formatted_n_months_ago(9), 8], + [formatted_n_months_ago(8), 10], + [formatted_n_months_ago(7), 12], + [formatted_n_months_ago(6), 14], + [formatted_n_months_ago(5), 16], + [formatted_n_months_ago(4), 18], + [formatted_n_months_ago(3), 20], + [formatted_n_months_ago(2), 22], + [formatted_n_months_ago(1), 24] ]) - expect(rs).to eq({ - 12 => 2, - 11 => 4, - 10 => 6, - 9 => 8, - 8 => 10, - 7 => 12, - 6 => 14, - 5 => 16, - 4 => 18, - 3 => 20, - 2 => 22, - 1 => 24 - }.transform_keys { |i| i.months.ago.beginning_of_month.to_date }) end end @@ -85,16 +91,22 @@ describe Stat do create(:dossier, state: :en_construction, depose_at: i.months.ago) create(:deleted_dossier, dossier_id: i + 100, state: :en_construction, deleted_at: i.month.ago) end - rs = Stat.send(:last_four_months_serie, [ - [Dossier.state_not_brouillon, :depose_at], - [DeletedDossier.where.not(state: :brouillon), :deleted_at] - ]) - expect(rs).to eq({ - "juillet 2021" => 2, - "août 2021" => 2, - "septembre 2021" => 2, - "octobre 2021" => 2 + s = Stat.new({ + dossiers_in_the_last_4_months: + Stat.send(:last_four_months_serie, [ + [Dossier.state_not_brouillon, :depose_at], + [DeletedDossier.where.not(state: :brouillon), :deleted_at] + ]) }) + s.save! + s.reload + # Use `Hash#to_a` to also test the key ordering + expect(s.dossiers_in_the_last_4_months.to_a).to eq([ + ['2021-07-01', 2], + ['2021-08-01', 2], + ['2021-09-01', 2], + ['2021-10-01', 2] + ]) end end end @@ -104,4 +116,8 @@ describe Stat do expect(Stat.send(:sum_hashes, *[{ a: 1, b: 2, d: 5 }, { a: 2, b: 3, c: 5 }])).to eq({ a: 3, b: 5, c: 5, d: 5 }) end end + + def formatted_n_months_ago(i) + i.months.ago.beginning_of_month.to_date.to_s + end end