Merge pull request #10898 from mfo/US/navigation-accros-dossiers

ETQ instructeur, je peux passer au dossiers suivant ou au dossier précédent lorsque je visualise un dossiers d'un des onglet (a-suivre, tous, ...)
This commit is contained in:
mfo 2024-12-11 14:03:48 +00:00 committed by GitHub
commit 6e5aa90bc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 434 additions and 30 deletions

View file

@ -397,3 +397,7 @@ ul.dropdown-items {
content: none !important; content: none !important;
} }
} }
.back-btn {
line-height: 1.75rem;
}

View file

@ -6,6 +6,8 @@ class Instructeurs::BackButtonComponent < ApplicationComponent
end end
def call def call
link_to "", @to, class: 'back-btn fr-btn fr-btn--secondary fr-btn--sm fr-mr-2w fr-icon-arrow-left-line', title: t('.back') link_to @to, class: 'back-btn fr-btn fr-btn--secondary fr-btn--sm fr-mr-2w fr-px-1v', title: t('.back') do
dsfr_icon("fr-icon-arrow-left-line")
end
end end
end end

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
class Instructeurs::DossiersNavigationComponent < ApplicationComponent
attr_reader :dossier, :statut
def initialize(dossier:, procedure_presentation:, statut:)
@dossier = dossier
@cache = Cache::ProcedureDossierPagination.new(procedure_presentation: procedure_presentation, statut:)
@statut = statut
end
def back_url_options
options = { statut: }
options = options.merge(page: @cache.incoming_page) if @cache.incoming_page
options
end
def link_next
if has_next?
html_tag = :a
options = { class: "fr-link no-wrap fr-ml-3w", href: next_instructeur_dossier_path(dossier:, statut:) }
else
html_tag = :span
options = { class: "fr-link no-wrap fr-ml-3w fr-text-mention--grey" }
end
tag.send(html_tag, t('.next').html_safe + tag.span(class: 'fr-icon-arrow-right-line fr-ml-1w'), **options)
end
def link_previous
if has_previous?
html_tag = :a
options = { class: "fr-link no-wrap", href: previous_instructeur_dossier_path(dossier:, statut:) }
else
html_tag = :span
options = { class: "fr-link no-wrap fr-text-mention--grey" }
end
tag.send(html_tag, tag.span(class: 'fr-icon-arrow-left-line fr-mr-1w') + t('.previous'), **options)
end
def has_next? = @has_next ||= @cache.next_dossier_id(from_id: dossier.id).present?
def has_previous? = @has_previous ||= @cache.previous_dossier_id(from_id: dossier.id).present?
end

View file

@ -0,0 +1,4 @@
---
en:
next: Next file
previous: Previous file

View file

@ -0,0 +1,4 @@
---
fr:
next: Dossier suivant
previous: Dossier précédent

View file

@ -0,0 +1,11 @@
.flex.fr-mb-1w.align-start
= render Instructeurs::BackButtonComponent.new(to: instructeur_procedure_path(dossier.procedure, **back_url_options))
%h1.fr-h3.fr-mb-0
= t('show_dossier', scope: [:layouts, :breadcrumb], dossier_id: dossier.id, owner_name: dossier.owner_name)
.fr.ml-auto.align-center.flex.fr-mt-1v
= link_previous
= link_next
= link_to dossier.procedure.libelle.truncate_words(10), instructeur_procedure_path(dossier.procedure), title: dossier.procedure.libelle, class: "fr-link"

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
module InstructeurConcern
extend ActiveSupport::Concern
included do
def retrieve_procedure_presentation
@procedure_presentation ||= current_instructeur.procedure_presentation_for_procedure_id(params[:procedure_id])
end
end
end

View file

@ -2,10 +2,12 @@
module Instructeurs module Instructeurs
class CommentairesController < ApplicationController class CommentairesController < ApplicationController
include InstructeurConcern
before_action :authenticate_instructeur_or_expert! before_action :authenticate_instructeur_or_expert!
after_action :mark_messagerie_as_read after_action :mark_messagerie_as_read
def destroy def destroy
retrieve_procedure_presentation if current_instructeur
if commentaire.sent_by?(current_instructeur) || commentaire.sent_by?(current_expert) if commentaire.sent_by?(current_instructeur) || commentaire.sent_by?(current_expert)
commentaire.soft_delete! commentaire.soft_delete!

View file

@ -7,15 +7,16 @@ module Instructeurs
include CreateAvisConcern include CreateAvisConcern
include DossierHelper include DossierHelper
include TurboChampsConcern include TurboChampsConcern
include InstructeurConcern
include ActionController::Streaming include ActionController::Streaming
include Zipline include Zipline
before_action :redirect_on_dossier_not_found, only: :show before_action :redirect_on_dossier_not_found, only: :show
before_action :redirect_on_dossier_in_batch_operation, only: [:archive, :unarchive, :follow, :unfollow, :passer_en_instruction, :repasser_en_construction, :repasser_en_instruction, :terminer, :restore, :destroy, :extend_conservation] before_action :redirect_on_dossier_in_batch_operation, only: [:archive, :unarchive, :follow, :unfollow, :passer_en_instruction, :repasser_en_construction, :repasser_en_instruction, :terminer, :restore, :destroy, :extend_conservation]
before_action :set_gallery_attachments, only: [:show, :pieces_jointes, :annotations_privees, :avis, :messagerie, :personnes_impliquees, :reaffectation] before_action :set_gallery_attachments, only: [:show, :pieces_jointes, :annotations_privees, :avis, :messagerie, :personnes_impliquees, :reaffectation]
after_action :mark_demande_as_read, only: :show before_action :retrieve_procedure_presentation, only: [:annotations_privees, :avis_new, :avis, :messagerie, :personnes_impliquees, :pieces_jointes, :reaffectation, :show, :dossier_labels, :passer_en_instruction, :repasser_en_construction, :repasser_en_instruction, :terminer, :pending_correction, :create_avis, :create_commentaire]
after_action :mark_demande_as_read, only: :show
after_action :mark_messagerie_as_read, only: [:messagerie, :create_commentaire, :pending_correction] after_action :mark_messagerie_as_read, only: [:messagerie, :create_commentaire, :pending_correction]
after_action :mark_avis_as_read, only: [:avis, :create_avis] after_action :mark_avis_as_read, only: [:avis, :create_avis]
after_action :mark_annotations_privees_as_read, only: [:annotations_privees, :update_annotations] after_action :mark_annotations_privees_as_read, only: [:annotations_privees, :update_annotations]
@ -390,8 +391,40 @@ module Instructeurs
@pieces_jointes_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.pieces_jointes_seen_at @pieces_jointes_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.pieces_jointes_seen_at
end end
def next
navigate_through_dossiers_list do |cache|
cache.next_dossier_id(from_id: params[:dossier_id])
end
end
def previous
navigate_through_dossiers_list do |cache|
cache.previous_dossier_id(from_id: params[:dossier_id])
end
end
private private
def navigate_through_dossiers_list
dossier = dossier_scope.find(params[:dossier_id])
procedure_presentation = current_instructeur.procedure_presentation_for_procedure_id(dossier.procedure.id)
cache = Cache::ProcedureDossierPagination.new(procedure_presentation:, statut: params[:statut])
next_or_previous_dossier_id = yield(cache)
if next_or_previous_dossier_id
redirect_to instructeur_dossier_path(procedure_id: procedure.id, dossier_id: next_or_previous_dossier_id, statut: params[:statut])
else
redirect_back fallback_location: instructeur_dossier_path(procedure_id: procedure.id, dossier_id: dossier.id, statut: params[:statut]), alert: "Une erreur est survenue"
end
rescue ActiveRecord::RecordNotFound
Sentry.capture_message(
"Navigation through dossier failed => ActiveRecord::RecordNotFound",
extra: { dossier_id: params[:dossier_id] }
)
redirect_to instructeur_procedure_path(procedure_id: procedure.id), alert: "Une erreur est survenue"
end
def dossier_scope def dossier_scope
if action_name == 'update_annotations' if action_name == 'update_annotations'
Dossier Dossier

View file

@ -122,6 +122,8 @@ module Instructeurs
.where(groupe_instructeurs: current_instructeur.groupe_instructeurs.where(procedure_id: @procedure.id)) .where(groupe_instructeurs: current_instructeur.groupe_instructeurs.where(procedure_id: @procedure.id))
.where(seen_at: nil) .where(seen_at: nil)
.distinct .distinct
cache_show_procedure_state # don't move in callback, inherited by Instructeurs::DossiersController
end end
def deleted_dossiers def deleted_dossiers
@ -392,5 +394,11 @@ module Instructeurs
def ordered_procedure_ids_params def ordered_procedure_ids_params
params.require(:ordered_procedure_ids) params.require(:ordered_procedure_ids)
end end
def cache_show_procedure_state
cache = Cache::ProcedureDossierPagination.new(procedure_presentation:, statut:)
cache.save_context(ids: @filtered_sorted_paginated_ids, incoming_page: params[:page])
end
end end
end end

View file

@ -0,0 +1,102 @@
# frozen_string_literal: true
class Cache::ProcedureDossierPagination
HALF_WINDOW = 50
TRESHOLD_BEFORE_REFRESH = 1
CACHE_EXPIRACY = 8.hours
attr_reader :procedure_presentation, :statut, :cache
delegate :procedure, :instructeur, to: :procedure_presentation
def initialize(procedure_presentation:, statut:)
@procedure_presentation = procedure_presentation
@statut = statut
@cache = Kredis.json(cache_key, expires_in: CACHE_EXPIRACY)
end
def save_context(ids:, incoming_page:)
value = { ids: }
value[:incoming_page] = incoming_page if incoming_page
write_cache(value)
end
def next_dossier_id(from_id:)
index = ids&.index(from_id.to_i)
return nil if index.nil? # not found
if refresh_cache_after?(from_id:)
renew_ids(from_id:)
index = ids.index(from_id.to_i)
end
return nil if index.blank?
return nil if index + 1 > ids.size # out of bound end
ids[index + 1]
end
def previous_dossier_id(from_id:)
index = ids&.index(from_id.to_i)
return nil if index.nil? # not found
if refresh_cache_before?(from_id:)
renew_ids(from_id:)
index = ids.index(from_id.to_i)
end
return nil if index.blank?
return nil if index - 1 < 0 # out of bound start
ids[index - 1]
end
def incoming_page
read_cache[:incoming_page]
end
private
def cache_key
[procedure.id, instructeur.id, statut].join(":")
end
def write_cache(value)
cache.value = value
@read_cache = nil
end
def read_cache
@read_cache ||= Hash(cache.value).with_indifferent_access
end
def ids = read_cache[:ids]
def refresh_cache_after?(from_id:) = from_id.in?(ids.last(TRESHOLD_BEFORE_REFRESH))
def refresh_cache_before?(from_id:) = from_id.in?(ids.first(TRESHOLD_BEFORE_REFRESH))
def renew_ids(from_id:)
value = read_cache
value[:ids] = fetch_ids_around(from_id:)
write_cache(value)
end
def fetch_all_ids
dossiers = Dossier.where(groupe_instructeur_id: GroupeInstructeur.joins(:instructeurs, :procedure).where(procedure: procedure, instructeurs: [instructeur]).select(:id))
DossierFilterService.filtered_sorted_ids(dossiers, statut, procedure_presentation.filters_for(statut), procedure_presentation.sorted_column, instructeur, count: 0)
end
def fetch_ids_around(from_id:)
all_ids = fetch_all_ids
from_id_at = all_ids.index(from_id)
if from_id_at.present?
new_page_starts_at = [0, from_id_at - HALF_WINDOW].max # avoid index below 0
new_page_ends_at = [from_id_at + HALF_WINDOW, all_ids.size].min # avoid index above all_ids.size
all_ids.slice(new_page_starts_at, new_page_ends_at)
else
[]
end
end
end

View file

@ -127,12 +127,14 @@ class Instructeur < ApplicationRecord
end end
end end
def procedure_presentation_for_procedure_id(procedure_id)
assign_to = assign_to_for_procedure_id(procedure_id)
assign_to.procedure_presentation || assign_to.create_procedure_presentation!
end
def procedure_presentation_and_errors_for_procedure_id(procedure_id) def procedure_presentation_and_errors_for_procedure_id(procedure_id)
assign_to assign_to = assign_to_for_procedure_id(procedure_id)
.joins(:groupe_instructeur) assign_to.procedure_presentation_or_default_and_errors
.includes(:instructeur, :procedure)
.find_by(groupe_instructeurs: { procedure_id: procedure_id })
.procedure_presentation_or_default_and_errors
end end
def notifications_for_dossier(dossier) def notifications_for_dossier(dossier)
@ -355,4 +357,11 @@ class Instructeur < ApplicationRecord
.merge(followed_dossiers) .merge(followed_dossiers)
.with_notifications .with_notifications
end end
def assign_to_for_procedure_id(procedure_id)
assign_to
.joins(:groupe_instructeur)
.includes(:instructeur, :procedure)
.find_by(groupe_instructeurs: { procedure_id: procedure_id })
end
end end

View file

@ -3,4 +3,4 @@
= render Dossiers::MessageComponent.new(commentaire: @commentaire, connected_user: @commentaire.instructeur || @commentaire.expert) = render Dossiers::MessageComponent.new(commentaire: @commentaire, connected_user: @commentaire.instructeur || @commentaire.expert)
- if current_user.instructeur? && @commentaire.dossier_correction.present? - if current_user.instructeur? && @commentaire.dossier_correction.present?
= turbo_stream.replace 'header-top', partial: 'instructeurs/dossiers/header_top', locals: { dossier: @commentaire.dossier } = turbo_stream.replace 'header-top', partial: 'instructeurs/dossiers/header_top', locals: { dossier: @commentaire.dossier, procedure_presentation: @procedure_presentation }

View file

@ -10,7 +10,7 @@
locals: { steps: [[t('show_procedure', scope: [:layouts, :breadcrumb], libelle: dossier.procedure.libelle.truncate(22)), instructeur_procedure_path(dossier.procedure)], locals: { steps: [[t('show_procedure', scope: [:layouts, :breadcrumb], libelle: dossier.procedure.libelle.truncate(22)), instructeur_procedure_path(dossier.procedure)],
[t('show_dossier', scope: [:layouts, :breadcrumb], dossier_id: dossier.id, owner_name: dossier.owner_name)]] } [t('show_dossier', scope: [:layouts, :breadcrumb], dossier_id: dossier.id, owner_name: dossier.owner_name)]] }
= render partial: 'instructeurs/dossiers/header_top', locals: { dossier: } = render partial: 'instructeurs/dossiers/header_top', locals: { dossier:, procedure_presentation: }
= render partial: 'instructeurs/dossiers/header_bottom', locals: { dossier:, gallery_attachments: } = render partial: 'instructeurs/dossiers/header_bottom', locals: { dossier:, gallery_attachments: }
.fr-container .fr-container

View file

@ -1,14 +1,9 @@
#header-top.fr-container #header-top.fr-container
.flex = render Instructeurs::DossiersNavigationComponent.new(dossier:, procedure_presentation:, statut: params[:statut])
.flex.fr-mb-3w
%div %div
.flex.fr-mb-1w .fr-mt-2w.badge-group
= render Instructeurs::BackButtonComponent.new(to: instructeur_procedure_path(dossier.procedure, statut: params[:statut]))
%h1.fr-h3.fr-mb-1w
= t('show_dossier', scope: [:layouts, :breadcrumb], dossier_id: dossier.id, owner_name: dossier.owner_name)
= link_to dossier.procedure.libelle.truncate_words(10), instructeur_procedure_path(dossier.procedure), title: dossier.procedure.libelle, class: "fr-link"
.fr-mt-2w.fr-badge-group
= procedure_badge(dossier.procedure) = procedure_badge(dossier.procedure)
= status_badge(dossier.state) = status_badge(dossier.state)

View file

@ -1,6 +1,6 @@
- content_for(:title, "Annotations privées · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") - content_for(:title, "Annotations privées · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
#dossier-annotations-privees #dossier-annotations-privees
.fr-container .fr-container

View file

@ -1,6 +1,6 @@
- content_for(:title, "Avis · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") - content_for(:title, "Avis · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
.container .container
.fr-grid-row .fr-grid-row

View file

@ -1,6 +1,6 @@
- content_for(:title, "Avis · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") - content_for(:title, "Avis · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
.container .container
.fr-grid-row .fr-grid-row

View file

@ -1 +1 @@
= turbo_stream.replace 'header-top', partial: 'header_top', locals: { dossier: @dossier } = turbo_stream.replace 'header-top', partial: 'header_top', locals: { dossier: @dossier, procedure_presentation: @procedure_presentation }

View file

@ -1,5 +1,5 @@
- content_for(:title, "Messagerie · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") - content_for(:title, "Messagerie · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
= render partial: "shared/dossiers/messagerie", locals: { dossier: @dossier, connected_user: current_instructeur, messagerie_seen_at: @messagerie_seen_at , new_commentaire: @commentaire, form_url: commentaire_instructeur_dossier_path(@dossier.procedure, @dossier, statut: params[:statut]) } = render partial: "shared/dossiers/messagerie", locals: { dossier: @dossier, connected_user: current_instructeur, messagerie_seen_at: @messagerie_seen_at , new_commentaire: @commentaire, form_url: commentaire_instructeur_dossier_path(@dossier.procedure, @dossier, statut: params[:statut]) }

View file

@ -1,6 +1,6 @@
- content_for(:title, "Personnes impliquées · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") - content_for(:title, "Personnes impliquées · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
.personnes-impliquees.container .personnes-impliquees.container
= render partial: 'instructeurs/dossiers/envoyer_dossier_block', locals: { dossier: @dossier, potential_recipients: @potential_recipients } = render partial: 'instructeurs/dossiers/envoyer_dossier_block', locals: { dossier: @dossier, potential_recipients: @potential_recipients }

View file

@ -1,6 +1,6 @@
- content_for(:title, "Pièces jointes") - content_for(:title, "Pièces jointes")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
.fr-container .fr-container
.gallery.gallery-pieces-jointes{ "data-controller": "lightbox" } .gallery.gallery-pieces-jointes{ "data-controller": "lightbox" }

View file

@ -1,6 +1,6 @@
- content_for(:title, "Réaffectation · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") - content_for(:title, "Réaffectation · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
.container.groupe-instructeur .container.groupe-instructeur

View file

@ -1,6 +1,6 @@
- content_for(:title, "Demande · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") - content_for(:title, "Demande · Dossier nº #{@dossier.id} (#{@dossier.owner_name})")
= render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments } = render partial: "header", locals: { dossier: @dossier, gallery_attachments: @gallery_attachments, procedure_presentation: @procedure_presentation }
- if @dossier.etablissement&.as_degraded_mode? - if @dossier.etablissement&.as_degraded_mode?

View file

@ -1,5 +1,5 @@
.procedure-header .procedure-header
.clipboard-container.flex .align-start.flex
= render Instructeurs::BackButtonComponent.new(to: instructeur_procedures_path) = render Instructeurs::BackButtonComponent.new(to: instructeur_procedures_path)
%h1.fr-h3.fr-mb-0 %h1.fr-h3.fr-mb-0
= "#{procedure_libelle procedure} (n°#{procedure.id})" = "#{procedure_libelle procedure} (n°#{procedure.id})"

View file

@ -485,6 +485,8 @@ Rails.application.routes.draw do
resources :dossiers, only: [:show, :destroy], param: :dossier_id, path: "(:statut)/dossiers", defaults: { statut: 'a-suivre' } do resources :dossiers, only: [:show, :destroy], param: :dossier_id, path: "(:statut)/dossiers", defaults: { statut: 'a-suivre' } do
member do member do
resources :commentaires, only: [:destroy] resources :commentaires, only: [:destroy]
get 'next'
get 'previous'
post 'repousser-expiration' => 'dossiers#extend_conservation' post 'repousser-expiration' => 'dossiers#extend_conservation'
post 'repousser-expiration-and-restore' => 'dossiers#extend_conservation_and_restore' post 'repousser-expiration-and-restore' => 'dossiers#extend_conservation_and_restore'
post 'dossier_labels' => 'dossiers#dossier_labels' post 'dossier_labels' => 'dossiers#dossier_labels'

View file

@ -984,6 +984,68 @@ describe Instructeurs::DossiersController, type: :controller do
end end
end end
describe 'navigation accross next/prev dossiers' do
let(:dossier_id) { dossier.id }
let(:statut) { 'a-suivre' }
let(:previous_dossier) { create(:dossier, :en_construction, procedure:) }
let(:next_dossier) { create(:dossier, :en_construction, procedure:) }
let(:cached_ids) { [previous_dossier, dossier, next_dossier].map(&:id) }
before do
cache = Cache::ProcedureDossierPagination.new(procedure_presentation: double(procedure:, instructeur:), statut:)
cache.save_context(incoming_page: 1, ids: cached_ids)
end
context 'when nexting' do
subject { get :next, params: { procedure_id: procedure.id, dossier_id: from_id, statut: } }
context 'when their is a next id' do
let(:from_id) { dossier.id }
it { is_expected.to redirect_to(instructeur_dossier_path(procedure_id: procedure.id, dossier_id: next_dossier.id)) }
end
context 'when their is not next id (en of list)' do
let(:from_id) { cached_ids.last }
it 'redirect on fallback location being current dossier and flashes an error' do
expect(subject).to redirect_to(instructeur_dossier_path(procedure_id: procedure.id, dossier_id: from_id))
expect(flash.alert).to eq("Une erreur est survenue")
end
end
context 'when id does not exists' do
let(:from_id) { 'kthxbye' }
it 'redirect on fallback location being current dossier and flashes an error' do
expect(subject).to redirect_to(instructeur_procedure_path(procedure_id: procedure.id))
expect(flash.alert).to eq("Une erreur est survenue")
end
end
end
context 'when previousing' do
subject { get :previous, params: { procedure_id: procedure.id, dossier_id: from_id, statut: } }
context 'when their is a previous id' do
let(:from_id) { dossier.id }
it { is_expected.to redirect_to(instructeur_dossier_path(procedure_id: procedure.id, dossier_id: previous_dossier.id)) }
end
context 'when their is not previous id (before list)' do
let(:from_id) { cached_ids.first }
it 'redirect on fallback location being current dossier and flashes an error' do
expect(subject).to redirect_to(instructeur_dossier_path(procedure_id: procedure.id, dossier_id: from_id))
expect(flash.alert).to eq("Une erreur est survenue")
end
end
context 'when id does not exists' do
let(:from_id) { 'kthxbye' }
it 'redirect on fallback location being current dossier and flashes an error' do
expect(subject).to redirect_to(instructeur_procedure_path(procedure_id: procedure.id))
expect(flash.alert).to eq("Une erreur est survenue")
end
end
end
end
describe "#update_annotations" do describe "#update_annotations" do
let(:procedure) do let(:procedure) do
create(:procedure, :published, types_de_champ_public:, types_de_champ_private:, instructeurs: instructeurs) create(:procedure, :published, types_de_champ_public:, types_de_champ_private:, instructeurs: instructeurs)

View file

@ -670,6 +670,18 @@ describe Instructeurs::ProceduresController, type: :controller do
end end
end end
end end
describe 'caches statut and page query param' do
let(:statut) { 'tous' }
let(:page) { '1' }
let!(:dossier) { create(:dossier, :accepte, procedure:) }
before { sign_in(instructeur.user) }
subject { get :show, params: { procedure_id: procedure.id, statut:, page: } }
it 'changes cached value' do
expect { subject }.to change { Cache::ProcedureDossierPagination.new(statut:, procedure_presentation: double(procedure:, instructeur:)).send(:read_cache) }
.from({}).to(ids: [dossier.id], incoming_page: page)
end
end
end end
describe '#deleted_dossiers' do describe '#deleted_dossiers' do

View file

@ -0,0 +1,86 @@
# frozen_string_literal: true
describe Cache::ProcedureDossierPagination do
let(:instructeur) { double(id: 1) }
let(:procedure) { double(id: 1) }
let(:procedure_presentation) { double(instructeur:, procedure:) }
let(:instance) { described_class.new(procedure_presentation:, statut: 'a-suivre') }
before do
instance.save_context(ids: cached_ids, incoming_page: nil)
end
describe 'next_dossier_id' do
context 'when procedure.dossiers.by_statut has only one page' do
let(:cached_ids) { [3, 4] }
before do
allow(instance).to receive(:fetch_all_ids).and_return(cached_ids)
end
it 'find next until the end' do
expect(instance.next_dossier_id(from_id: cached_ids.last)).to eq(nil)
expect(instance.next_dossier_id(from_id: cached_ids.first)).to eq(cached_ids.last)
end
end
context 'when procedure.dossiers.by_statut has more than one page' do
let(:cached_ids) { [2, 3, 4] }
let(:next_page_ids) { (0..10).to_a }
subject { instance.next_dossier_id(from_id: cached_ids.last) }
before do
allow(instance).to receive(:fetch_all_ids).and_return(next_page_ids)
end
it 'refreshes paginated_ids' do
expect { subject }.to change { instance.send(:ids) }.from(cached_ids).to(next_page_ids)
end
end
context 'when procedure.dossiers.by_statut does not include searched dossiers anymore' do
let(:cached_ids) { [] }
let(:next_page_ids) { [] }
before { allow(instance).to receive(:fetch_all_ids).and_return(next_page_ids) }
it 'works' do
expect(instance.next_dossier_id(from_id: 50)).to eq(nil)
end
end
end
describe 'previous_dossier_id' do
context 'when procedure.dossiers.by_statut has only one page' do
let(:cached_ids) { [3, 4] }
before do
allow(instance).to receive(:fetch_all_ids).and_return(cached_ids)
end
it 'find next until the end' do
expect(instance.previous_dossier_id(from_id: cached_ids.last)).to eq(cached_ids.first)
expect(instance.previous_dossier_id(from_id: cached_ids.first)).to eq(nil)
end
end
context 'when procedure.dossiers.by_statut has more than one page' do
let(:cached_ids) { [11, 12, 13] }
subject { instance.previous_dossier_id(from_id: cached_ids.first) }
let(:next_page_ids) { (11..20).to_a }
before do
allow(instance).to receive(:fetch_all_ids).and_return(next_page_ids)
end
it 'works' do
expect { subject }.to change { instance.send(:ids) }.from(cached_ids).to(next_page_ids)
end
end
context 'when procedure.dossiers.by_statut does not include searched dossiers anymore' do
let(:cached_ids) { [] }
before { allow(instance).to receive(:fetch_all_ids).and_return([]) }
it 'works' do
expect(instance.previous_dossier_id(from_id: 50)).to eq(nil)
end
end
end
end

View file

@ -3,12 +3,15 @@
describe 'instructeurs/dossiers/annotations_privees', type: :view do describe 'instructeurs/dossiers/annotations_privees', type: :view do
let(:current_instructeur) { create(:instructeur) } let(:current_instructeur) { create(:instructeur) }
let(:dossier) { create(:dossier, :en_construction) } let(:dossier) { create(:dossier, :en_construction) }
let(:procedure_presentation) { double(instructeur: current_instructeur, procedure: dossier.procedure) }
before do before do
sign_in(current_instructeur.user) sign_in(current_instructeur.user)
allow(view).to receive(:current_instructeur).and_return(current_instructeur) allow(view).to receive(:current_instructeur).and_return(current_instructeur)
allow(controller).to receive(:params).and_return({ statut: 'a-suivre' }) allow(controller).to receive(:params).and_return({ statut: 'a-suivre' })
assign(:dossier, dossier) assign(:dossier, dossier)
assign(:procedure_presentation, procedure_presentation)
end end
subject { render } subject { render }

View file

@ -3,12 +3,15 @@
describe 'instructeurs/dossiers/show', type: :view do describe 'instructeurs/dossiers/show', type: :view do
let(:current_instructeur) { create(:instructeur) } let(:current_instructeur) { create(:instructeur) }
let(:dossier) { create(:dossier, :en_construction) } let(:dossier) { create(:dossier, :en_construction) }
let(:statut) { { statut: 'tous' } }
let(:procedure_presentation) { double(instructeur: current_instructeur, procedure: dossier.procedure) }
before do before do
sign_in(current_instructeur.user) sign_in(current_instructeur.user)
allow(view).to receive(:current_instructeur).and_return(current_instructeur) allow(view).to receive(:current_instructeur).and_return(current_instructeur)
allow(controller).to receive(:params).and_return(statut: 'a-suivre') allow(controller).to receive(:params).and_return(statut:)
assign(:dossier, dossier) assign(:dossier, dossier)
assign(:procedure_presentation, procedure_presentation)
end end
subject { render } subject { render }
@ -17,6 +20,12 @@ describe 'instructeurs/dossiers/show', type: :view do
expect(subject).to have_text("Dossier nº #{dossier.id}") expect(subject).to have_text("Dossier nº #{dossier.id}")
end end
context 'when procedure statut / page was saved in session' do
it 'renders back button with saved state' do
expect(subject).to have_selector("a[href=\"#{instructeur_procedure_path(dossier.procedure, statut: statut)}\"]")
end
end
it 'renders the dossier infos' do it 'renders the dossier infos' do
expect(subject).to have_text('Identité') expect(subject).to have_text('Identité')
expect(subject).to have_text('Demande') expect(subject).to have_text('Demande')