From ff62e99e7be81fb694cfb431551871bcb97cc793 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 24 Jul 2024 17:18:02 +0200 Subject: [PATCH] refactor(contact): suggest email correction, strict email validation, fix admin form --- app/controllers/support_controller.rb | 82 +++++++------- app/helpers/application_helper.rb | 2 +- app/jobs/helpscout_create_conversation_job.rb | 31 +++++- .../helpscout/{form_adapter.rb => form.rb} | 97 ++++++++-------- app/views/support/_form.html.haml | 58 ++++++++++ app/views/support/admin.html.haml | 47 +------- app/views/support/index.html.haml | 2 +- config/i18n-tasks.yml | 1 + config/locales/en.yml | 3 - config/locales/fr.yml | 3 - config/locales/views/support/en.yml | 66 +++++++---- config/locales/views/support/fr.yml | 65 +++++++---- spec/controllers/support_controller_spec.rb | 72 +++++++++--- .../helpscout_create_conversation_job_spec.rb | 69 ++++++++---- spec/lib/helpscout/form_adapter_spec.rb | 104 ------------------ 15 files changed, 371 insertions(+), 331 deletions(-) rename app/lib/helpscout/{form_adapter.rb => form.rb} (51%) create mode 100644 app/views/support/_form.html.haml delete mode 100644 spec/lib/helpscout/form_adapter_spec.rb diff --git a/app/controllers/support_controller.rb b/app/controllers/support_controller.rb index e2e536499..f967f6fa2 100644 --- a/app/controllers/support_controller.rb +++ b/app/controllers/support_controller.rb @@ -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]}]

#{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 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 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 177d5196e..3cfe2b1c1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -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 diff --git a/app/jobs/helpscout_create_conversation_job.rb b/app/jobs/helpscout_create_conversation_job.rb index a8175d029..068fe6511 100644 --- a/app/jobs/helpscout_create_conversation_job.rb +++ b/app/jobs/helpscout_create_conversation_job.rb @@ -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 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 4127e9dc2..647c4e2a1 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 @@ 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 diff --git a/app/views/support/_form.html.haml b/app/views/support/_form.html.haml new file mode 100644 index 000000000..bbe8747fe --- /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 28a53bc72..1771256fc 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 26727bcbe..237e5fa86 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 82d29a1c9..f2a6e6cc6 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 9737fcab8..be6edf0fc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -52,9 +52,6 @@ en: asterisk_html: "Fields marked by an asterisk ( required ) 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 2c6469d62..dfcb5d7b8 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 ( obligatoire ) 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 970129276..6378df37e 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: + '

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.

' procedure_info: question: I've encountered a problem while completing my application - answer_html: "

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).

" + answer_html: + '

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).

' instruction_info: question: I have a question about the instruction of my application - answer_html: "

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.

" + answer_html: + '

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.

' product: question: I have an idea to improve the website - answer_html: "

Got an idea? Please check our enhancement dashboard :

- " + answer_html: + '

Got an idea? Please check our enhancement dashboard :

+ ' lost_user: question: I am having trouble finding the procedure I am looking for - answer_html: "

We invite you to contact the administration in charge of the procedure so they can provide you the link. - It should look like this: %{base_url}/commencer/NOM_DE_LA_DEMARCHE.

-

You can find here the most popular procedures (licence, detr, etc.).

" + answer_html: + '

We invite you to contact the administration in charge of the procedure so they can provide you the link. + It should look like this: %{base_url}/commencer/NOM_DE_LA_DEMARCHE.

+

You can find here the most popular procedures (licence, detr, etc.).

' other: question: Other topic admin: - your_question: Your question - admin_intro_html: "

As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone.

+ admin_intro_html: + '

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.

" + 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.

' 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 diff --git a/config/locales/views/support/fr.yml b/config/locales/views/support/fr.yml index 02f520625..eb6f74a58 100644 --- a/config/locales/views/support/fr.yml +++ b/config/locales/views/support/fr.yml @@ -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 l’endroit à 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: + '

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 :

" +
  • Proposez votre propre idée.
  • ' lost_user: question: Je ne trouve pas la démarche que je veux faire - 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.).

    " + 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 281c7e4a3..55915b0f1 100644 --- a/spec/controllers/support_controller_spec.rb +++ b/spec/controllers/support_controller_spec.rb @@ -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 diff --git a/spec/jobs/helpscout_create_conversation_job_spec.rb b/spec/jobs/helpscout_create_conversation_job_spec.rb index 1220e538d..1aa36a5d4 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 @@ 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 diff --git a/spec/lib/helpscout/form_adapter_spec.rb b/spec/lib/helpscout/form_adapter_spec.rb deleted file mode 100644 index 8eaa085a6..000000000 --- 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