diff --git a/app/controllers/concerns/create_avis_concern.rb b/app/controllers/concerns/create_avis_concern.rb index 8b7e55865..74965976c 100644 --- a/app/controllers/concerns/create_avis_concern.rb +++ b/app/controllers/concerns/create_avis_concern.rb @@ -17,6 +17,9 @@ module CreateAvisConcern allowed_dossiers += dossier.linked_dossiers_for(instructeur_or_expert) end + if (instructeur_or_expert.is_a?(Instructeur)) && !instructeur_or_expert.follows.exists?(dossier: dossier) + instructeur_or_expert.follow(dossier) + end create_results = Avis.create( expert_emails.flat_map do |email| user = User.create_or_promote_to_expert(email, SecureRandom.hex) diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 7dbdc2365..172891bc8 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -76,10 +76,11 @@ module Instructeurs @has_termine_notifications = notifications[:termines].present? @not_archived_notifications_dossier_ids = notifications[:en_cours] + notifications[:termines] - filtered_sorted_ids = procedure_presentation.filtered_sorted_ids(dossiers, dossiers_count, statut) + filtered_sorted_ids = procedure_presentation.filtered_sorted_ids(dossiers, statut, count: dossiers_count) page = params[:page].presence || 1 + @dossiers_count = filtered_sorted_ids.size @filtered_sorted_paginated_ids = Kaminari .paginate_array(filtered_sorted_ids) .page(page) @@ -137,8 +138,6 @@ module Instructeurs end def download_export - export_format = params[:export_format] - time_span_type = params[:time_span_type] || Export.time_span_types.fetch(:everything) groupe_instructeurs = current_instructeur .groupe_instructeurs .where(procedure: procedure) @@ -148,17 +147,19 @@ module Instructeurs .visible_by_administration .exists?(groupe_instructeur_id: groupe_instructeur_ids) - export = Export.find_or_create_export(export_format, time_span_type, groupe_instructeurs) + export = Export.find_or_create_export(export_format, groupe_instructeurs, **export_options) - if export.ready? && export.old? && params[:force_export] + if export.ready? && export.old? && force_export? export.destroy - export = Export.find_or_create_export(export_format, time_span_type, groupe_instructeurs) + export = Export.find_or_create_export(export_format, groupe_instructeurs, **export_options) end if export.ready? respond_to do |format| format.js do @procedure = procedure + @statut = export_options[:statut] + @dossiers_count = export.count assign_exports flash.notice = "L’export au format \"#{export_format}\" est prêt. Vous pouvez le télécharger" end @@ -173,6 +174,8 @@ module Instructeurs format.js do @procedure = procedure + @statut = export_options[:statut] + @dossiers_count = export.count assign_exports if !params[:no_progress_notification] flash.notice = notice_message @@ -261,7 +264,7 @@ module Instructeurs end def assign_exports - @exports = Export.find_for_groupe_instructeurs(groupe_instructeur_ids) + @exports = Export.find_for_groupe_instructeurs(groupe_instructeur_ids, procedure_presentation) end def assign_tos @@ -280,6 +283,22 @@ module Instructeurs @statut ||= (params[:statut].presence || 'a-suivre') end + 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: params[:statut].present? ? procedure_presentation : nil + }.compact + end + def procedure_id params[:procedure_id] end diff --git a/app/helpers/dossier_helper.rb b/app/helpers/dossier_helper.rb index 8a2582100..a0d9f1905 100644 --- a/app/helpers/dossier_helper.rb +++ b/app/helpers/dossier_helper.rb @@ -109,9 +109,23 @@ module DossierHelper "#{base_url}/entreprise/#{siren}" end - def exports_list(exports) - Export::FORMATS.map do |(format, time_span_type)| - [format, time_span_type, exports[format] && exports[format][time_span_type]] + def exports_list(exports, statut = nil) + if statut + Export::FORMATS.map do |item| + export = exports + .fetch(item.fetch(:format)) + .fetch(:statut) + .fetch(statut, nil) + item.merge(export: export) + end + else + Export::FORMATS_WITH_TIME_SPAN.map do |item| + export = exports + .fetch(item.fetch(:format)) + .fetch(:time_span_type) + .fetch(item.fetch(:time_span_type), nil) + item.merge(export: export) + end end end end diff --git a/app/models/export.rb b/app/models/export.rb index caef9ce48..a62872c5b 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -2,12 +2,15 @@ # # Table name: exports # -# id :bigint not null, primary key -# format :string not null -# key :text not null -# time_span_type :string default("everything"), not null -# created_at :datetime not null -# updated_at :datetime not null +# id :bigint not null, primary key +# format :string not null +# key :text not null +# procedure_presentation_snapshot :jsonb +# statut :string default("tous") +# time_span_type :string default("everything"), not null +# created_at :datetime not null +# updated_at :datetime not null +# procedure_presentation_id :bigint # class Export < ApplicationRecord MAX_DUREE_CONSERVATION_EXPORT = 3.hours @@ -23,7 +26,18 @@ class Export < ApplicationRecord monthly: 'monthly' } + enum statut: { + 'a-suivre': 'a-suivre', + suivis: 'suivis', + traites: 'traites', + tous: 'tous', + supprimes_recemment: 'supprimes_recemment', + archives: 'archives', + expirant: 'expirant' + } + has_and_belongs_to_many :groupe_instructeurs + belongs_to :procedure_presentation, optional: true has_one_attached :file @@ -33,19 +47,24 @@ class Export < ApplicationRecord after_create_commit :compute_async - FORMATS = [:xlsx, :ods, :csv].flat_map do |format| - Export.time_span_types.values.map do |time_span_type| - [format, time_span_type] + FORMATS_WITH_TIME_SPAN = [:xlsx, :ods, :csv].flat_map do |format| + time_span_types.keys.map do |time_span_type| + { format: format.to_sym, time_span_type: time_span_type } end end + FORMATS = [:xlsx, :ods, :csv].map do |format| + { format: format.to_sym } + end def compute_async ExportJob.perform_later(self) end def compute + load_snapshot! + file.attach( - io: io(since: since), + io: io, filename: filename, content_type: content_type, # We generate the exports ourselves, so they are safe @@ -62,47 +81,109 @@ class Export < ApplicationRecord end def old? - updated_at < 20.minutes.ago + updated_at < 20.minutes.ago || filters_changed? end - def self.find_or_create_export(format, time_span_type, groupe_instructeurs) - create_with(groupe_instructeurs: groupe_instructeurs) + def filters_changed? + procedure_presentation&.snapshot != procedure_presentation_snapshot + end + + def filtered? + procedure_presentation_id.present? + end + + def xlsx? + format == self.class.formats.fetch(:xlsx) + end + + def ods? + format == self.class.formats.fetch(:ods) + end + + def csv? + format == self.class.formats.fetch(:csv) + end + + def self.find_or_create_export(format, groupe_instructeurs, time_span_type: time_span_types.fetch(:everything), statut: statuts.fetch(:tous), procedure_presentation: nil) + create_with(groupe_instructeurs: groupe_instructeurs, procedure_presentation: procedure_presentation, procedure_presentation_snapshot: procedure_presentation&.snapshot) + .includes(:procedure_presentation) .create_or_find_by(format: format, time_span_type: time_span_type, - key: generate_cache_key(groupe_instructeurs.map(&:id))) + statut: statut, + key: generate_cache_key(groupe_instructeurs.map(&:id), procedure_presentation&.id)) end - def self.find_for_groupe_instructeurs(groupe_instructeurs_ids) - exports = where(key: generate_cache_key(groupe_instructeurs_ids)) + def self.find_for_groupe_instructeurs(groupe_instructeurs_ids, procedure_presentation) + exports = if procedure_presentation.present? + where(key: generate_cache_key(groupe_instructeurs_ids)) + .or(where(key: generate_cache_key(groupe_instructeurs_ids, procedure_presentation.id))) + else + where(key: generate_cache_key(groupe_instructeurs_ids)) + end + filtered, not_filtered = exports.partition(&:filtered?) - [:xlsx, :csv, :ods].map do |format| - [ - format, - Export.time_span_types.values.map do |time_span_type| - [time_span_type, exports.find { |export| export.format == format.to_s && export.time_span_type == time_span_type }] - end.filter { |(_, export)| export.present? }.to_h - ] - end.filter { |(_, exports)| exports.present? }.to_h + { + xlsx: { + time_span_type: not_filtered.filter(&:xlsx?).index_by(&:time_span_type), + statut: filtered.filter(&:xlsx?).index_by(&:statut) + }, + ods: { + time_span_type: not_filtered.filter(&:ods?).index_by(&:time_span_type), + statut: filtered.filter(&:ods?).index_by(&:statut) + }, + csv: { + time_span_type: not_filtered.filter(&:csv?).index_by(&:time_span_type), + statut: filtered.filter(&:csv?).index_by(&:statut) + } + } end - def self.generate_cache_key(groupe_instructeurs_ids) - groupe_instructeurs_ids.sort.join('-') + def self.generate_cache_key(groupe_instructeurs_ids, procedure_presentation_id = nil) + if procedure_presentation_id.present? + "#{groupe_instructeurs_ids.sort.join('-')}--#{procedure_presentation_id}" + else + groupe_instructeurs_ids.sort.join('-') + end + end + + def count + if procedure_presentation_id.present? + dossiers_for_export.size + end end private - def filename - procedure_identifier = procedure.path || "procedure-#{procedure.id}" - "dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}" + def load_snapshot! + if procedure_presentation_snapshot.present? + procedure_presentation.attributes = procedure_presentation_snapshot + end end - def io(since: nil) - dossiers = Dossier.visible_by_administration - .where(groupe_instructeur: groupe_instructeurs) - if since.present? - dossiers = dossiers.where('dossiers.depose_at > ?', since) + def dossiers_for_export + @dossiers_for_export ||= begin + dossiers = Dossier.where(groupe_instructeur: groupe_instructeurs) + + if since.present? + dossiers.visible_by_administration.where('dossiers.depose_at > ?', since) + elsif procedure_presentation.present? + filtered_sorted_ids = procedure_presentation + .filtered_sorted_ids(dossiers, statut) + + dossiers.where(id: filtered_sorted_ids) + else + dossiers + end end - service = ProcedureExportService.new(procedure, dossiers) + end + + def filename + procedure_identifier = procedure.path || "procedure-#{procedure.id}" + "dossiers_#{procedure_identifier}_#{statut}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}" + end + + def io + service = ProcedureExportService.new(procedure, dossiers_for_export) case format.to_sym when :csv diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 1c39669d5..c2edecc70 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -25,6 +25,8 @@ class ProcedurePresentation < ApplicationRecord FILTERS_VALUE_MAX_LENGTH = 100 belongs_to :assign_to, optional: false + has_many :exports, dependent: :destroy + delegate :procedure, :instructeur, to: :assign_to validate :check_allowed_displayed_fields @@ -180,9 +182,9 @@ class ProcedurePresentation < ApplicationRecord end.reduce(:&) end - def filtered_sorted_ids(dossiers, count, statut) + def filtered_sorted_ids(dossiers, statut, count: nil) dossiers_by_statut = dossiers.by_statut(instructeur, statut) - dossiers_sorted_ids = self.sorted_ids(dossiers_by_statut, count) + dossiers_sorted_ids = self.sorted_ids(dossiers_by_statut, count || dossiers_by_statut.size) if filters[statut].present? filtered_ids(dossiers_by_statut, statut).intersection(dossiers_sorted_ids) @@ -261,6 +263,10 @@ class ProcedurePresentation < ApplicationRecord }) end + def snapshot + slice(:filters, :sort, :displayed_fields) + end + private def field_id(field) diff --git a/app/views/instructeurs/procedures/_dossiers_export.html.haml b/app/views/instructeurs/procedures/_dossiers_export.html.haml new file mode 100644 index 000000000..45f0754b5 --- /dev/null +++ b/app/views/instructeurs/procedures/_dossiers_export.html.haml @@ -0,0 +1,19 @@ +%span.dropdown + %button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'download-menu' } + = t(".download", count: count) + #download-menu.dropdown-content.fade-in-down{ style: 'width: 450px' } + %ul.dropdown-items + - exports_list(exports, statut).each do |item| + - format = item[:format] + - export = item[:export] + %li + - if export.nil? + = link_to t(".everything_#{format}_html"), download_export_instructeur_procedure_path(procedure, statut: statut, export_format: format), remote: true + - elsif export.ready? + = link_to t(".everything_ready_html", export_time: time_ago_in_words(export.updated_at), export_format: ".#{format}"), export.file.service_url, target: "_blank", rel: "noopener" + - if export.old? + = button_to download_export_instructeur_procedure_path(procedure, export_format: format, statut: statut, force_export: true), class: "button small", style: "padding-right: 2px", title: t(".everything_short", export_format: ".#{format}"), remote: true, method: :get, params: { export_format: format, statut: statut, force_export: true } do + .icon.retry + - else + %span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, statut: statut, no_progress_notification: true) } + = t(".everything_pending_html", export_time: time_ago_in_words(export.created_at), export_format: ".#{format}") diff --git a/app/views/instructeurs/procedures/_dossiers_filter.html.haml b/app/views/instructeurs/procedures/_dossiers_filter.html.haml new file mode 100644 index 000000000..ac7c88839 --- /dev/null +++ b/app/views/instructeurs/procedures/_dossiers_filter.html.haml @@ -0,0 +1,24 @@ +%span.dropdown + %button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'filter-menu' } + Filtrer + #filter-menu.dropdown-content.left-aligned.fade-in-down + = form_tag add_filter_instructeur_procedure_path(procedure), method: :post, class: 'dropdown-form large' do + = label_tag :field, "Colonne" + = select_tag :field, options_for_select(displayed_fields_options) + %br + = label_tag :value, "Valeur" + = text_field_tag :value, nil, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH + = hidden_field_tag :statut, statut + %br + = submit_tag "Ajouter le filtre", class: 'button' + +- current_filters.group_by { |filter| filter['table'] }.each_with_index do |(table, filters), i| + - if i > 0 + et + - filters.each_with_index do |filter, i| + - if i > 0 + ou + %span.filter + = link_to remove_filter_instructeur_procedure_path(procedure, { statut: statut, field: "#{filter['table']}/#{filter['column']}", value: filter['value'] }) do + %img.close-icon{ src: image_url("close.svg") } + = "#{filter['label'].truncate(50)} : #{procedure_presentation.human_value_for_filter(filter)}" diff --git a/app/views/instructeurs/procedures/_download_dossiers.html.haml b/app/views/instructeurs/procedures/_download_dossiers.html.haml index c07b51811..11e83b75e 100644 --- a/app/views/instructeurs/procedures/_download_dossiers.html.haml +++ b/app/views/instructeurs/procedures/_download_dossiers.html.haml @@ -3,7 +3,10 @@ Télécharger tous les dossiers #download-menu.dropdown-content.fade-in-down{ style: 'width: 450px' } %ul.dropdown-items - - exports_list(exports).each do |(format, time_span_type, export)| + - exports_list(exports).each do |item| + - format = item[:format] + - time_span_type = item[:time_span_type] + - export = item[:export] %li - if export.nil? = link_to t("#{time_span_type}_#{format}_html", scope: [:instructeurs, :procedure, :export_stale]), download_export_instructeur_procedure_path(procedure, time_span_type: time_span_type, export_format: format), remote: true @@ -13,7 +16,7 @@ = button_to download_export_instructeur_procedure_path(procedure, export_format: format, time_span_type: time_span_type, force_export: true), class: "button small", style: "padding-right: 2px", title: t("#{time_span_type}_short", export_format: ".#{format}", scope: [:instructeurs, :procedure, :export_stale]), remote: true, method: :get, params: { export_format: format, time_span_type: time_span_type, force_export: true } do .icon.retry - else - %span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, no_progress_notification: true) } + %span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, time_span_type: time_span_type, no_progress_notification: true) } = t("export_#{time_span_type}_pending_html", export_time: time_ago_in_words(export.created_at), export_format: ".#{format}", scope: [:instructeurs, :procedure]) %li = link_to t(:download_archive, scope: [:instructeurs, :procedure]), instructeur_archives_path(procedure) diff --git a/app/views/instructeurs/procedures/download_export.js.erb b/app/views/instructeurs/procedures/download_export.js.erb index aab5dbf9f..ab489f92d 100644 --- a/app/views/instructeurs/procedures/download_export.js.erb +++ b/app/views/instructeurs/procedures/download_export.js.erb @@ -1,11 +1,22 @@ <% if @can_download_dossiers %> - <%= render_to_element('.procedure-actions', partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports }) %> + <% if @statut.present? %> + <%= render_to_element('.dossiers-export', partial: "dossiers_export", locals: { procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count }) %> + <% else %> + <%= render_to_element('.procedure-actions', partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports }) %> + <% end %> <% end %> -<% @exports.each do |format, exports| %> - <% exports.each do |time_span_type, export| %> - <% if !export.ready? %> - <%= fire_event('export:update', { url: download_export_instructeur_procedure_path(@procedure, export_format: format, time_span_type: time_span_type, no_progress_notification: true) }.to_json) %> +<% @exports.values.each do |exports| %> + <% if @statut.present? %> + <% export = exports[:statut][@statut] %> + <% if export && !export.ready? %> + <%= fire_event('export:update', { url: download_export_instructeur_procedure_path(@procedure, export_format: export.format, statut: export.statut, no_progress_notification: true) }.to_json) %> + <% end %> + <% else %> + <% exports[:time_span_type].values.each do |export| %> + <% if !export.ready? %> + <%= fire_event('export:update', { url: download_export_instructeur_procedure_path(@procedure, export_format: export.format, time_span_type: export.time_span_type, no_progress_notification: true) }.to_json) %> + <% end %> <% end %> <% end %> <% end %> diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index ac166b19d..feae02087 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -52,34 +52,16 @@ - if @statut == 'expirant' %p.explication-onglet Les dossiers n'expireront pas avant la période de conservation des données. - - if @filtered_sorted_paginated_ids.present? || @current_filters.count > 0 - pagination = paginate @filtered_sorted_paginated_ids = pagination - %span.dropdown - %button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'filter-menu' } - Filtrer - #filter-menu.dropdown-content.left-aligned.fade-in-down - = form_tag add_filter_instructeur_procedure_path(@procedure), method: :post, class: 'dropdown-form large' do - = label_tag :field, "Colonne" - = select_tag :field, options_for_select(@displayed_fields_options) - %br - = label_tag :value, "Valeur" - = text_field_tag :value, nil, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH - = hidden_field_tag :statut, @statut - %br - = submit_tag "Ajouter le filtre", class: 'button' + .flex + .flex-grow + = render partial: "dossiers_filter", locals: { procedure: @procedure, procedure_presentation: @procedure_presentation, current_filters: @current_filters, statut: @statut, displayed_fields_options: @displayed_fields_options } + - if @dossiers_count > 0 + .dossiers-export + = render partial: "dossiers_export", locals: { procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count } - - @current_filters.group_by { |filter| filter['table'] }.each_with_index do |(table, filters), i| - - if i > 0 - et - - filters.each_with_index do |filter, i| - - if i > 0 - ou - %span.filter - = link_to remove_filter_instructeur_procedure_path(@procedure, { statut: @statut, field: "#{filter['table']}/#{filter['column']}", value: filter['value'] }) do - %img.close-icon{ src: image_url("close.svg") } - = "#{filter['label'].truncate(50)} : #{@procedure_presentation.human_value_for_filter(filter)}" %table.table.dossiers-table.hoverable %thead %tr diff --git a/config/brakeman.ignore b/config/brakeman.ignore index ac201cf2d..692e07c58 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -15,7 +15,7 @@ "type": "controller", "class": "Users::DossiersController", "method": "merci", - "line": 196, + "line": 201, "file": "app/controllers/users/dossiers_controller.rb", "rendered": { "name": "users/dossiers/merci", @@ -115,24 +115,24 @@ { "warning_type": "Redirect", "warning_code": 18, - "fingerprint": "c46b5c9cd6474ffae789f39a2280ba6b5a5a74d3ffa8a38cf8a409f9a027ed0e", + "fingerprint": "eae88be293b3849e07be81a18de99c04b08c7b2c045cc4a43ca3624ea178d965", "check_name": "Redirect", "message": "Possible unprotected redirect", "file": "app/controllers/instructeurs/procedures_controller.rb", - "line": 202, + "line": 191, "link": "https://brakemanscanner.org/docs/warning_types/redirect/", - "code": "redirect_to(Export.find_or_create_export(params[:export_format], (params[:time_span_type] or \"everything\"), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url)", + "code": "redirect_to(Export.find_or_create_export(export_format, current_instructeur.groupe_instructeurs.where(:procedure => procedure), **export_options).file.service_url)", "render_path": null, "location": { "type": "method", "class": "Instructeurs::ProceduresController", "method": "download_export" }, - "user_input": "Export.find_or_create_export(params[:export_format], (params[:time_span_type] or \"everything\"), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url", + "user_input": "Export.find_or_create_export(export_format, current_instructeur.groupe_instructeurs.where(:procedure => procedure), **export_options).file.service_url", "confidence": "High", "note": "" } ], - "updated": "2022-02-22 15:46:39 +0100", + "updated": "2022-04-05 14:21:07 +0200", "brakeman_version": "5.1.1" } diff --git a/config/initializers/legit_admin_domains.rb b/config/initializers/legit_admin_domains.rb index c064dfe4b..5df4e341f 100644 --- a/config/initializers/legit_admin_domains.rb +++ b/config/initializers/legit_admin_domains.rb @@ -1,2 +1,2 @@ -domains = ["gouv.fr", "sante.fr", "cnafmail.fr", "cnamts.fr", "cci.fr", "caf.fr", "msa.fr"] +domains = ["gouv.fr", "sante.fr", "cnafmail.fr", "cnamts.fr", "cci.fr", "caf.fr", "msa.fr", "assurance-maladie.fr"] LEGIT_ADMIN_DOMAINS = ENV["LEGIT_ADMIN_DOMAINS"]&.split(';') || domains diff --git a/config/locales/views/instructeurs/procedures/fr.yml b/config/locales/views/instructeurs/procedures/fr.yml new file mode 100644 index 000000000..9cf758507 --- /dev/null +++ b/config/locales/views/instructeurs/procedures/fr.yml @@ -0,0 +1,13 @@ +fr: + instructeurs: + procedures: + dossiers_export: + everything_csv_html: Demander un export au format .csv
(uniquement les dossiers, sans les champs répétables) + everything_xlsx_html: Demander un export au format .xlsx + everything_ods_html: Demander un export au format .ods + everything_short: Demander un export au format %{export_format} + everything_pending_html: Un export au format %{export_format} est en train d’être généré
(demandé il y a %{export_time}) + everything_ready_html: Télécharger l’export au format %{export_format}
(généré il y a %{export_time}) + download: + one: Télécharger un dossier + other: Télécharger %{count} dossiers diff --git a/db/migrate/20220323143325_add_procedure_presentation_and_state_to_exports.rb b/db/migrate/20220323143325_add_procedure_presentation_and_state_to_exports.rb new file mode 100644 index 000000000..3b2ac9406 --- /dev/null +++ b/db/migrate/20220323143325_add_procedure_presentation_and_state_to_exports.rb @@ -0,0 +1,11 @@ +class AddProcedurePresentationAndStateToExports < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def change + add_reference :exports, :procedure_presentation, null: true, index: { algorithm: :concurrently } + add_column :exports, :statut, :string + change_column_default :exports, :statut, "tous" + remove_index :exports, [:format, :time_span_type, :key] + add_index :exports, [:format, :time_span_type, :statut, :key], unique: true, algorithm: :concurrently + end +end diff --git a/db/migrate/20220405163206_add_procedure_presentation_snapshot_to_exports.rb b/db/migrate/20220405163206_add_procedure_presentation_snapshot_to_exports.rb new file mode 100644 index 000000000..2ac3dd06b --- /dev/null +++ b/db/migrate/20220405163206_add_procedure_presentation_snapshot_to_exports.rb @@ -0,0 +1,5 @@ +class AddProcedurePresentationSnapshotToExports < ActiveRecord::Migration[6.1] + def change + add_column :exports, :procedure_presentation_snapshot, :jsonb + end +end diff --git a/db/migrate/20220407081538_backfill_export_status.rb b/db/migrate/20220407081538_backfill_export_status.rb new file mode 100644 index 000000000..721789437 --- /dev/null +++ b/db/migrate/20220407081538_backfill_export_status.rb @@ -0,0 +1,10 @@ +class BackfillExportStatus < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def change + Export.in_batches do |relation| + relation.update_all statut: "tous" + sleep(0.01) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 11feb6627..d59c95dd9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_04_05_100354) do +ActiveRecord::Schema.define(version: 2022_04_07_081538) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -431,9 +431,13 @@ ActiveRecord::Schema.define(version: 2022_04_05_100354) do t.datetime "created_at", null: false t.string "format", null: false t.text "key", null: false + t.bigint "procedure_presentation_id" + t.jsonb "procedure_presentation_snapshot" + t.string "statut", default: "tous" t.string "time_span_type", default: "everything", null: false t.datetime "updated_at", null: false - t.index ["format", "time_span_type", "key"], name: "index_exports_on_format_and_time_span_type_and_key", unique: true + t.index ["format", "time_span_type", "statut", "key"], name: "index_exports_on_format_and_time_span_type_and_statut_and_key", unique: true + t.index ["procedure_presentation_id"], name: "index_exports_on_procedure_presentation_id" end create_table "exports_groupe_instructeurs", force: :cascade do |t| diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index bd3c80f20..4962ac9d4 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -509,6 +509,13 @@ describe Instructeurs::DossiersController, type: :controller do end end + context 'as an instructeur, i auto follow the dossier so I get the notifications' do + it 'works' do + subject + expect(instructeur.followed_dossiers).to match_array([dossier]) + end + end + context 'email sending' do before do subject diff --git a/spec/factories/export.rb b/spec/factories/export.rb index 3069ca238..c7ebda9b4 100644 --- a/spec/factories/export.rb +++ b/spec/factories/export.rb @@ -1,11 +1,12 @@ FactoryBot.define do factory :export do - format { :csv } + format { Export.formats.fetch(:csv) } + statut { Export.statuts.fetch(:tous) } time_span_type { Export.time_span_types.fetch(:everything) } groupe_instructeurs { [association(:groupe_instructeur)] } after(:build) do |export, _evaluator| - export.key = Export.generate_cache_key(export.groupe_instructeurs.map(&:id)) + export.key = Export.generate_cache_key(export.groupe_instructeurs.map(&:id), export.procedure_presentation&.id) end end end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index e074999b7..2bfdec18a 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -48,9 +48,9 @@ RSpec.describe Export, type: :model do context 'when an export is made for one groupe instructeur' do let!(:export) { create(:export, groupe_instructeurs: [gi_1, gi_2]) } - it { expect(Export.find_for_groupe_instructeurs([gi_1.id])).to eq({}) } - it { expect(Export.find_for_groupe_instructeurs([gi_2.id, gi_1.id])).to eq({ csv: { 'everything' => export } }) } - it { expect(Export.find_for_groupe_instructeurs([gi_1.id, gi_2.id, gi_3.id])).to eq({}) } + it { expect(Export.find_for_groupe_instructeurs([gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} } }) } + it { expect(Export.find_for_groupe_instructeurs([gi_2.id, gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: { 'everything' => export } }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} } }) } + it { expect(Export.find_for_groupe_instructeurs([gi_1.id, gi_2.id, gi_3.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} } }) } end end end diff --git a/spec/system/instructeurs/instruction_spec.rb b/spec/system/instructeurs/instruction_spec.rb index 032397f8e..9af7c53aa 100644 --- a/spec/system/instructeurs/instruction_spec.rb +++ b/spec/system/instructeurs/instruction_spec.rb @@ -105,7 +105,9 @@ describe 'Instructing a dossier:', js: true do assert_performed_jobs 1 click_on "Télécharger tous les dossiers" - click_on "Demander un export au format .xlsx" + within(:css, '.procedure-actions') do + click_on "Demander un export au format .xlsx" + end expect(page).to have_text('Nous générons cet export.') expect(page).to have_text('Un export au format .xlsx est en train d’être généré') @@ -114,13 +116,25 @@ describe 'Instructing a dossier:', js: true do expect(page).to have_text('Nous générons cet export.') expect(page).to have_text('Un export des 30 derniers jours au format .xlsx est en train d’être généré') + click_on "Télécharger un dossier" + 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é') + perform_enqueued_jobs(only: ExportJob) - assert_performed_jobs 3 + assert_performed_jobs 4 page.driver.browser.navigate.refresh click_on "Télécharger tous les dossiers" expect(page).to have_text('Télécharger l’export au format .xlsx') expect(page).to have_text('Télécharger l’export des 30 derniers jours au format .xlsx') + # close dropdown menu + click_on "Télécharger tous les dossiers" + + click_on "Télécharger un dossier" + expect(page).to have_text('Télécharger l’export au format .xlsx') end scenario 'A instructeur can see the personnes impliquées' do