Merge pull request #7197 from betagouv/7129-add_lien_dpo_to_procedure

feat(administrateur/procedure#create): allow admin to add a lien to the DPO, allow user to consult link to dpo. enhance spec on _procedure_footer.html
This commit is contained in:
mfo 2022-04-27 15:12:40 +02:00 committed by GitHub
commit 990c1ab6f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 149 additions and 17 deletions

View file

@ -254,7 +254,7 @@ module Administrateurs
end end
def procedure_params def procedure_params
editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :declarative_with_state, :logo, :auto_archive_on, :monavis_embed, :api_entreprise_token, :duree_conservation_dossiers_dans_ds, :zone_id] editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :declarative_with_state, :logo, :auto_archive_on, :monavis_embed, :api_entreprise_token, :duree_conservation_dossiers_dans_ds, :zone_id, :lien_dpo]
permited_params = if @procedure&.locked? permited_params = if @procedure&.locked?
params.require(:procedure).permit(*editable_params) params.require(:procedure).permit(*editable_params)
else else

View file

@ -7,7 +7,9 @@ module ConservationDeDonneesHelper
def conservation_dans_ds(procedure) def conservation_dans_ds(procedure)
if procedure.duree_conservation_dossiers_dans_ds.present? if procedure.duree_conservation_dossiers_dans_ds.present?
"Dans #{APPLICATION_NAME} : #{procedure.duree_conservation_dossiers_dans_ds} mois" I18n.t('users.procedure_footer.legals.data_retention',
application_name: APPLICATION_NAME,
duree_conservation_dossiers_dans_ds: procedure.duree_conservation_dossiers_dans_ds)
end end
end end
end end

View file

@ -74,4 +74,12 @@ module ProcedureHelper
.includes(:groupe_instructeur) .includes(:groupe_instructeur)
.exists?(groupe_instructeur: current_instructeur.groupe_instructeurs) .exists?(groupe_instructeur: current_instructeur.groupe_instructeurs)
end end
def url_or_email_to_lien_dpo(procedure)
URI::MailTo.build([procedure.lien_dpo, "subject="]).to_s
rescue URI::InvalidComponentError
uri = URI.parse(procedure.lien_dpo)
return "//#{uri}" if uri.scheme.nil?
uri.to_s
end
end end

View file

@ -28,6 +28,7 @@
# juridique_required :boolean default(TRUE) # juridique_required :boolean default(TRUE)
# libelle :string # libelle :string
# lien_demarche :string # lien_demarche :string
# lien_dpo :string
# lien_notice :string # lien_notice :string
# lien_site_web :string # lien_site_web :string
# monavis_embed :text # monavis_embed :text
@ -266,6 +267,7 @@ class Procedure < ApplicationRecord
validate :check_juridique validate :check_juridique
validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false } validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false }
validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION } validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }
validates :lien_dpo, email_or_link: true, allow_nil: true
validates_with MonAvisEmbedValidator validates_with MonAvisEmbedValidator
FILE_MAX_SIZE = 20.megabytes FILE_MAX_SIZE = 20.megabytes

View file

@ -0,0 +1,7 @@
class EmailOrLinkValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
URI.parse(value)
rescue URI::InvalidURIError
record.errors.add(attribute, :invalid_uri_or_email)
end
end

View file

@ -57,6 +57,13 @@
= f.label :deliberation, 'Importer le texte' = f.label :deliberation, 'Importer le texte'
= text_upload_and_render f, @procedure.deliberation = text_upload_and_render f, @procedure.deliberation
%h3.header-subsection
RGPD
%p.notice
Pour certaines démarches, veuillez indiquer soit un mail le mail de contact de votre délégué à la protection des données, soit un lien web pointant vers les informations
= f.label :lien_dpo, 'Lien ou email pour contacter le Délégué à la Protection des Données (DPO)'
= f.text_field :lien_dpo, class: 'form-control'
%h3.header-subsection Notice explicative de la démarche %h3.header-subsection Notice explicative de la démarche
%p.notice %p.notice

View file

@ -4,7 +4,7 @@
- if service.present? - if service.present?
.footer-row.footer-columns .footer-row.footer-columns
.footer-column .footer-column
%p.footer-header Cette démarche est gérée par : %p.footer-header= I18n.t('users.procedure_footer.managed_by.header')
%ul %ul
%li %li
= service.nom = service.nom
@ -14,46 +14,49 @@
= string_to_html(service.adresse, wrapper_tag = 'span') = string_to_html(service.adresse, wrapper_tag = 'span')
.footer-column .footer-column
%p.footer-header Poser une question sur votre dossier : %p.footer-header= I18n.t('users.procedure_footer.contact.header')
%ul %ul
%li %li
- if dossier.present? && dossier.messagerie_available? - if dossier.present? && dossier.messagerie_available?
Directement = I18n.t('users.procedure_footer.contact.in_app_mail.prefix')
= link_to "par la messagerie", messagerie_dossier_path(dossier) = link_to I18n.t('users.procedure_footer.contact.in_app_mail.link'), messagerie_dossier_path(dossier)
- else - else
Par email : = I18n.t('users.procedure_footer.contact.email.prefix')
= link_to service.email, "mailto:#{service.email}" = link_to service.email, "mailto:#{service.email}"
- if service.telephone.present? - if service.telephone.present?
%li %li
Par téléphone : = I18n.t('users.procedure_footer.contact.phone.prefix')
= link_to service.telephone, service.telephone_url = link_to service.telephone, service.telephone_url
%li %li
- horaires = "Horaires : #{formatted_horaires(service.horaires)}" - horaires = "#{I18n.t('users.procedure_footer.contact.schedule.prefix')}#{formatted_horaires(service.horaires)}"
= simple_format(horaires, {}, wrapper_tag: 'span') = simple_format(horaires, {}, wrapper_tag: 'span')
%li %li
Statistiques : = I18n.t('users.procedure_footer.contact.stats.prefix')
= link_to "voir les statistiques de la démarche", statistiques_path(procedure.path) = link_to I18n.t('users.procedure_footer.contact.stats.cta'), statistiques_path(procedure.path)
- politiques = politiques_conservation_de_donnees(procedure) - politiques = politiques_conservation_de_donnees(procedure)
- if politiques.present? - if politiques.present?
.footer-column .footer-column
%p.footer-header Conservation des données : %p.footer-header= I18n.t('users.procedure_footer.legals.header')
%ul %ul
- politiques.each do |politique| - politiques.each do |politique|
%li= politique %li= politique
%p.mt-2.footer-header Cadre juridique :
%ul
- if procedure.deliberation.attached? - if procedure.deliberation.attached?
%li %li
= link_to url_for(procedure.deliberation), target: '_blank', rel: 'noopener' do = link_to url_for(procedure.deliberation), target: '_blank', rel: 'noopener' do
= "Texte cadrant la demande d'information" = I18n.t("users.procedure_footer.legals.terms")
- else - else
%li %li
= link_to "Texte juridique la demande d'information", procedure.cadre_juridique, target: '_blank', rel: 'noopener' = link_to I18n.t("users.procedure_footer.legals.terms"), procedure.cadre_juridique, target: '_blank', rel: 'noopener'
- if procedure.lien_dpo.present?
%li
= link_to url_or_email_to_lien_dpo(procedure), target: '_blank', rel: 'noopener' do
= I18n.t("users.procedure_footer.legals.dpo")
= render partial: 'users/general_footer_row', locals: { dossier: dossier } = render partial: 'users/general_footer_row', locals: { dossier: dossier }

View file

@ -17,6 +17,7 @@ fr:
declarative_with_state/en_instruction: En instruction declarative_with_state/en_instruction: En instruction
declarative_with_state/accepte: Accepté declarative_with_state/accepte: Accepté
api_particulier_token: Jeton API Particulier api_particulier_token: Jeton API Particulier
lien_dpo: Contact du DPO
errors: errors:
models: models:
procedure: procedure:
@ -27,3 +28,5 @@ fr:
format: 'Le champ %{message}' format: 'Le champ %{message}'
draft_types_de_champ_private: draft_types_de_champ_private:
format: 'Lannotation privée %{message}' format: 'Lannotation privée %{message}'
lien_dpo:
invalid_uri_or_email: "Veuillez saisir un mail ou un lien"

View file

@ -0,0 +1,24 @@
en:
users:
procedure_footer:
managed_by:
header: 'This procedure is managed by :'
contact:
header: 'Ask a question about your file :'
in_app_mail:
prefix: 'Directly :'
link: "via the chat"
email:
prefix: 'By mail :'
phone:
prefix: 'By phone :'
schedule:
prefix: 'Hours : '
stats:
prefix: 'Stats :'
cta: "see the procedure's stats"
legals:
header: "Legals :"
data_retention: "Within %{application_name} : %{duree_conservation_dossiers_dans_ds} months"
terms: "Laws regarding this data collection"
dpo: "Contact the Data Protection Officer"

View file

@ -0,0 +1,24 @@
fr:
users:
procedure_footer:
managed_by:
header: 'Cette démarche est gérée par :'
contact:
header: 'Poser une question sur votre dossier :'
in_app_mail:
prefix: 'Directement :'
link: "par la messagerie"
email:
prefix: 'Par email :'
phone:
prefix: 'Par téléphone :'
schedule:
prefix: 'Horaires : '
stats:
prefix: 'Statistiques :'
cta: "voir les statistiques de la démarche"
legals:
header: "Cadre juridique :"
data_retention: "Dans %{application_name} : %{duree_conservation_dossiers_dans_ds} mois"
terms: "Texte cadrant la demande d'information"
dpo: "Contacter le Délégué à la Protection des Données"

View file

@ -0,0 +1,5 @@
class AddLienDpoToProcedure < ActiveRecord::Migration[6.1]
def change
add_column :procedures, :lien_dpo, :string
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_04_07_081538) do ActiveRecord::Schema.define(version: 2022_04_25_140107) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -645,6 +645,7 @@ ActiveRecord::Schema.define(version: 2022_04_07_081538) do
t.boolean "juridique_required", default: true t.boolean "juridique_required", default: true
t.string "libelle" t.string "libelle"
t.string "lien_demarche" t.string "lien_demarche"
t.string "lien_dpo"
t.string "lien_notice" t.string "lien_notice"
t.string "lien_site_web" t.string "lien_site_web"
t.text "monavis_embed" t.text "monavis_embed"

View file

@ -1185,6 +1185,13 @@ describe Procedure do
end end
end end
describe 'lien_dpo' do
it { expect(build(:procedure).valid?).to be(true) }
it { expect(build(:procedure, lien_dpo: 'dpo@ministere.amere').valid?).to be(true) }
it { expect(build(:procedure, lien_dpo: 'https://legal.fr/contact_dpo').valid?).to be(true) }
it { expect(build(:procedure, lien_dpo: 'askjdlad l akdj asd ').valid?).to be(false) }
end
private private
def create_dossier_with_pj_of_size(size, procedure) def create_dossier_with_pj_of_size(size, procedure)

View file

@ -27,4 +27,43 @@ describe 'users/procedure_footer.html.haml', type: :view do
it { is_expected.to have_link("Accessibilité") } it { is_expected.to have_link("Accessibilité") }
it { is_expected.not_to have_text('téléphone') } it { is_expected.not_to have_text('téléphone') }
end end
describe '#cadre_juridique' do
context 'when an external link is provided' do
before { dossier.procedure.update(cadre_juridique: "http://google.fr") }
it { is_expected.to have_link("Texte cadrant la demande d'information", href: 'http://google.fr') }
end
context 'when there is deliberation attached' do
before { dossier.procedure.update(cadre_juridique: nil, deliberation: fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf')) }
it { is_expected.to have_link("Texte cadrant la demande d'information") }
end
end
describe '#lien_dpo' do
context "when there is not lien_dpo" do
before { dossier.procedure.update(lien_dpo: nil) }
it { is_expected.not_to have_text('Contacter le Délégué à la Protection des Données') }
end
context "when there is a lien_dpo with an email" do
before { dossier.procedure.update(lien_dpo: 'dpo@beta.gouv.fr') }
it { is_expected.to have_selector('a[href="mailto:dpo@beta.gouv.fr?subject="]') }
end
context "when there is a lien_dpo with a schemaless link" do
before { dossier.procedure.update(lien_dpo: 'beta.gouv.fr') }
it { is_expected.to have_link('Contacter le Délégué à la Protection des Données', href: '//beta.gouv.fr') }
end
context "when there is a lien_dpo with a link with http:// schema" do
before { dossier.procedure.update(lien_dpo: 'http://beta.gouv.fr') }
it { is_expected.to have_link('Contacter le Délégué à la Protection des Données', href: 'http://beta.gouv.fr') }
end
context "when there is a lien_dpo with a link with https:// schema" do
before { dossier.procedure.update(lien_dpo: 'https://beta.gouv.fr') }
it { is_expected.to have_link('Contacter le Délégué à la Protection des Données', href: 'https://beta.gouv.fr') }
end
end
end end