refactor(contact): suggest email correction, strict email validation, fix admin form

This commit is contained in:
Colin Darie 2024-07-24 17:18:02 +02:00
parent 49be3a797a
commit ff62e99e7b
No known key found for this signature in database
GPG key ID: 4FB865FDBCA4BCC4
15 changed files with 371 additions and 331 deletions

View file

@ -2,11 +2,11 @@ class SupportController < ApplicationController
invisible_captcha only: [:create], on_spam: :redirect_to_root
def index
setup_context
@form = Helpscout::Form.new(tags: tags_from_query_params, dossier_id: dossier&.id, current_user:)
end
def admin
setup_context_admin
@form = Helpscout::Form.new(tags: tags_from_query_params, current_user:, for_admin: true)
end
def create
@ -17,85 +17,79 @@ class SupportController < ApplicationController
return
end
create_conversation_later
flash.notice = "Votre message a été envoyé."
@form = Helpscout::Form.new(support_form_params.except(:piece_jointe).merge(current_user:))
if params[:admin]
redirect_to root_path(formulaire_contact_admin_submitted: true)
if @form.valid?
create_conversation_later(@form)
flash.notice = "Votre message a été envoyé."
redirect_to root_path
else
redirect_to root_path(formulaire_contact_general_submitted: true)
flash.alert = @form.errors.full_messages
render @form.for_admin ? :admin : :index
end
end
private
def setup_context
@dossier_id = dossier&.id
@tags = tags
@options = Helpscout::FormAdapter.options
end
def setup_context_admin
@tags = tags
@options = Helpscout::FormAdapter.admin_options
end
def create_conversation_later
if params[:piece_jointe]
def create_conversation_later(form)
if support_form_params[:piece_jointe].present?
blob = ActiveStorage::Blob.create_and_upload!(
io: params[:piece_jointe].tempfile,
filename: params[:piece_jointe].original_filename,
content_type: params[:piece_jointe].content_type,
io: support_form_params[:piece_jointe].tempfile,
filename: support_form_params[:piece_jointe].original_filename,
content_type: support_form_params[:piece_jointe].content_type,
identify: false
).tap(&:scan_for_virus_later)
end
HelpscoutCreateConversationJob.perform_later(
blob_id: blob&.id,
subject: params[:subject],
email: email,
phone: params[:phone],
text: params[:text],
dossier_id: dossier&.id,
subject: form.subject,
email: current_user&.email || form.email,
phone: form.phone,
text: form.text,
dossier_id: form.dossier_id,
browser: browser_name,
tags: tags
tags: form.tags_array
)
end
def create_commentaire
attributes = {
piece_jointe: params[:piece_jointe],
body: "[#{params[:subject]}]<br><br>#{params[:text]}"
piece_jointe: support_form_params[:piece_jointe],
body: "[#{support_form_params[:subject]}]<br><br>#{support_form_params[:text]}"
}
CommentaireService.create!(current_user, dossier, attributes)
end
def tags
[params[:tags], params[:type]].flatten.compact
.map { |tag| tag.split(',') }
.flatten
.compact_blank.uniq
end
def browser_name
if browser.known?
"#{browser.name} #{browser.version} (#{browser.platform.name})"
end
end
def tags_from_query_params
support_form_params[:tags]&.join(",") || ""
end
def direct_message?
user_signed_in? && params[:type] == Helpscout::FormAdapter::TYPE_INSTRUCTION && dossier.present? && dossier.messagerie_available?
user_signed_in? && support_form_params[:type] == Helpscout::Form::TYPE_INSTRUCTION && dossier.present? && dossier.messagerie_available?
end
def dossier
@dossier ||= current_user&.dossiers&.find_by(id: params[:dossier_id])
end
def email
current_user&.email || params[:email]
@dossier ||= current_user&.dossiers&.find_by(id: support_form_params[:dossier_id])
end
def redirect_to_root
redirect_to root_path, alert: t('invisible_captcha.sentence_for_humans')
end
def support_form_params
keys = [:email, :subject, :text, :type, :dossier_id, :piece_jointe, :phone, :tags, :for_admin]
if params.key?(:helpscout_form) # submitting form
params.require(:helpscout_form).permit(*keys)
else
params.permit(:dossier_id, tags: []) # prefilling form
end
end
end

View file

@ -74,7 +74,7 @@ module ApplicationHelper
tags, type, dossier_id = options.values_at(:tags, :type, :dossier_id)
options.except!(:tags, :type, :dossier_id)
params = { tags: tags, type: type, dossier_id: dossier_id }.compact
params = { tags: Array.wrap(tags), type: type, dossier_id: dossier_id }.compact
link_to title, contact_url(params), options
end

View file

@ -8,7 +8,9 @@ class HelpscoutCreateConversationJob < ApplicationJob
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
def perform(blob_id: nil, **args)
attr_reader :api
def perform(blob_id: nil, **params)
if blob_id.present?
blob = ActiveStorage::Blob.find(blob_id)
raise FileNotScannedYetError if blob.virus_scanner.pending?
@ -16,6 +18,31 @@ class HelpscoutCreateConversationJob < ApplicationJob
blob = nil unless blob.virus_scanner.safe?
end
Helpscout::FormAdapter.new(**args, blob:).send_form
@api = Helpscout::API.new
create_conversation(params, blob)
end
private
def create_conversation(params, blob)
response = api.create_conversation(
params[:email],
params[:subject],
params[:text],
blob
)
if response.success?
conversation_id = response.headers['Resource-ID']
if params[:phone].present?
api.add_phone_number(params[:email], params[:phone])
end
api.add_tags(conversation_id, params[:tags])
else
fail "Error while creating conversation: #{response.response_code} '#{response.body}'"
end
end
end

View file

@ -1,7 +1,39 @@
class Helpscout::FormAdapter
attr_reader :params
class Helpscout::Form
include ActiveModel::Model
include ActiveModel::Attributes
def self.options
attribute :email, :string
attribute :subject, :string
attribute :text, :string
attribute :type, :string
attribute :dossier_id, :integer
attribute :tags, :string
attribute :phone, :string
attribute :tags, :string
attribute :for_admin, :boolean, default: false
validates :email, presence: true, strict_email: true, if: :require_email? # i18n-tasks-use t('activemodel.errors.models.helpscout/form.invalid_email_format')
validates :subject, presence: true
validates :text, presence: true
validates :type, presence: true
attr_reader :current_user
attr_reader :options
TYPE_INFO = 'procedure_info'
TYPE_PERDU = 'lost_user'
TYPE_INSTRUCTION = 'instruction_info'
TYPE_AMELIORATION = 'product'
TYPE_AUTRE = 'other'
ADMIN_TYPE_RDV = 'admin_demande_rdv'
ADMIN_TYPE_QUESTION = 'admin_question'
ADMIN_TYPE_SOUCIS = 'admin_soucis'
ADMIN_TYPE_PRODUIT = 'admin_suggestion_produit'
ADMIN_TYPE_DEMANDE_COMPTE = 'admin_demande_compte'
ADMIN_TYPE_AUTRE = 'admin_autre'
def self.default_options
[
[I18n.t(:question, scope: [:support, :index, TYPE_INFO]), TYPE_INFO, I18n.t("links.common.faq.contacter_service_en_charge_url")],
[I18n.t(:question, scope: [:support, :index, TYPE_PERDU]), TYPE_PERDU, LISTE_DES_DEMARCHES_URL],
@ -22,60 +54,25 @@ class Helpscout::FormAdapter
]
end
def initialize(params = {}, api = nil)
@params = params
@api = api || Helpscout::API.new
end
def initialize(params)
@current_user = params.delete(:current_user)
params[:email] = EmailSanitizableConcern::EmailSanitizer.sanitize(params[:email]) if params[:email].present?
super(params)
TYPE_INFO = 'procedure_info'
TYPE_PERDU = 'lost_user'
TYPE_INSTRUCTION = 'instruction_info'
TYPE_AMELIORATION = 'product'
TYPE_AUTRE = 'other'
ADMIN_TYPE_RDV = 'admin demande rdv'
ADMIN_TYPE_QUESTION = 'admin question'
ADMIN_TYPE_SOUCIS = 'admin soucis'
ADMIN_TYPE_PRODUIT = 'admin suggestion produit'
ADMIN_TYPE_DEMANDE_COMPTE = 'admin demande compte'
ADMIN_TYPE_AUTRE = 'admin autre'
def send_form
conversation_id = create_conversation
if conversation_id.present?
add_tags(conversation_id)
true
@options = if for_admin?
self.class.admin_options
else
false
self.class.default_options
end
end
private
alias for_admin? for_admin
def add_tags(conversation_id)
@api.add_tags(conversation_id, tags)
def tags_array
(tags&.split(",") || []) + ['contact form', type]
end
def tags
(params[:tags].presence || []) + ['contact form']
end
def require_email? = current_user.blank?
def create_conversation
response = @api.create_conversation(
params[:email],
params[:subject],
params[:text],
params[:blob]
)
if response.success?
if params[:phone].present?
@api.add_phone_number(params[:email], params[:phone])
end
response.headers['Resource-ID']
else
raise StandardError, "Error while creating conversation: #{response.response_code} '#{response.body}'"
end
end
def persisted? = false
end

View file

@ -0,0 +1,58 @@
= form_for form, url: contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do |f|
%p.fr-hint-text= t('asterisk_html', scope: [:utils])
- if form.require_email?
= render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field, opts: { autocomplete: 'email' }) do |c|
- c.with_label { Helpscout::Form.human_attribute_name(form.for_admin? ? :email_pro : :email) }
%fieldset.fr-fieldset{ name: "type" }
%legend.fr-fieldset__legend.fr-fieldset__legend--regular
= t('.your_question')
= render EditableChamp::AsteriskMandatoryComponent.new
.fr-fieldset__content
- form.options.each do |(question, question_type, link)|
.fr-radio-group
= f.radio_button :type, question_type, required: true, data: {"support-target": "inputRadio" }, checked: question_type == form.type
= f.label "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do
= question
- if link.present?
.fr-ml-3w{ id: "card-#{question_type}",
class: class_names('hidden' => question_type != form.type),
"aria-hidden": question_type != form.type,
data: { "support-target": "content" } }
= render Dsfr::CalloutComponent.new(title: t('.our_answer')) do |c|
- c.with_html_body do
-# i18n-tasks-use t("support.index.#{question_type}.answer_html")
= t('answer_html', scope: [:support, :index, question_type], base_url: Current.application_base_url, "link_#{question_type}": link)
- if form.for_admin?
= render Dsfr::InputComponent.new(form: f, attribute: :phone, required: false)
- else
= render Dsfr::InputComponent.new(form: f, attribute: :dossier_id, required: false)
= render Dsfr::InputComponent.new(form: f, attribute: :subject)
= render Dsfr::InputComponent.new(form: f, attribute: :text, input_type: :text_area, opts: { rows: 6 })
- if !form.for_admin?
.fr-upload-group
= f.label :piece_jointe, class: 'fr-label' do
= t('pj', scope: [:utils])
%span.fr-hint-text
= t('.notice_upload_group')
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::Form::TYPE_AMELIORATION } }
= t('.notice_pj_product')
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::Form::TYPE_AUTRE } }
= t('.notice_pj_other')
= f.file_field :piece_jointe, class: 'fr-upload', accept: '.jpg, .jpeg, .png, .pdf'
= f.hidden_field :tags
= f.hidden_field :for_admin
= invisible_captcha
.fr-input-group.fr-my-3w
= f.submit t('send_mail', scope: [:utils]), type: :submit, class: 'fr-btn', data: { disable: true }

View file

@ -1,49 +1,12 @@
- content_for(:title, 'Contact')
- content_for(:title, t('.contact_team'))
- content_for :footer do
= render partial: "root/footer"
#contact-form
.fr-container
%h1
= t('.contact_team')
.description
= t('.admin_intro_html', contact_path: contact_path)
%br
%p.mandatory-explanation= t('asterisk_html', scope: [:utils])
.fr-highlight= t('.admin_intro_html', contact_path: contact_path)
= form_tag contact_path, method: :post, class: 'form' do |f|
- if !user_signed_in?
.contact-champ
= label_tag :email do
= t('.pro_mail')
%span.mandatory *
= text_field_tag :email, params[:email], required: true
.contact-champ
= label_tag :type do
= t('.your_question')
%span.mandatory *
= select_tag :type, options_for_select(@options, params[:type])
.contact-champ
= label_tag :phone do
= t('.pro_phone_number')
= text_field_tag :phone
.contact-champ
= label_tag :subject do
= t('subject', scope: [:utils])
= text_field_tag :subject, params[:subject], required: false
.contact-champ
= label_tag :text do
= t('message', scope: [:utils])
%span.mandatory *
= text_area_tag :text, params[:text], rows: 6, required: true
= invisible_captcha
= hidden_field_tag :tags, @tags&.join(',')
= hidden_field_tag :admin, true
.send-wrapper
= button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'button send primary'
= render partial: "form", object: @form

View file

@ -7,7 +7,7 @@
%h1
= t('.contact')
= form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do
.fr-highlight= t('.intro_html')
.description
.recommandations

View file

@ -102,6 +102,7 @@ ignore_unused:
- 'activerecord.models.*'
- 'activerecord.attributes.*'
- 'activemodel.attributes.map_filter.*'
- 'activemodel.attributes.helpscout/form.*'
- 'activerecord.errors.*'
- 'errors.messages.blank'
- 'errors.messages.content_type_invalid'

View file

@ -52,9 +52,6 @@ en:
asterisk_html: "Fields marked by an asterisk ( <svg aria-label='required' class='icon mandatory' height='10' role='img' viewBox='0 0 1200 1200' width='10' xml:space='preserve' xmlns='http://www.w3.org/2000/svg'><desc>required</desc><path d='M489.838 29.354v443.603L68.032 335.894 0 545.285l421.829 137.086-260.743 358.876 178.219 129.398L600.048 811.84l260.673 358.806 178.146-129.398-260.766-358.783L1200 545.379l-68.032-209.403-421.899 137.07V29.443H489.84l-.002-.089z'></path></svg> ) are mandatory."
mandatory_champs: All fields are mandatory.
no_mandatory: (optional)
file_number: File number
subject: Subject
message: Message
send_mail: Send message
new_tab: New tab
helpers:

View file

@ -43,9 +43,6 @@ fr:
asterisk_html: "Les champs suivis dun astérisque ( <svg aria-label='obligatoire' class='icon mandatory' height='10' role='img' viewBox='0 0 1200 1200' width='10' xml:space='preserve' xmlns='http://www.w3.org/2000/svg'><desc>obligatoire</desc><path d='M489.838 29.354v443.603L68.032 335.894 0 545.285l421.829 137.086-260.743 358.876 178.219 129.398L600.048 811.84l260.673 358.806 178.146-129.398-260.766-358.783L1200 545.379l-68.032-209.403-421.899 137.07V29.443H489.84l-.002-.089z'></path></svg> ) sont obligatoires."
mandatory_champs: Tous les champs sont obligatoires.
no_mandatory: (facultatif)
file_number: Numéro de dossier
subject: Sujet
message: Message
send_mail: Envoyer le message
new_tab: "Nouvel onglet"
helpers:

View file

@ -1,4 +1,19 @@
en:
activemodel:
attributes:
helpscout/form:
email: 'Your email address'
email_pro: Professional email address
phone: Professional phone number (direct line)
subject: Subject
text: Message
dossier_id: File number
hints:
email: 'Example: address@mail.com'
errors:
models:
helpscout/form:
invalid_email_format: 'is not valid'
support:
index:
contact: Contact
@ -10,44 +25,51 @@ en:
our_answer: Our answer
notice_pj_product: A screenshot can help us identify the element to improve.
notice_pj_other: A screenshot can help us identify the issue.
notice_upload_group: "Maximum size: 200 MB. Supported formats: jpg, png, pdf."
notice_upload_group: 'Maximum size: 200 MB. Supported formats: jpg, png, pdf.'
index:
contact: Contact
intro_html:
'<p>Contact us via this form and we will answer you as quickly as possible.</p>
<p>Make sure you provide all the required information so we can help you in the best way.</p>'
procedure_info:
question: I've encountered a problem while completing my application
answer_html: "<p>Are you sure that all the mandatory fields (<span class= mandatory> * </span>) are properly filled?
<p>If you have questions about the information requested, <a href=\"%{link_procedure_info}\">contact the service in charge of the procedure (FAQ)</a>.</p>"
answer_html:
'<p>Are you sure that all the mandatory fields (<span class= mandatory> * </span>) are properly filled?
<p>If you have questions about the information requested, <a href="%{link_procedure_info}">contact the service in charge of the procedure (FAQ)</a>.</p>'
instruction_info:
question: I have a question about the instruction of my application
answer_html: "<p>If you have questions about the instruction of your application (response delay for example), <a href=\"%{link_instruction_info}\">contact directly the instructor via our mail system (FAQ)</a>.</p>
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>"
answer_html:
'<p>If you have questions about the instruction of your application (response delay for example), <a href="%{link_instruction_info}">contact directly the instructor via our mail system (FAQ)</a>.</p>
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>'
product:
question: I have an idea to improve the website
answer_html: "<p>Got an idea? <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Please check our enhancement dashboard - New tab\">Please check our enhancement dashboard</a> :</p>
<ul><li>Vote for your priority improvements</li>
<li>Share your own ideas</li></ul>"
answer_html:
'<p>Got an idea? <a href="%{link_product}" rel="noopener noreferrer" target="_blank" title="Please check our enhancement dashboard - New tab">Please check our enhancement dashboard</a> :</p>
<ul><li>Vote for your priority improvements</li>
<li>Share your own ideas</li></ul>'
lost_user:
question: I am having trouble finding the procedure I am looking for
answer_html: "<p>We invite you to contact the administration in charge of the procedure so they can provide you the link.
It should look like this: <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<p>You can <a href=\"%{link_lost_user}\">find here the most popular procedures (licence, detr, etc.)</a>.</p>"
answer_html:
'<p>We invite you to contact the administration in charge of the procedure so they can provide you the link.
It should look like this: <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<p>You can <a href="%{link_lost_user}">find here the most popular procedures (licence, detr, etc.)</a>.</p>'
other:
question: Other topic
admin:
your_question: Your question
admin_intro_html: "<p>As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone.</p>
admin_intro_html:
'<p>As an administration, you can contact us through this form. We''ll answer you as quickly as possibly by e-mail or phone.</p>
<p><strong>Caution, this form is dedicated to public bodies only.</strong>
It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href=\"%{contact_path}\">here</a>.</p>"
It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href="%{contact_path}">here</a>.</p>'
contact_team: Contact our team
pro_phone_number: Professional phone number (direct line)
pro_mail: Professional email address
admin question:
admin_question:
question: I have a question about %{app_name}
admin demande rdv:
admin_demande_rdv:
question: I request an appointment for an online presentation of %{app_name}
admin soucis:
admin_soucis:
question: I am facing a technical issue on %{app_name}
admin suggestion produit:
admin_suggestion_produit:
question: I have a suggestion for an evolution
admin demande compte:
admin_demande_compte:
question: I want to open an admin account with an Orange, Wanadoo, etc. email
admin autre:
admin_autre:
question: Other topic

View file

@ -1,4 +1,19 @@
fr:
activemodel:
attributes:
helpscout/form:
email: 'Votre adresse email'
email_pro: Votre adresse email professionnelle
phone: Numéro de téléphone professionnel (ligne directe)
subject: Sujet
text: Message
dossier_id: Numéro de dossier
hints:
email: 'Exemple: adresse@mail.com'
errors:
models:
helpscout/form:
invalid_email_format: 'est invalide'
support:
index:
contact: Contact
@ -10,44 +25,52 @@ fr:
our_answer: Notre réponse
notice_pj_product: Une capture décran peut nous aider à identifier plus facilement lendroit à améliorer.
notice_pj_other: Une capture décran peut nous aider à identifier plus facilement le problème.
notice_upload_group: "Taille maximale : 200 Mo. Formats supportés : jpg, png, pdf."
notice_upload_group: 'Taille maximale : 200 Mo. Formats supportés : jpg, png, pdf.'
index:
contact: Contact
intro_html:
'<p>Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.</p>
<p>Pensez bien à nous donner le plus dinformations possible pour que nous puissions vous aider au mieux.</p>'
procedure_info:
question: Jai un problème lors du remplissage de mon dossier
answer_html: "<p>Avez-vous bien vérifié que tous les champs obligatoires (<span class= mandatory> * </span>) sont remplis ?
<p>Si vous avez des questions sur les informations à saisir, <a href=\"%{link_procedure_info}\">contactez les services en charge de la démarche (FAQ)</a>.</p>"
answer_html:
'<p>Avez-vous bien vérifié que tous les champs obligatoires (<span class= mandatory> * </span>) sont remplis ?
<p>Si vous avez des questions sur les informations à saisir, <a href="%{link_procedure_info}">contactez les services en charge de la démarche (FAQ)</a>.</p>'
instruction_info:
question: Jai une question sur linstruction de mon dossier
answer_html: "<p>Si vous avez des questions sur linstruction de votre dossier (par exemple sur les délais), nous vous invitons à <a href=\"%{link_instruction_info}\">contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ)</a>.</p>
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur linstruction de votre dossier.</p>"
answer_html:
'<p>Si vous avez des questions sur linstruction de votre dossier (par exemple sur les délais), nous vous invitons à <a href="%{link_instruction_info}">contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ)</a>.</p>
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur linstruction de votre dossier.</p>'
product:
question: Jai une idée damélioration pour votre site
answer_html: "<p>Une idée ? Pensez à <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Consulter notre tableau de bord des améliorations - Nouvel onglet\">consulter notre tableau de bord des améliorations</a> :</p>
answer_html:
'<p>Une idée ? Pensez à <a href="%{link_product}" rel="noopener noreferrer" target="_blank" title="Consulter notre tableau de bord des améliorations - Nouvel onglet">consulter notre tableau de bord des améliorations</a> :</p>
<ul><li>Votez pour vos améliorations prioritaires,</li>
<li>Proposez votre propre idée.</li></ul>"
<li>Proposez votre propre idée.</li></ul>'
lost_user:
question: Je ne trouve pas la démarche que je veux faire
answer_html: "<p>Nous vous invitons à contacter ladministration en charge de votre démarche pour quelle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<p>Vous pouvez aussi <a href=\"%{link_lost_user}\">consulter ici la liste de nos démarches les plus fréquentes (permis, detr, etc.)</a>.</p>"
answer_html:
'<p>Nous vous invitons à contacter ladministration en charge de votre démarche pour quelle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<p>Vous pouvez aussi <a href="%{link_lost_user}">consulter ici la liste de nos démarches les plus fréquentes (permis, detr, etc.)</a>.</p>'
other:
question: Autre sujet
admin:
your_question: Votre question
admin_intro_html: "<p>En tant quadministration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.</p>
admin_intro_html:
'<p>En tant quadministration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.</p>
<p><strong>Attention, ce formulaire est réservé uniquement aux organismes publics.</strong>
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues dutilité publique). Si c'est votre cas, rendez-vous sur notre
<a href=\"%{contact_path}\">formulaire de contact public</a>.</p>"
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues dutilité publique). Si c''est votre cas, rendez-vous sur notre
<a href="%{contact_path}">formulaire de contact public</a>.</p>'
contact_team: Contactez notre équipe
pro_phone_number: Numéro de téléphone professionnel (ligne directe)
pro_mail: Adresse e-mail professionnelle
admin question:
admin_question:
question: Jai une question sur %{app_name}
admin demande rdv:
admin_demande_rdv:
question: Demande de RDV pour une présentation à distance de %{app_name}
admin soucis:
admin_soucis:
question: Jai un problème technique avec %{app_name}
admin suggestion produit:
admin_suggestion_produit:
question: Jai une proposition dévolution
admin demande compte:
admin_demande_compte:
question: Je souhaite ouvrir un compte administrateur avec un email Orange, Wanadoo, etc.
admin autre:
admin_autre:
question: Autre sujet

View file

@ -12,7 +12,7 @@ describe SupportController, type: :controller do
get :index
expect(response.status).to eq(200)
expect(response.body).not_to have_content("Email *")
expect(response.body).not_to have_content("Votre adresse email")
end
describe "with dossier" do
@ -51,29 +51,29 @@ describe SupportController, type: :controller do
describe "send form" do
subject do
post :create, params: params
post :create, params: { helpscout_form: params }
end
context "when invisible captcha is ignored" do
let(:params) { { subject: 'bonjour', text: 'un message' } }
let(:params) { { subject: 'bonjour', text: 'un message', type: 'procedure_info' } }
it 'creates a conversation on HelpScout' do
expect { subject }.to \
change(Commentaire, :count).by(0).and \
have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params))
have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:type)))
expect(flash[:notice]).to match('Votre message a été envoyé.')
expect(response).to redirect_to root_path(formulaire_contact_general_submitted: true)
expect(response).to redirect_to root_path
end
context 'when a drafted dossier is mentionned' do
let(:dossier) { create(:dossier) }
let(:user) { dossier.user }
subject do
post :create, params: {
let(:params) do
{
dossier_id: dossier.id,
type: Helpscout::FormAdapter::TYPE_INSTRUCTION,
type: Helpscout::Form::TYPE_INSTRUCTION,
subject: 'bonjour',
text: 'un message'
}
@ -85,7 +85,7 @@ describe SupportController, type: :controller do
have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(subject: 'bonjour', dossier_id: dossier.id))
expect(flash[:notice]).to match('Votre message a été envoyé.')
expect(response).to redirect_to root_path(formulaire_contact_general_submitted: true)
expect(response).to redirect_to root_path
end
end
@ -93,10 +93,10 @@ describe SupportController, type: :controller do
let(:dossier) { create(:dossier, :en_construction) }
let(:user) { dossier.user }
subject do
post :create, params: {
let(:params) do
{
dossier_id: dossier.id,
type: Helpscout::FormAdapter::TYPE_INSTRUCTION,
type: Helpscout::Form::TYPE_INSTRUCTION,
subject: 'bonjour',
text: 'un message'
}
@ -118,7 +118,13 @@ describe SupportController, type: :controller do
end
context "when invisible captcha is filled" do
let(:params) { { subject: 'bonjour', text: 'un message', InvisibleCaptcha.honeypots.sample => 'boom' } }
subject do
post :create, params: {
helpscout_form: { subject: 'bonjour', text: 'un message', type: 'procedure_info' },
InvisibleCaptcha.honeypots.sample => 'boom'
}
end
it 'does not create a conversation on HelpScout' do
expect { subject }.not_to change(Commentaire, :count)
expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans'))
@ -133,7 +139,7 @@ describe SupportController, type: :controller do
get :index
expect(response.status).to eq(200)
expect(response.body).to have_text("Email")
expect(response.body).to have_text("Votre adresse email")
end
end
@ -147,18 +153,46 @@ describe SupportController, type: :controller do
expect(response.body).to include(tag)
end
end
describe 'send form' do
subject do
post :create, params: { helpscout_form: params }
end
let(:params) { { subject: 'bonjour', email: "me@rspec.net", text: 'un message', type: 'procedure_info' } }
it 'creates a conversation on HelpScout' do
expect { subject }.to \
change(Commentaire, :count).by(0).and \
have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:type)))
expect(flash[:notice]).to match('Votre message a été envoyé.')
expect(response).to redirect_to root_path
end
context "when email is invalid" do
let(:params) { super().merge(email: "me@rspec") }
it 'creates a conversation on HelpScout' do
expect { subject }.not_to have_enqueued_job(HelpscoutCreateConversationJob)
expect(response.body).to include("Le champ « Votre adresse email » est invalide")
expect(response.body).to include("bonjour")
expect(response.body).to include("un message")
end
end
end
end
context 'contact admin' do
subject do
post :create, params: params
post :create, params: { helpscout_form: params }
end
let(:params) { { admin: "true", email: "email@pro.fr", subject: 'bonjour', text: 'un message' } }
let(:params) { { for_admin: "true", email: "email@pro.fr", subject: 'bonjour', text: 'un message', type: 'admin question', phone: '06' } }
describe "when form is filled" do
it "creates a conversation on HelpScout" do
expect { subject }.to have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:admin)))
expect { subject }.to have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:for_admin, :type)))
expect(flash[:notice]).to match('Votre message a été envoyé.')
end
@ -176,7 +210,9 @@ describe SupportController, type: :controller do
end
describe "when invisible captcha is filled" do
let(:params) { super().merge(InvisibleCaptcha.honeypots.sample => 'boom') }
subject do
post :create, params: { helpscout_form: params, InvisibleCaptcha.honeypots.sample => 'boom' }
end
it 'does not create a conversation on HelpScout' do
subject

View file

@ -1,16 +1,43 @@
require 'rails_helper'
RSpec.describe HelpscoutCreateConversationJob, type: :job do
let(:args) { { email: 'sender@email.com' } }
let(:api) { instance_double("Helpscout::API") }
let(:email) { 'help@rspec.net' }
let(:subject_text) { 'Bonjour' }
let(:text) { "J'ai un pb" }
let(:tags) { ["first tag"] }
let(:phone) { nil }
let(:params) {
{
email:,
subject: subject_text,
text:,
tags:,
phone:
}
}
describe '#perform' do
before do
allow(Helpscout::API).to receive(:new).and_return(api)
allow(api).to receive(:create_conversation)
.and_return(double(
success?: true,
headers: { 'Resource-ID' => 'new-conversation-id' }
))
allow(api).to receive(:add_tags)
allow(api).to receive(:add_phone_number) if params[:phone].present?
end
subject {
described_class.perform_now(**params)
}
context 'when blob_id is not present' do
it 'sends the form without a file' do
form_adapter = double('Helpscout::FormAdapter')
allow(Helpscout::FormAdapter).to receive(:new).with(hash_including(args.merge(blob: nil))).and_return(form_adapter)
expect(form_adapter).to receive(:send_form)
described_class.perform_now(**args)
subject
expect(api).to have_received(:create_conversation).with(email, subject_text, text, nil)
expect(api).to have_received(:add_tags).with("new-conversation-id", tags)
end
end
@ -18,9 +45,11 @@ RSpec.describe HelpscoutCreateConversationJob, type: :job do
let(:blob) {
ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png")
}
let(:params) { super().merge(blob_id: blob.id) }
before do
allow(blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: pending, safe?: safe))
allow(ActiveStorage::Blob).to receive(:find).with(blob.id).and_return(blob)
end
context 'when the file has not been scanned yet' do
@ -28,9 +57,7 @@ RSpec.describe HelpscoutCreateConversationJob, type: :job do
let(:safe) { false }
it 'reenqueue job' do
expect {
described_class.perform_now(blob_id: blob.id, **args)
}.to have_enqueued_job(described_class).with(blob_id: blob.id, **args)
expect { subject }.to have_enqueued_job(described_class).with(params)
end
end
@ -39,11 +66,8 @@ RSpec.describe HelpscoutCreateConversationJob, type: :job do
let(:safe) { true }
it 'downloads the file and sends the form' do
form_adapter = double('Helpscout::FormAdapter')
allow(Helpscout::FormAdapter).to receive(:new).with(hash_including(args.merge(blob:))).and_return(form_adapter)
allow(form_adapter).to receive(:send_form)
described_class.perform_now(blob_id: blob.id, **args)
subject
expect(api).to have_received(:create_conversation).with(email, subject_text, text, blob)
end
end
@ -51,14 +75,19 @@ RSpec.describe HelpscoutCreateConversationJob, type: :job do
let(:pending) { false }
let(:safe) { false }
it 'downloads the file and sends the form' do
form_adapter = double('Helpscout::FormAdapter')
allow(Helpscout::FormAdapter).to receive(:new).with(hash_including(args.merge(blob: nil))).and_return(form_adapter)
allow(form_adapter).to receive(:send_form)
described_class.perform_now(blob_id: blob.id, **args)
it 'ignore the file' do
subject
expect(api).to have_received(:create_conversation).with(email, subject_text, text, nil)
end
end
end
context 'with a phone' do
let(:phone) { "06" }
it 'associates the phone number' do
subject
expect(api).to have_received(:add_phone_number).with(email, phone)
end
end
end
end

View file

@ -1,104 +0,0 @@
describe Helpscout::FormAdapter do
describe '#send_form' do
let(:api) { spy(double(:api)) }
context 'create_conversation' do
before do
allow(api).to receive(:create_conversation)
.and_return(double(success?: true, headers: {}))
described_class.new(params, api).send_form
end
let(:params) {
{
email: email,
subject: subject,
text: text
}
}
let(:email) { 'paul.chavard@beta.gouv.fr' }
let(:subject) { 'Bonjour' }
let(:text) { "J'ai un problem" }
it 'should call method' do
expect(api).to have_received(:create_conversation)
.with(email, subject, text, nil)
end
end
context 'add_tags' do
before do
allow(api).to receive(:create_conversation)
.and_return(
double(
success?: true,
headers: {
'Resource-ID' => conversation_id
}
)
)
described_class.new(params, api).send_form
end
let(:params) {
{
email: email,
subject: subject,
text: text,
tags: tags
}
}
let(:email) { 'paul.chavard@beta.gouv.fr' }
let(:subject) { 'Bonjour' }
let(:text) { "J'ai un problem" }
let(:tags) { ['info demarche'] }
let(:conversation_id) { '123' }
it 'should call method' do
expect(api).to have_received(:create_conversation)
.with(email, subject, text, nil)
expect(api).to have_received(:add_tags)
.with(conversation_id, tags + ['contact form'])
end
end
context 'add_phone' do
before do
allow(api).to receive(:create_conversation)
.and_return(
double(
success?: true,
headers: {
'Resource-ID' => conversation_id
}
)
)
described_class.new(params, api).send_form
end
let(:params) {
{
email: email,
subject: subject,
text: text,
phone: '0666666666'
}
}
let(:phone) { '0666666666' }
let(:email) { 'paul.chavard@beta.gouv.fr' }
let(:subject) { 'Bonjour' }
let(:text) { "J'ai un problem" }
let(:tags) { ['info demarche'] }
let(:conversation_id) { '123' }
it 'should call method' do
expect(api).to have_received(:create_conversation)
.with(email, subject, text, nil)
expect(api).to have_received(:add_phone_number)
.with(email, phone)
end
end
end
end