Carica oggetto e corpo dell'email appena si sceglie un template A/B.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -166,19 +166,18 @@ class MailingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def apply_template_if_needed
|
def apply_template_if_needed
|
||||||
copy_template(params.dig(:mailing, :mail_template_id), :subject, :body_html, params[:use_template_content])
|
copy_template_if_blank(params.dig(:mailing, :mail_template_id), :subject, :body_html)
|
||||||
copy_template(params.dig(:mailing, :mail_template_b_id), :subject_b, :body_html_b, params[:use_template_b_content])
|
copy_template_if_blank(params.dig(:mailing, :mail_template_b_id), :subject_b, :body_html_b)
|
||||||
end
|
end
|
||||||
|
|
||||||
def copy_template(template_id, subject_attr, body_attr, flag)
|
def copy_template_if_blank(template_id, subject_attr, body_attr)
|
||||||
return if template_id.blank?
|
return if template_id.blank?
|
||||||
return unless ActiveModel::Type::Boolean.new.cast(flag)
|
|
||||||
|
|
||||||
template = MailTemplate.for_project(current_project).find_by(id: template_id)
|
template = MailTemplate.for_project(current_project).find_by(id: template_id)
|
||||||
return unless template
|
return unless template
|
||||||
|
|
||||||
@mailing.public_send("#{subject_attr}=", template.subject)
|
@mailing.public_send("#{subject_attr}=", template.subject) if @mailing.public_send(subject_attr).blank?
|
||||||
@mailing.public_send("#{body_attr}=", template.body_html)
|
@mailing.public_send("#{body_attr}=", template.body_html) if @mailing.public_send(body_attr).blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
def rebuild_recipients_if_needed
|
def rebuild_recipients_if_needed
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ module ApplicationHelper
|
|||||||
"w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none focus:ring-1 focus:ring-zinc-400 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500 dark:focus:ring-zinc-500"
|
"w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none focus:ring-1 focus:ring-zinc-400 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500 dark:focus:ring-zinc-500"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mail_templates_picker_payload(templates)
|
||||||
|
Array(templates).map { |template| { id: template.id, subject: template.subject, body_html: template.body_html } }
|
||||||
|
end
|
||||||
|
|
||||||
def card_class
|
def card_class
|
||||||
"rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100"
|
"rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["select", "subject"]
|
||||||
|
static values = { templates: Array }
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.pendingHtml = null
|
||||||
|
this.onEditorReady = this.onEditorReady.bind(this)
|
||||||
|
this.editorElement?.addEventListener("trix-initialize", this.onEditorReady)
|
||||||
|
if (this.shouldPrefill()) this.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
this.editorElement?.removeEventListener("trix-initialize", this.onEditorReady)
|
||||||
|
}
|
||||||
|
|
||||||
|
apply() {
|
||||||
|
const template = this.currentTemplate()
|
||||||
|
if (!template) return
|
||||||
|
|
||||||
|
if (this.hasSubjectTarget) {
|
||||||
|
this.subjectTarget.value = template.subject || ""
|
||||||
|
this.subjectTarget.dispatchEvent(new Event("input", { bubbles: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setBody(template.body_html || "")
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldPrefill() {
|
||||||
|
if (!this.selectTarget.value) return false
|
||||||
|
const subjectBlank = !this.hasSubjectTarget || this.subjectTarget.value.trim() === ""
|
||||||
|
return subjectBlank && this.bodyIsBlank()
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTemplate() {
|
||||||
|
const id = Number(this.selectTarget.value)
|
||||||
|
if (!id) return null
|
||||||
|
return (this.templatesValue || []).find((template) => Number(template.id) === id) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
setBody(html) {
|
||||||
|
this.pendingHtml = html
|
||||||
|
const editor = this.editorElement?.editor
|
||||||
|
if (editor) {
|
||||||
|
editor.loadHTML(html)
|
||||||
|
this.pendingHtml = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.hiddenInput) this.hiddenInput.value = html
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditorReady() {
|
||||||
|
if (this.pendingHtml != null) {
|
||||||
|
this.editorElement.editor?.loadHTML(this.pendingHtml)
|
||||||
|
this.pendingHtml = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.shouldPrefill()) this.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyIsBlank() {
|
||||||
|
const value = this.hiddenInput?.value || ""
|
||||||
|
return value.replace(/<[^>]*>/g, "").replace(/ /g, " ").trim() === ""
|
||||||
|
}
|
||||||
|
|
||||||
|
get editorElement() {
|
||||||
|
return this.element.querySelector("trix-editor")
|
||||||
|
}
|
||||||
|
|
||||||
|
get hiddenInput() {
|
||||||
|
return this.element.querySelector("[data-wysiwyg-target='input']")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<%= f.check_box :ab_test, { data: { ab_test_target: "checkbox", action: "ab-test#toggle" } } %>
|
<%= f.check_box :ab_test, { data: { ab_test_target: "checkbox", action: "ab-test#toggle" } } %>
|
||||||
<span>
|
<span>
|
||||||
<span class="font-medium">Test A/B su questa comunicazione</span>
|
<span class="font-medium">Test A/B su questa comunicazione</span>
|
||||||
<span class="mt-1 block text-xs text-zinc-500">Due versioni di oggetto e HTML. Ogni destinatario riceve A o B.</span>
|
<span class="mt-1 block text-xs text-zinc-500">Due versioni della stessa email. Carica un template A e un template B nelle colonne: ogni destinatario ne riceve una.</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -48,48 +48,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% templates_payload = mail_templates_picker_payload(@mail_templates) %>
|
||||||
<div class="grid gap-6 <%= "lg:grid-cols-2" if mailing.ab_test? %>" data-ab-test-target="grid">
|
<div class="grid gap-6 <%= "lg:grid-cols-2" if mailing.ab_test? %>" data-ab-test-target="grid">
|
||||||
<div class="space-y-4">
|
<%= tag.div class: "space-y-4", data: { controller: "template-picker", template_picker_templates_value: templates_payload } do %>
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-zinc-500">Variante A</h2>
|
<h2 class="text-sm font-semibold uppercase tracking-wide text-zinc-500">Variante A</h2>
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium">Template A (opzionale)</label>
|
<label class="mb-1 block text-sm font-medium">Template A</label>
|
||||||
<%= f.collection_select :mail_template_id, @mail_templates, :id, :name, { include_blank: "Nessuno" }, { class: input_class } %>
|
<%= f.collection_select :mail_template_id, @mail_templates, :id, :name,
|
||||||
|
{ include_blank: "Nessuno — scrivi a mano" },
|
||||||
|
{ class: input_class, data: { template_picker_target: "select", action: "change->template-picker#apply" } } %>
|
||||||
|
<p class="mt-1 text-xs text-zinc-500">Scegliendo un template, oggetto e testo si copiano qui sotto. Poi puoi modificarli solo per questo invio.</p>
|
||||||
</div>
|
</div>
|
||||||
<% unless mailing.new_record? %>
|
|
||||||
<label class="flex items-center gap-2 text-sm">
|
|
||||||
<%= check_box_tag :use_template_content, "1", false %>
|
|
||||||
Sostituisci oggetto e HTML A dal template
|
|
||||||
</label>
|
|
||||||
<% end %>
|
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium">Oggetto A</label>
|
<label class="mb-1 block text-sm font-medium">Oggetto A</label>
|
||||||
<%= f.text_field :subject, required: true, class: input_class %>
|
<%= f.text_field :subject, required: true, class: input_class, data: { template_picker_target: "subject" } %>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<%= render "shared/wysiwyg_field", form: f, method: :body_html, label: "Corpo HTML A" %>
|
<%= render "shared/wysiwyg_field", form: f, method: :body_html, label: "Corpo HTML A" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
|
|
||||||
<div class="space-y-4 <%= "hidden" unless mailing.ab_test? %>" data-ab-test-target="panel">
|
<%= tag.div class: "space-y-4 #{'hidden' unless mailing.ab_test?}", data: { controller: "template-picker", template_picker_templates_value: templates_payload, ab_test_target: "panel" } do %>
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-zinc-500">Variante B</h2>
|
<h2 class="text-sm font-semibold uppercase tracking-wide text-zinc-500">Variante B</h2>
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium">Template B (opzionale)</label>
|
<label class="mb-1 block text-sm font-medium">Template B</label>
|
||||||
<%= f.collection_select :mail_template_b_id, @mail_templates, :id, :name, { include_blank: "Nessuno" }, { class: input_class } %>
|
<%= f.collection_select :mail_template_b_id, @mail_templates, :id, :name,
|
||||||
|
{ include_blank: "Nessuno — scrivi a mano" },
|
||||||
|
{ class: input_class, data: { template_picker_target: "select", action: "change->template-picker#apply" } } %>
|
||||||
|
<p class="mt-1 text-xs text-zinc-500">Stesso meccanismo della variante A: il template riempie oggetto e corpo B.</p>
|
||||||
</div>
|
</div>
|
||||||
<% unless mailing.new_record? %>
|
|
||||||
<label class="flex items-center gap-2 text-sm">
|
|
||||||
<%= check_box_tag :use_template_b_content, "1", false %>
|
|
||||||
Sostituisci oggetto e HTML B dal template
|
|
||||||
</label>
|
|
||||||
<% end %>
|
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium">Oggetto B</label>
|
<label class="mb-1 block text-sm font-medium">Oggetto B</label>
|
||||||
<%= f.text_field :subject_b, class: input_class %>
|
<%= f.text_field :subject_b, class: input_class, data: { template_picker_target: "subject" } %>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<%= render "shared/wysiwyg_field", form: f, method: :body_html_b, label: "Corpo HTML B" %>
|
<%= render "shared/wysiwyg_field", form: f, method: :body_html_b, label: "Corpo HTML B" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -104,4 +104,53 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
body = (mail.html_part || mail).body.to_s
|
body = (mail.html_part || mail).body.to_s
|
||||||
assert_match(/Versione B/, body)
|
assert_match(/Versione B/, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "new mailing form embeds template content for the picker" do
|
||||||
|
login_as users(:admin)
|
||||||
|
follow_redirect! if response.redirect?
|
||||||
|
template = MailTemplate.create!(
|
||||||
|
project: @project,
|
||||||
|
name: "Variante B picker",
|
||||||
|
subject: "Oggetto dal template B",
|
||||||
|
body_html: "<p>Corpo dal template B</p>"
|
||||||
|
)
|
||||||
|
|
||||||
|
get new_mailing_path(project_code: @project.code)
|
||||||
|
assert_response :success
|
||||||
|
assert_match(/template-picker/, response.body)
|
||||||
|
assert_match(/Oggetto dal template B/, response.body)
|
||||||
|
assert_match(/Corpo dal template B/, response.body)
|
||||||
|
assert_includes response.body, template.id.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create copies blank variant B fields from selected template" do
|
||||||
|
login_as users(:admin)
|
||||||
|
follow_redirect! if response.redirect?
|
||||||
|
template_b = MailTemplate.create!(
|
||||||
|
project: @project,
|
||||||
|
name: "Template B server",
|
||||||
|
subject: "B {{societa}}",
|
||||||
|
body_html: "<p>Versione B dal template</p>"
|
||||||
|
)
|
||||||
|
|
||||||
|
post mailings_path(project_code: @project.code), params: {
|
||||||
|
mailing: {
|
||||||
|
name: "AB da template",
|
||||||
|
mail_identity_id: @identity.id,
|
||||||
|
mail_template_b_id: template_b.id,
|
||||||
|
audience: "to_send",
|
||||||
|
ab_test: "1",
|
||||||
|
ab_assignment: "from_record",
|
||||||
|
subject: "Oggetto A",
|
||||||
|
body_html: "<p>Versione A</p>",
|
||||||
|
subject_b: "",
|
||||||
|
body_html_b: "",
|
||||||
|
interval_seconds: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mailing = Mailing.last
|
||||||
|
assert_redirected_to mailing_path(mailing, project_code: @project.code)
|
||||||
|
assert_equal "B {{societa}}", mailing.subject_b
|
||||||
|
assert_includes mailing.body_html_b, "Versione B dal template"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user