Merge pull request #8727 from tchak/annotations-autosave

feat(annotations): activate autosave
This commit is contained in:
Paul Chavard 2023-03-14 14:45:03 +00:00 committed by GitHub
commit a119a301bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 146 additions and 117 deletions

View file

@ -0,0 +1,15 @@
class Dossiers::AutosaveFooterComponent < ApplicationComponent
include ApplicationHelper
attr_reader :dossier
def initialize(dossier:, annotation:)
@dossier = dossier
@annotation = annotation
end
private
def annotation?
@annotation
end
end

View file

@ -0,0 +1,15 @@
---
en:
brouillon:
explanation: Your draft is automatically saved.
confirmation: Draft saved
error: Impossible to save the draft
en_construction:
explanation: Your file is automatically saved.
confirmation: File saved
error: Impossible to save the file
annotations:
explanation: Your annotations are automatically saved.
confirmation: Annotations saved
error: Impossible to save the annotations
more_information: More informations

View file

@ -0,0 +1,15 @@
---
fr:
brouillon:
explanation: Votre brouillon est automatiquement enregistré.
confirmation: Brouillon enregistré
error: Impossible denregistrer le brouillon
en_construction:
explanation: Votre dossier est automatiquement enregistré.
confirmation: Dossier enregistré
error: Impossible denregistrer le dossier
annotations:
explanation: Vos annotations sont automatiquement enregistrées.
confirmation: Annotations enregistré
error: Impossible denregistrer les annotations
more_information: En savoir plus

View file

@ -0,0 +1,37 @@
.autosave.autosave-state-idle{ data: { controller: 'autosave-status' } }
%p.autosave-explanation.fr-text--sm
%span.autosave-explanation-text
- if annotation?
= t('.annotations.explanation')
- elsif dossier.brouillon?
= t('.brouillon.explanation')
- else
= t('.en_construction.explanation')
- if !annotation?
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
%p.autosave-status.succeeded
%span.autosave-icon.icon.accept
%span.autosave-label
- if annotation?
= t('.annotations.confirmation')
- elsif dossier.brouillon?
= t('.brouillon.confirmation')
- else
= t('.en_construction.confirmation')
- if !annotation?
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
%p.autosave-status.failed
%span.autosave-icon ⚠️
%span.autosave-label
- if annotation?
= t('.annotations.error')
- elsif dossier.brouillon?
= t('.brouillon.error')
- else
= t('.en_construction.error')
%button.button.small.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
%span.autosave-retry-label réessayer
%span.autosave-retrying-label enregistrement en cours…

View file

@ -1,6 +1,7 @@
class Dossiers::EditFooterComponent < ApplicationComponent
def initialize(dossier:)
def initialize(dossier:, annotation:)
@dossier = dossier
@annotation = annotation
end
private
@ -9,6 +10,10 @@ class Dossiers::EditFooterComponent < ApplicationComponent
controller.current_user.owns?(@dossier)
end
def annotation?
@annotation.present?
end
def button_options
{
class: 'fr-btn fr-btn--sm',

View file

@ -1,12 +1,13 @@
.dossier-edit-sticky-footer
.send-dossier-actions-bar
= render partial: 'shared/dossiers/autosave', locals: { dossier: @dossier }
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
- if @dossier.can_transition_to_en_construction?
- if !annotation? && @dossier.can_transition_to_en_construction?
= button_to t('.submit'), brouillon_dossier_url(@dossier), button_options
- if @dossier.brouillon? && !owner?
.send-notice.invite-cannot-submit
= t('.invite_notice')
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier }
- if !annotation?
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier }

View file

@ -8,4 +8,4 @@
- if @champ.rebased_at.present? && @champ.rebased_at > (@seen_at || @champ.updated_at) && current_user.owns_or_invite?(@champ.dossier)
%span.updated-at.highlighted
Le type de ce @champ où sa description a été modifiée par l'administration. Vérifier son contenu.
Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.

View file

@ -28,12 +28,7 @@ class EditableChamp::EditableChampComponent < ApplicationComponent
def stimulus_controller
if !@champ.block? && @champ.fillable?
# This is an editable champ. Lets find what controllers it might need.
controllers = []
# This is a public champ it can have an autosave controller.
if @champ.public?
controllers << 'autosave'
end
controllers = ['autosave']
# This is a dropdown champ. Activate special behaviours it might have.
if @champ.simple_drop_down_list? || @champ.linked_drop_down_list?

View file

@ -60,10 +60,6 @@ class TypesDeChampEditor::ChampComponent < ApplicationComponent
TypeDeChamp.type_champs
.keys
# FIXME
# We can only refresh after update champs when autosave is enabled. And it is disabled for now in private forms.
# So for new we restrict champs that require refresh after update to public forms.
.filter { type_de_champ.public? || !TypeDeChamp.refresh_after_update?(_1) }
.filter(&method(:filter_type_champ))
.filter(&method(:filter_featured_type_champ))
.filter(&method(:filter_block_type_champ))

View file

@ -0,0 +1,22 @@
module TurboChampsConcern
extend ActiveSupport::Concern
private
def champs_to_turbo_update(params, champs)
champ_ids = params.keys.map(&:to_i)
to_update = champs.filter { _1.id.in?(champ_ids) && _1.refresh_after_update? }
to_show, to_hide = champs.filter(&:conditional?)
.partition(&:visible?)
.map { champs_to_one_selector(_1 - to_update) }
return to_show, to_hide, to_update
end
def champs_to_one_selector(champs)
champs
.map { "##{_1.input_group_id}" }
.join(',')
end
end

View file

@ -4,6 +4,7 @@ module Instructeurs
include ActionView::Helpers::TextHelper
include CreateAvisConcern
include DossierHelper
include TurboChampsConcern
include ActionController::Streaming
include Zipline
@ -244,12 +245,14 @@ module Instructeurs
if dossier.champs_private_all.any?(&:changed?)
dossier.last_champ_private_updated_at = Time.zone.now
end
dossier.save
dossier.log_modifier_annotations!(current_instructeur)
if !dossier.save(context: :annotations)
flash.now.alert = dossier.errors.full_messages
end
respond_to do |format|
format.html { redirect_to annotations_privees_instructeur_dossier_path(procedure, dossier) }
format.turbo_stream
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_params.fetch(:champs_private_all_attributes), dossier.champs_private_all)
end
end
end

View file

@ -1,6 +1,7 @@
module Users
class DossiersController < UserController
include DossierHelper
include TurboChampsConcern
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
@ -198,7 +199,7 @@ module Users
respond_to do |format|
format.html { render :brouillon }
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
render(:update, layout: false)
end
@ -216,7 +217,7 @@ module Users
respond_to do |format|
format.html { render :modifier }
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
end
end
end
@ -483,24 +484,6 @@ module Users
errors
end
def champs_to_turbo_update
champ_ids = champs_public_params
.fetch(:champs_public_all_attributes)
.keys
.map(&:to_i)
to_update = dossier
.champs_public_all
.filter { _1.id.in?(champ_ids) && _1.refresh_after_update? }
to_show, to_hide = dossier
.champs_public_all
.filter(&:conditional?)
.partition(&:visible?)
.map { champs_to_one_selector(_1 - to_update) }
return to_show, to_hide, to_update
end
def ensure_ownership!
if !current_user.owns?(dossier)
forbidden!
@ -561,12 +544,5 @@ module Users
submit_validation_options
end
end
def champs_to_one_selector(champs)
champs
.map(&:input_group_id)
.map { |id| "##{id}" }
.join(',')
end
end
end

View file

@ -21,8 +21,6 @@ module Mutations
end
if annotation.save
dossier.log_modifier_annotation!(annotation, instructeur)
{ annotation: }
else
{ errors: annotation.errors.full_messages }

View file

@ -45,10 +45,7 @@ export class AutosaveController extends ApplicationController {
this.#latestPromise = Promise.resolve();
this.onGlobal('autosave:retry', () => this.didRequestRetry());
this.on('change', (event) => this.onChange(event));
if (this.saveOnInput) {
this.on('input', (event) => this.onInput(event));
}
this.on('input', (event) => this.onInput(event));
}
disconnect() {
@ -86,8 +83,7 @@ export class AutosaveController extends ApplicationController {
this.debounce(this.enqueueAutosaveRequest, AUTOSAVE_DEBOUNCE_DELAY);
} else if (
isSelectElement(target) ||
isCheckboxOrRadioInputElement(target) ||
(!this.saveOnInput && isTextInputElement(target))
isCheckboxOrRadioInputElement(target)
) {
// Wait next tick so champs having JS can interact
// with form elements before extracting form data.
@ -138,10 +134,6 @@ export class AutosaveController extends ApplicationController {
}, AUTOSAVE_CONDITIONAL_SPINNER_DEBOUNCE_DELAY);
}
private get saveOnInput() {
return !!this.form?.dataset.saveOnInput;
}
private didRequestRetry() {
if (this.#needsRetry) {
this.enqueueAutosaveRequest();

View file

@ -1062,16 +1062,6 @@ class Dossier < ApplicationRecord
end
end
def log_modifier_annotations!(instructeur)
champs_private.filter(&:value_previously_changed?).each do |champ|
log_dossier_operation(instructeur, :modifier_annotation, champ)
end
end
def log_modifier_annotation!(champ, instructeur)
log_dossier_operation(instructeur, :modifier_annotation, champ)
end
def demander_un_avis!(avis)
log_dossier_operation(avis.claimant, :demander_un_avis, avis)
end

View file

@ -0,0 +1,8 @@
- if @to_show.present?
= turbo_stream.show_all(@to_show)
- if @to_hide.present?
= turbo_stream.hide_all(@to_hide)
- @to_update.each do |champ|
= fields_for champ.input_name, champ do |form|
= turbo_stream.replace champ.input_group_id do
= render EditableChamp::EditableChampComponent.new champ:, form:

View file

@ -1,25 +0,0 @@
.autosave.autosave-state-idle{ data: { controller: 'autosave-status' } }
%p.autosave-explanation.fr-text--sm
%span.autosave-explanation-text
- if dossier.brouillon?
= t('views.users.dossiers.autosave.draft_explanation')
- else
= t('views.users.dossiers.autosave.explanation')
= link_to t('views.users.dossiers.autosave.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
%p.autosave-status.succeeded
%span.autosave-icon.icon.accept
%span.autosave-label
- if dossier.brouillon?
= t('views.users.dossiers.autosave.draft_confirmation')
- else
= t('views.users.dossiers.autosave.confirmation')
= link_to t('views.users.dossiers.autosave.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
%p.autosave-status.failed
%span.autosave-icon ⚠️
%span.autosave-label Impossible denregistrer le brouillon
%button.button.small.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
%span.autosave-retry-label réessayer
%span.autosave-retrying-label enregistrement en cours…

View file

@ -6,7 +6,7 @@
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier }
- if dossier.brouillon?
- form_options = { url: brouillon_dossier_url(dossier), method: :patch, data: { save_on_input: true } }
- form_options = { url: brouillon_dossier_url(dossier), method: :patch }
- else
- form_options = { url: modifier_dossier_url(dossier), method: :patch }
= render Attachment::DeleteFormComponent.new
@ -47,4 +47,4 @@
= fields_for champ.input_name, champ do |form|
= render EditableChamp::EditableChampComponent.new form: form, champ: champ
= render Dossiers::EditFooterComponent.new(dossier: dossier)
= render Dossiers::EditFooterComponent.new(dossier: dossier, annotation: false)

View file

@ -7,9 +7,6 @@
= fields_for champ.input_name, champ do |form|
= render EditableChamp::EditableChampComponent.new form: form, champ: champ, seen_at: seen_at
- if !dossier.for_procedure_preview?
.send-wrapper
= f.submit 'Sauvegarder', class: 'button primary send', data: { disable: true }
= render Dossiers::EditFooterComponent.new(dossier: dossier, annotation: true)
- else
%h2.empty-text Aucune annotation privée

View file

@ -112,6 +112,7 @@ ignore_unused:
- 'helpers.page_entries_info.*'
- 'combo_search_component.result_slot_html.*'
- 'combo_search_component.screen_reader_instructions'
- 'links.common.*'
# - '{devise,kaminari,will_paginate}.*'
# - 'simple_form.{yes,no}'
# - 'simple_form.{placeholders,hints,labels}.*'

View file

@ -281,12 +281,6 @@ en:
users:
dossiers:
archived_dossier: "Your file will be kept %{duree_conservation_dossiers_dans_ds} more months"
autosave:
explanation: Your file is automatically saved.
confirmation: File saved
draft_explanation: Your draft is automatically saved.
draft_confirmation: Draft saved
more_information: More informations
identite:
identity_data: Identity data
all_required: All fields are required.

View file

@ -277,12 +277,6 @@ fr:
users:
dossiers:
archived_dossier: "Votre dossier sera conservé %{duree_conservation_dossiers_dans_ds} mois supplémentaire"
autosave:
explanation: Votre dossier est automatiquement enregistré.
confirmation: Dossier enregistré
draft_explanation: Votre brouillon est automatiquement enregistré.
draft_confirmation: Brouillon enregistré
more_information: En savoir plus
identite:
identity_data: Données didentité
all_required: Tous les champs sont obligatoires.

View file

@ -24,7 +24,7 @@ describe EditableChamp::EditableChampComponent, type: :component do
context 'when a private champ' do
let(:champ) { create(:champ, dossier: dossier, private: true) }
it { expect(subject).to eq('') }
it { expect(subject).to eq('autosave') }
end
context 'when a dossier is en_construction' do
@ -41,7 +41,7 @@ describe EditableChamp::EditableChampComponent, type: :component do
end
context 'when a private dropdown champ' do
let(:controllers) { ['champ-dropdown'] }
let(:controllers) { ['autosave', 'champ-dropdown'] }
let(:champ) { create(:champ_drop_down_list, dossier: dossier, private: true) }
it { expect(subject).to eq(data) }
@ -56,7 +56,7 @@ describe EditableChamp::EditableChampComponent, type: :component do
end
context 'when a private dropdown champ' do
let(:controllers) { ['champ-dropdown'] }
let(:controllers) { ['autosave', 'champ-dropdown'] }
let(:champ) { create(:champ_drop_down_list, dossier: dossier, private: true) }
it { expect(subject).to eq(data) }

View file

@ -771,7 +771,7 @@ describe Instructeurs::DossiersController, type: :controller do
expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :annotations_privees)
another_instructeur.follow(dossier)
Timecop.freeze(now)
patch :update_annotations, params: params
patch :update_annotations, params: params, format: :turbo_stream
champ_multiple_drop_down_list.reload
champ_linked_drop_down_list.reload
@ -819,7 +819,7 @@ describe Instructeurs::DossiersController, type: :controller do
expect(champ_datetime.value).to eq('2019-12-21T13:17:00+01:00')
expect(champ_repetition.champs.first.value).to eq('text')
expect(dossier.reload.last_champ_private_updated_at).to eq(now)
expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier))
expect(response).to have_http_status(200)
}
it 'updates the annotations' do
@ -848,7 +848,7 @@ describe Instructeurs::DossiersController, type: :controller do
it {
expect(dossier.reload.last_champ_private_updated_at).to eq(nil)
expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier))
expect(response).to have_http_status(200)
}
end
end