From 49be3a797a6b521ef50702bb769e942ba92585d3 Mon Sep 17 00:00:00 2001
From: Colin Darie Contact us via this form and we will answer you as quickly as possible. Make sure you provide all the required information so we can help you in the best way. Are you sure that all the mandatory fields ( * ) are properly filled?
- If you have questions about the information requested, contact the service in charge of the procedure (FAQ). Are you sure that all the mandatory fields ( * ) are properly filled?
+ If you have questions about the information requested, contact the service in charge of the procedure (FAQ). If you have questions about the instruction of your application (response delay for example), contact directly the instructor via our mail system (FAQ). 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. If you have questions about the instruction of your application (response delay for example), contact directly the instructor via our mail system (FAQ). 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. Got an idea? Please check our enhancement dashboard : Got an idea? Please check our enhancement dashboard : We invite you to contact the administration in charge of the procedure so they can provide you the link.
- It should look like this: You can find here the most popular procedures (licence, detr, etc.). We invite you to contact the administration in charge of the procedure so they can provide you the link.
+ It should look like this: You can find here the most popular procedures (licence, detr, etc.). As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone. As an administration, you can contact us through this form. We''ll answer you as quickly as possibly by e-mail or phone. Caution, this form is dedicated to public bodies only.
- It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us here.
#{params[:text]}"
+ piece_jointe: support_form_params[:piece_jointe],
+ body: "[#{support_form_params[:subject]}]
#{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 direct_message?
- user_signed_in? && params[:type] == Helpscout::FormAdapter::TYPE_INSTRUCTION && dossier.present? && dossier.messagerie_available?
+ def tags_from_query_params
+ support_form_params[:tags]&.join(",") || ""
end
- def dossier
- @dossier ||= current_user&.dossiers&.find_by(id: params[:dossier_id])
+ def direct_message?
+ user_signed_in? && support_form_params[:type] == Helpscout::Form::TYPE_INSTRUCTION && dossier.present? && dossier.messagerie_available?
end
- def email
- current_user&.email || params[:email]
+ def dossier
+ @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
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 177d5196ed3..3cfe2b1c152 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -74,7 +74,7 @@ def contact_link(title, options = {})
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
diff --git a/app/jobs/helpscout_create_conversation_job.rb b/app/jobs/helpscout_create_conversation_job.rb
index a8175d0294c..068fe651130 100644
--- a/app/jobs/helpscout_create_conversation_job.rb
+++ b/app/jobs/helpscout_create_conversation_job.rb
@@ -8,7 +8,9 @@ class FileNotScannedYetError < StandardError
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 @@ def perform(blob_id: nil, **args)
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
diff --git a/app/lib/helpscout/form_adapter.rb b/app/lib/helpscout/form.rb
similarity index 51%
rename from app/lib/helpscout/form_adapter.rb
rename to app/lib/helpscout/form.rb
index 4127e9dc252..647c4e2a170 100644
--- a/app/lib/helpscout/form_adapter.rb
+++ b/app/lib/helpscout/form.rb
@@ -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 @@ def self.admin_options
]
end
- def initialize(params = {}, api = nil)
- @params = params
- @api = api || Helpscout::API.new
- end
-
- 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 initialize(params)
+ @current_user = params.delete(:current_user)
+ params[:email] = EmailSanitizableConcern::EmailSanitizer.sanitize(params[:email]) if params[:email].present?
+ super(params)
- 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
-
- def add_tags(conversation_id)
- @api.add_tags(conversation_id, tags)
- end
+ alias for_admin? for_admin
- def tags
- (params[:tags].presence || []) + ['contact form']
+ def tags_array
+ (tags&.split(",") || []) + ['contact form', type]
end
- def create_conversation
- response = @api.create_conversation(
- params[:email],
- params[:subject],
- params[:text],
- params[:blob]
- )
+ def require_email? = current_user.blank?
- 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
diff --git a/app/views/support/_form.html.haml b/app/views/support/_form.html.haml
new file mode 100644
index 00000000000..bbe8747fea7
--- /dev/null
+++ b/app/views/support/_form.html.haml
@@ -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 }
diff --git a/app/views/support/admin.html.haml b/app/views/support/admin.html.haml
index 28a53bc7243..1771256fc49 100644
--- a/app/views/support/admin.html.haml
+++ b/app/views/support/admin.html.haml
@@ -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
diff --git a/app/views/support/index.html.haml b/app/views/support/index.html.haml
index 26727bcbeb3..237e5fa86b6 100644
--- a/app/views/support/index.html.haml
+++ b/app/views/support/index.html.haml
@@ -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
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 82d29a1c910..f2a6e6cc65d 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -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'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 9737fcab837..be6edf0fc52 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -52,9 +52,6 @@ en:
asterisk_html: "Fields marked by an asterisk ( ) 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:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 2c6469d62e4..dfcb5d7b816 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -43,9 +43,6 @@ fr:
asterisk_html: "Les champs suivis d’un astérisque ( ) 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:
diff --git a/config/locales/views/support/en.yml b/config/locales/views/support/en.yml
index 97012927680..6378df37e29 100644
--- a/config/locales/views/support/en.yml
+++ b/config/locales/views/support/en.yml
@@ -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:
+ '
"
+ answer_html:
+ '
'
lost_user:
question: I am having trouble finding the procedure I am looking for
- answer_html: "%{base_url}/commencer/NOM_DE_LA_DEMARCHE
.%{base_url}/commencer/NOM_DE_LA_DEMARCHE
.
Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.
+Pensez bien à nous donner le plus d’informations possible pour que nous puissions vous aider au mieux.
' procedure_info: question: J’ai un problème lors du remplissage de mon dossier - answer_html: "Avez-vous bien vérifié que tous les champs obligatoires ( * ) sont remplis ? -
Si vous avez des questions sur les informations à saisir, contactez les services en charge de la démarche (FAQ).
" + answer_html: + 'Avez-vous bien vérifié que tous les champs obligatoires ( * ) sont remplis ? +
Si vous avez des questions sur les informations à saisir, contactez les services en charge de la démarche (FAQ).
' instruction_info: question: J’ai une question sur l’instruction de mon dossier - answer_html: "Si vous avez des questions sur l’instruction de votre dossier (par exemple sur les délais), nous vous invitons à contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ).
-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 l’instruction de votre dossier.
" + answer_html: + 'Si vous avez des questions sur l’instruction de votre dossier (par exemple sur les délais), nous vous invitons à contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ).
+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 l’instruction de votre dossier.
' product: question: J’ai une idée d’amélioration pour votre site - answer_html: "Une idée ? Pensez à consulter notre tableau de bord des améliorations :
+ answer_html: + 'Une idée ? Pensez à consulter notre tableau de bord des améliorations :
Nous vous invitons à contacter l’administration en charge de votre démarche pour qu’elle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : %{base_url}/commencer/NOM_DE_LA_DEMARCHE
.
Vous pouvez aussi consulter ici la liste de nos démarches les plus fréquentes (permis, detr, etc.).
" + answer_html: + 'Nous vous invitons à contacter l’administration en charge de votre démarche pour qu’elle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : %{base_url}/commencer/NOM_DE_LA_DEMARCHE
.
Vous pouvez aussi consulter ici la liste de nos démarches les plus fréquentes (permis, detr, etc.).
' other: question: Autre sujet + admin: - your_question: Votre question - admin_intro_html: "En tant qu’administration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.
+ admin_intro_html: + 'En tant qu’administration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.
Attention, ce formulaire est réservé uniquement aux organismes publics. - Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d’utilité publique). Si c'est votre cas, rendez-vous sur notre - formulaire de contact public.
" + Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d’utilité publique). Si c''est votre cas, rendez-vous sur notre + formulaire de contact public.' 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: J’ai 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: J’ai un problème technique avec %{app_name} - admin suggestion produit: + admin_suggestion_produit: question: J’ai 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 diff --git a/spec/controllers/support_controller_spec.rb b/spec/controllers/support_controller_spec.rb index 281c7e4a3dc..55915b0f124 100644 --- a/spec/controllers/support_controller_spec.rb +++ b/spec/controllers/support_controller_spec.rb @@ -12,7 +12,7 @@ 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 "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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 diff --git a/spec/jobs/helpscout_create_conversation_job_spec.rb b/spec/jobs/helpscout_create_conversation_job_spec.rb index 1220e538d64..1aa36a5d467 100644 --- a/spec/jobs/helpscout_create_conversation_job_spec.rb +++ b/spec/jobs/helpscout_create_conversation_job_spec.rb @@ -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 @@ 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 @@ 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 @@ 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 @@ 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 diff --git a/spec/lib/helpscout/form_adapter_spec.rb b/spec/lib/helpscout/form_adapter_spec.rb deleted file mode 100644 index 8eaa085a622..00000000000 --- a/spec/lib/helpscout/form_adapter_spec.rb +++ /dev/null @@ -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 From 4bc0a04106e07cdce3b2a80c831929945b0a55b1 Mon Sep 17 00:00:00 2001 From: Colin DarieContact us via this form and we will answer you as quickly as possible.
-Make sure you provide all the required information so we can help you in the best way.
' - notice_email: 'Expected format: address@mail.com' + form: your_question: Your question our_answer: Our answer notice_pj_product: A screenshot can help us identify the element to improve. diff --git a/config/locales/views/support/fr.yml b/config/locales/views/support/fr.yml index eb6f74a5808..e3ab7a913cd 100644 --- a/config/locales/views/support/fr.yml +++ b/config/locales/views/support/fr.yml @@ -1,7 +1,7 @@ fr: - activemodel: + activerecord: attributes: - helpscout/form: + contact_form: email: 'Votre adresse email' email_pro: Votre adresse email professionnelle phone: Numéro de téléphone professionnel (ligne directe) @@ -12,15 +12,10 @@ fr: email: 'Exemple: adresse@mail.com' errors: models: - helpscout/form: + contact_form: invalid_email_format: 'est invalide' support: - index: - contact: Contact - intro_html: - 'Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.
-Pensez bien à nous donner le plus d’informations possible pour que nous puissions vous aider au mieux.
' - notice_email: 'Format attendu : adresse@mail.com' + form: your_question: Votre question our_answer: Notre réponse notice_pj_product: Une capture d’écran peut nous aider à identifier plus facilement l’endroit à améliorer. diff --git a/spec/controllers/support_controller_spec.rb b/spec/controllers/support_controller_spec.rb index 55915b0f124..4e19ffb09a0 100644 --- a/spec/controllers/support_controller_spec.rb +++ b/spec/controllers/support_controller_spec.rb @@ -1,4 +1,4 @@ -describe SupportController, type: :controller do +describe SupportController, question_type: :controller do render_views context 'signed in' do @@ -45,22 +45,30 @@ get :index, params: { tags: tags } expect(response.status).to eq(200) - expect(response.body).to include(tags.join(',')) + expect(response.body).to include("value=\"yolo\"") + expect(response.body).to include("value=\"toto\"") end end describe "send form" do subject do - post :create, params: { helpscout_form: params } + post :create, params: { contact_form: params } end context "when invisible captcha is ignored" do - let(:params) { { subject: 'bonjour', text: 'un message', type: 'procedure_info' } } + let(:params) { { subject: 'bonjour', text: 'un message', question_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))) + change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + + expect(contact_form.subject).to eq("bonjour") + expect(contact_form.text).to eq("un message") + expect(contact_form.tags).to include("procedure_info") expect(flash[:notice]).to match('Votre message a été envoyé.') expect(response).to redirect_to root_path @@ -73,7 +81,7 @@ let(:params) do { dossier_id: dossier.id, - type: Helpscout::Form::TYPE_INSTRUCTION, + question_type: ContactForm::TYPE_INSTRUCTION, subject: 'bonjour', text: 'un message' } @@ -82,7 +90,11 @@ it 'creates a conversation on HelpScout' do expect { subject }.to \ change(Commentaire, :count).by(0).and \ - have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(subject: 'bonjour', dossier_id: dossier.id)) + change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + expect(contact_form.dossier_id).to eq(dossier.id) expect(flash[:notice]).to match('Votre message a été envoyé.') expect(response).to redirect_to root_path @@ -96,7 +108,7 @@ let(:params) do { dossier_id: dossier.id, - type: Helpscout::Form::TYPE_INSTRUCTION, + question_type: ContactForm::TYPE_INSTRUCTION, subject: 'bonjour', text: 'un message' } @@ -120,7 +132,7 @@ context "when invisible captcha is filled" do subject do post :create, params: { - helpscout_form: { subject: 'bonjour', text: 'un message', type: 'procedure_info' }, + contact_form: { subject: 'bonjour', text: 'un message', question_type: 'procedure_info' }, InvisibleCaptcha.honeypots.sample => 'boom' } end @@ -156,15 +168,19 @@ describe 'send form' do subject do - post :create, params: { helpscout_form: params } + post :create, params: { contact_form: params } end - let(:params) { { subject: 'bonjour', email: "me@rspec.net", text: 'un message', type: 'procedure_info' } } + let(:params) { { subject: 'bonjour', email: "me@rspec.net", text: 'un message', question_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))) + change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + expect(contact_form.email).to eq("me@rspec.net") expect(flash[:notice]).to match('Votre message a été envoyé.') expect(response).to redirect_to root_path @@ -184,39 +200,57 @@ end context 'contact admin' do - subject do - post :create, params: { helpscout_form: params } + context 'index' do + it 'should have professionnal email field' do + get :admin + expect(response.body).to have_text('Votre adresse email professionnelle') + expect(response.body).to have_text('téléphone') + expect(response.body).to include('for_admin') + end end - 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(:for_admin, :type))) - expect(flash[:notice]).to match('Votre message a été envoyé.') + context 'create' do + subject do + post :create, params: { contact_form: params } end - context "with a piece justificative" do - let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } - let(:params) { super().merge(piece_jointe: logo) } + let(:params) { { for_admin: "true", email: "email@pro.fr", subject: 'bonjour', text: 'un message', question_type: 'admin question', phone: '06' } } - it "create blob and pass it to conversation job" do - expect { subject }.to \ - change(ActiveStorage::Blob, :count).by(1).and \ - have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(blob_id: Integer)).and \ - have_enqueued_job(VirusScannerJob) + describe "when form is filled" do + it "creates a conversation on HelpScout" do + expect { subject }.to change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + expect(contact_form.email).to eq(params[:email]) + expect(contact_form.phone).to eq("06") + expect(contact_form.tags).to match_array(["admin question", "contact form"]) + + expect(flash[:notice]).to match('Votre message a été envoyé.') end - end - end - describe "when invisible captcha is filled" do - subject do - post :create, params: { helpscout_form: params, InvisibleCaptcha.honeypots.sample => 'boom' } + context "with a piece justificative" do + let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } + let(:params) { super().merge(piece_jointe: logo) } + + it "create blob and pass it to conversation job" do + expect { subject }.to change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(contact_form.piece_jointe).to be_attached + end + end end - it 'does not create a conversation on HelpScout' do - subject - expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans')) + describe "when invisible captcha is filled" do + subject do + post :create, params: { contact_form: params, InvisibleCaptcha.honeypots.sample => 'boom' } + end + + it 'does not create a conversation on HelpScout' do + subject + expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans')) + end end end end diff --git a/spec/jobs/helpscout_create_conversation_job_spec.rb b/spec/jobs/helpscout_create_conversation_job_spec.rb index 1aa36a5d467..5b45084f4d9 100644 --- a/spec/jobs/helpscout_create_conversation_job_spec.rb +++ b/spec/jobs/helpscout_create_conversation_job_spec.rb @@ -6,16 +6,9 @@ let(:subject_text) { 'Bonjour' } let(:text) { "J'ai un pb" } let(:tags) { ["first tag"] } + let(:question_type) { "lost" } let(:phone) { nil } - let(:params) { - { - email:, - subject: subject_text, - text:, - tags:, - phone: - } - } + let(:contact_form) { create(:contact_form, email:, subject: subject_text, text:, tags:, phone:, question_type:) } describe '#perform' do before do @@ -26,56 +19,55 @@ headers: { 'Resource-ID' => 'new-conversation-id' } )) allow(api).to receive(:add_tags) - allow(api).to receive(:add_phone_number) if params[:phone].present? + allow(api).to receive(:add_phone_number) if phone.present? end subject { - described_class.perform_now(**params) + described_class.perform_now(contact_form) } - context 'when blob_id is not present' do + context 'when no file is attached' do it 'sends the form without a file' do 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) + expect(api).to have_received(:add_tags).with("new-conversation-id", match_array(tags.concat(["contact form", question_type]))) + expect(contact_form).to be_destroyed end end - context 'when blob_id is present' do - let(:blob) { - ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png") - } - let(:params) { super().merge(blob_id: blob.id) } - + context 'when a file is attached' do 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) + file = fixture_file_upload('spec/fixtures/files/white.png', 'image/png') + contact_form.piece_jointe.attach(file) end context 'when the file has not been scanned yet' do - let(:pending) { true } - let(:safe) { false } + before do + allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: true, safe?: false)) + end - it 'reenqueue job' do - expect { subject }.to have_enqueued_job(described_class).with(params) + it 'reenqueues job' do + expect { subject }.to have_enqueued_job(described_class).with(contact_form) end end context 'when the file is safe' do - let(:pending) { false } - let(:safe) { true } + before do + allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: false, safe?: true)) + end - it 'downloads the file and sends the form' do + it 'sends the form with the file' do subject - expect(api).to have_received(:create_conversation).with(email, subject_text, text, blob) + expect(api).to have_received(:create_conversation).with(email, subject_text, text, contact_form.piece_jointe) end end context 'when the file is not safe' do - let(:pending) { false } - let(:safe) { false } + before do + allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: false, safe?: false)) + end - it 'ignore the file' do + it 'ignores the file' do subject expect(api).to have_received(:create_conversation).with(email, subject_text, text, nil) end @@ -84,6 +76,7 @@ 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) From e71c1781a91bf38ed6b97131f38906a41831e099 Mon Sep 17 00:00:00 2001 From: Colin Darie