Commit 142c5f71 authored by Martin Lowe's avatar Martin Lowe 🇨🇦 Committed by Martin Lowe
Browse files

Update mail templates with new formats+fields, remove duplicate

Collects data under single data object called MailerData to simplify
calls to the mailer. Removes author email templates and instead uses
base email template with an additional section.
parent d7cb7dc2
......@@ -54,6 +54,8 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
private String registrationCountry;
@SortableField
private Long dateCreated;
@SortableField
private Long dateSubmitted;
@NotNull(message = "The form state cannot be null")
private FormState state;
......@@ -133,6 +135,15 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
this.dateCreated = dateCreated;
}
public Long getDateSubmitted() {
return this.dateSubmitted;
}
@JsonbTransient
public void setDateSubmitted(Long dateSubmitted) {
this.dateSubmitted = dateSubmitted;
}
public FormState getState() {
return this.state;
}
......@@ -153,6 +164,9 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
if (getDateCreated() != null) {
target.setDateCreated(getDateCreated());
}
if (getDateSubmitted() != null) {
target.setDateSubmitted(getDateSubmitted());
}
return target;
}
......@@ -161,7 +175,7 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(id, membershipLevel, signingAuthority, userID, vatNumber,
registrationCountry, purchaseOrderRequired, dateCreated, state);
registrationCountry, purchaseOrderRequired, dateCreated, dateSubmitted, state);
return result;
}
......@@ -179,7 +193,7 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
&& Objects.equals(vatNumber, other.vatNumber) && Objects.equals(dateCreated, other.dateCreated)
&& Objects.equals(registrationCountry, other.registrationCountry)
&& Objects.equals(purchaseOrderRequired, other.purchaseOrderRequired)
&& Objects.equals(state, other.state);
&& Objects.equals(state, other.state) && Objects.equals(dateSubmitted, other.dateSubmitted);
}
@Override
......@@ -201,6 +215,8 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
builder.append(vatNumber);
builder.append(", dateCreated=");
builder.append(dateCreated);
builder.append(", dateSubmitted=");
builder.append(dateSubmitted);
builder.append(", state=");
builder.append(state);
builder.append("]");
......
package org.eclipsefoundation.react.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import io.quarkus.qute.TemplateData;
@TemplateData
public class MailerData {
public final MembershipForm form;
public final FormOrganization org;
public final List<FormWorkingGroup> wgs;
public final List<Contact> contacts;
public MailerData(MembershipForm form, FormOrganization org, List<FormWorkingGroup> wgs, List<Contact> contacts) {
this.form = form;
this.org = org;
this.wgs = Collections.unmodifiableList(wgs != null ? wgs: new ArrayList<>());
this.contacts = Collections.unmodifiableList(contacts != null ? contacts : new ArrayList<>());
}
}
......@@ -45,6 +45,7 @@ import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.dto.ValidationGroups.Completion;
import org.eclipsefoundation.react.model.ConstraintViolationWrapFactory;
import org.eclipsefoundation.react.model.ConstraintViolationWrapFactory.ConstraintViolationWrap;
import org.eclipsefoundation.react.model.MailerData;
import org.eclipsefoundation.react.namespace.FormState;
import org.eclipsefoundation.react.namespace.MembershipFormAPIParameterNames;
import org.eclipsefoundation.react.service.MailerService;
......@@ -205,10 +206,12 @@ public class MembershipFormResource extends AbstractRESTResource {
}
// send the forms to the mailing service
mailer.sendToFormAuthor(mf);
mailer.sendToMembershipTeam(mf, !orgs.isEmpty() ? orgs.get(0) : null, wgs, contacts);
MailerData data = new MailerData(mf, !orgs.isEmpty() ? orgs.get(0) : null, wgs, contacts);
mailer.sendToFormAuthor(data);
mailer.sendToMembershipTeam(data);
// update the state and push the update
mf.setDateSubmitted(System.currentTimeMillis());
mf.setState(FormState.SUBMITTED);
return Response.ok(dao.add(new RDBMSQuery<>(wrap, filters.get(MembershipForm.class)), Arrays.asList(mf)))
.build();
......
package org.eclipsefoundation.react.service;
import java.util.List;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.model.MailerData;
/**
* Interface defining emails that need to be generated and sent as part of the submission of the membership forms to the
......@@ -20,19 +15,15 @@ public interface MailerService {
* Sends an EMail message to the author of the form thanking them for their submission and interest. This request is
* asynchronous and has no return.
*
* @param form the form that is being submitted
* @param data the collected form data for the mailer
*/
void sendToFormAuthor(MembershipForm form);
void sendToFormAuthor(MailerData data);
/**
* Sends an email message to the membership team regarding the submitted form. This should list all of the
* information about the form and the submission for the membership team.
*
* @param form the membership form that was submitted
* @param org the organization associated with the membership form
* @param wgs the working groups associated with the membership form
* @param contacts contacts associated with the organization and working groups for the membership form
* @param data the collected form data for the mailer
*/
void sendToMembershipTeam(MembershipForm form, FormOrganization org, List<FormWorkingGroup> wgs,
List<Contact> contacts);
void sendToMembershipTeam(MailerData data);
}
package org.eclipsefoundation.react.service.impl;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
......@@ -7,10 +8,7 @@ import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.model.MailerData;
import org.eclipsefoundation.react.service.MailerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -46,32 +44,26 @@ public class DefaultMailerService implements MailerService {
Mailer mailer;
// Qute templates, generates email bodies
@Location("emails/form_author_email_web_template")
Template authorTemplateWeb;
@Location("emails/form_author_email_template")
Template authorTemplate;
@Location("emails/form_membership_email_web_template")
Template membershipTemplateWeb;
@Location("emails/form_membership_email_template")
Template membershipTemplate;
@Override
public void sendToFormAuthor(MembershipForm form) {
if (form == null) {
public void sendToFormAuthor(MailerData data) {
if (data == null || data.form == null) {
throw new IllegalStateException("A form is required to submit for mailing");
} else if (data.org == null || data.wgs == null || data.contacts == null || data.contacts.isEmpty()) {
throw new IllegalStateException(
"Could not find a fully complete form for form with ID '" + data.form.getId() + "'");
} else if (ident.isAnonymous()) {
// in the future we should fall back to accounts API
throw new IllegalStateException("A user must be logged in to send the author form message");
}
// convert the logged in user into a JWT token to read user claims
DefaultJWTCallerPrincipal defaultPrin = (DefaultJWTCallerPrincipal) ident.getPrincipal();
String name = generateName(defaultPrin);
// send the email, using the users primary email address
Mail m = Mail.withHtml(defaultPrin.getClaim("email"), "Thank you for completing the member enrollment form",
authorTemplateWeb.data("form", form, "name", name).render());
m.setText(authorTemplate.data("form", form, "name", name).render());
Mail m = getMail(defaultPrin.getClaim("email"), "Thank you for completing the member enrollment form", data,
true);
// add BCC if set
if (!authorMessageMailboxBcc.isEmpty()) {
m.setBcc(authorMessageMailboxBcc.get());
......@@ -80,21 +72,15 @@ public class DefaultMailerService implements MailerService {
}
@Override
public void sendToMembershipTeam(MembershipForm form, FormOrganization org, List<FormWorkingGroup> wgs,
List<Contact> contacts) {
if (form == null) {
public void sendToMembershipTeam(MailerData data) {
if (data == null || data.form == null) {
throw new IllegalStateException("A form is required to submit for mailing");
} else if (org == null || wgs == null || contacts == null || contacts.isEmpty()) {
} else if (data.org == null || data.wgs == null || data.contacts == null || data.contacts.isEmpty()) {
throw new IllegalStateException(
"Could not find a fully complete form for form with ID '" + form.getId() + "'");
"Could not find a fully complete form for form with ID '" + data.form.getId() + "'");
}
String name = generateName((DefaultJWTCallerPrincipal) ident.getPrincipal());
// generate the mail message, sending the messsage to the membershipMailbox
Mail m = Mail.withHtml(membershipMailbox, "New Request to join working group(s) - " + name,
membershipTemplateWeb.data("form", form, "org", org, "wgs", wgs, "contacts", contacts, "name", name)
.render());
m.setText(membershipTemplate.data("form", form, "org", org, "wgs", wgs, "contacts", contacts, "name", name)
.render());
Mail m = getMail(membershipMailbox, "New Request to join working group(s) - "
+ generateName((DefaultJWTCallerPrincipal) ident.getPrincipal()), data, false);
// add BCC if set
if (!membershipMessageMailboxBcc.isEmpty()) {
m.setBcc(membershipMessageMailboxBcc.get());
......@@ -102,6 +88,28 @@ public class DefaultMailerService implements MailerService {
mailer.send(m);
}
/**
* Centralize the creation of the mail object to reduce repetition. A preamble may be included at the top of the
* message based on the includePreamble argument.
*
* @param recipient the recipient of the mail message
* @param subject the subject line for the message
* @param data the collected form data that is the base of the email.
* @param includePreamble whether or not to include the preamble in the email.
* @return the mail message with text and HTML versions set.
*/
private Mail getMail(String recipient, String subject, MailerData data, boolean includePreamble) {
String name = generateName((DefaultJWTCallerPrincipal) ident.getPrincipal());
// generate the mail message, sending the messsage to the membershipMailbox
Mail m = Mail.withHtml(recipient, subject, membershipTemplateWeb
.data("data", data, "name", name, "now", LocalDateTime.now(), "includePreamble", includePreamble)
.render());
m.setText(membershipTemplate
.data("data", data, "name", name, "now", LocalDateTime.now(), "includePreamble", includePreamble)
.render());
return m;
}
private String generateName(DefaultJWTCallerPrincipal defaultPrin) {
return new StringBuilder().append((String) defaultPrin.getClaim("given_name")).append(" ")
.append((String) defaultPrin.getClaim("family_name")).toString();
......
......@@ -2,6 +2,7 @@ DROP TABLE IF EXISTS Address, FormWorkingGroup, Contact, FormOrganization, Membe
CREATE TABLE `MembershipForm` (
`id` varchar(255) NOT NULL,
`dateCreated` bigint(20) DEFAULT NULL,
`dateSubmitted` bigint(20) DEFAULT NULL,
`membershipLevel` varchar(255) DEFAULT NULL,
`purchaseOrderRequired` varchar(255) DEFAULT NULL,
`registrationCountry` varchar(255) DEFAULT NULL,
......
Dear {name},
Thank you for submitting the member enrollment form on behalf of your organization. Our team will review your application and follow up within 2 business days.
Meanwhile, if you have any questions, please reach out to us at membership.coordination@eclipse-foundation.org.
Best regards,
Eclipse Foundation Membership Coordination
\ No newline at end of file
<p>Dear {name},</p>
<p>Thank you for submitting the member enrollment form on behalf of your organization. Our team will review your application and follow up within 2 business days.</p>
<p>Meanwhile, if you have any questions, please reach out to us at membership.coordination@eclipse-foundation.org.</p>
<p>Best regards,</p>
<p>Eclipse Foundation Membership Coordination</p>
Eclipse Foundation AISBL Membership Application date submitted: {now}
{#if includePreamble}
Dear {name},
Thank you for submitting the member enrollment form on behalf of your organization. Our team will review your application and follow up within 2 business days.
Meanwhile, if you have any questions, please reach out to us at membership.coordination@eclipse-foundation.org.
Best regards,
Eclipse Foundation Membership Coordination
{/if}
New form submission by: {name} ({form.userID})
Organization
Name: {org.legalName}
Twitter: {org.twitterHandle}
Requires purchase order: {form.purchaseOrderRequired}
VAT number: {form.vatNumber}
VAT Registration country: {form.registrationCountry}
Aggregate Revenue: {org.aggregateRevenue}
Employee count: {org.employeeCount}
Address
{org.address.street}
{org.address.city}, {org.address.provinceState}
......@@ -32,4 +46,4 @@ Participation Level: {wg.participationLevel}
Effective Date: {wg.effectiveDate}
Contact Name: {wg.contact.fName} {wg.contact.lName}
{/for}
\ No newline at end of file
{/for}
<div style="background: #efefef; margin-left: -10px; margin-right: -10px;">
<div style="background: #FFF; min-width: 700px; width: 66%; margin: auto; margin-top: -20px; margin-bottom: -20px;">
<div style="padding: 20px">
<h1>New form submission by: {name} ({form.userID})</h1>
<h1>Eclipse Foundation AISBL Membership Application date submitted: {now}</h1>
{#if includePreamble}
<p>Dear {name},</p>
<p>Thank you for submitting the member enrollment form on behalf of your organization. Our team will review your application and follow up within 2 business days.</p>
<p>Meanwhile, if you have any questions, please reach out to us at membership.coordination@eclipse-foundation.org.</p>
<p>Best regards,</p>
<p>Eclipse Foundation Membership Coordination</p>
{/if}
<p>New form submission by: {name} ({data.form.userID})</p>
<h2>Organization</h2>
<p style="margin-bottom: 0px">Name: {org.legalName}</p>
<p style="margin-bottom: 0px; margin-top: 5px">Aggregate Revenue: {org.aggregateRevenue}</p>
<p style="margin-bottom: 0px; margin-top: 5px">Employee count: {org.employeeCount}</p>
<p style="margin-top: 5px">Twitter: {org.twitterHandle}</p>
<p style="margin-bottom: 0px">Name: {data.org.legalName}</p>
<p style="margin-bottom: 0px; margin-top: 5px">Aggregate Revenue: {data.org.aggregateRevenue}</p>
<p style="margin-bottom: 0px; margin-top: 5px">Employee count: {data.org.employeeCount}</p>
<p style="margin-bottom: 0px; margin-top: 5px">Twitter: {data.org.twitterHandle}</p>
<p style="margin-bottom: 0px; margin-top: 5px"></p>Requires purchase order: {data.form.purchaseOrderRequired}</p>
<p style="margin-bottom: 0px; margin-top: 5px"></p>VAT number: {data.form.vatNumber}</p>
<p style="margin-top: 5px">VAT Registration country: {data.form.registrationCountry}</p>
<div>
<h3>Address</h3>
<p>
{org.address.street}<br /> {org.address.city}<br /> {org.address.provinceState}<br /> {org.address.country}<br /> {org.address.postalCode}
{data.org.address.street}<br /> {data.org.address.city}<br /> {data.org.address.provinceState}<br /> {data.org.address.country}<br /> {data.org.address.postalCode}
</p>
</div>
......@@ -25,7 +36,7 @@
</tr>
</thead>
<tbody>
{#for contact in contacts.orEmpty}
{#for contact in data.contacts.orEmpty}
<tr {#if even} style="background: #efefef"{/if}>
<td>{contact.fName} {contact.lName}</td>
<td>{contact.email}</td>
......@@ -48,7 +59,7 @@
</tr>
</thead>
<tbody>
{#for wg in wgs.orEmpty}
{#for wg in data.wgs.orEmpty}
<tr {#if even} style="background: #efefef"{/if}>
<td>{wg.workingGroupID}</td>
<td>{wg.participationLevel}</td>
......@@ -60,4 +71,4 @@
</table>
</div>
</div>
</div>
\ No newline at end of file
</div>
......@@ -11,6 +11,7 @@ import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.model.MailerData;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.junit.jupiter.api.Assertions;
......@@ -22,7 +23,6 @@ import io.quarkus.mailer.MockMailbox;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;
import io.quarkus.test.security.TestSecurity;
@QuarkusTest
@QuarkusTestResource(OidcWiremockTestResource.class)
......@@ -51,9 +51,14 @@ class DefaultMailerServiceTest {
void sendToFormAuthor_success() {
// set up form to submit through mock service
MembershipForm f = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
FormOrganization org = DtoHelper.generateOrg(f);
List<FormWorkingGroup> wgs = DtoHelper.generateWorkingGroups(f);
List<Contact> contacts = DtoHelper.generateContacts(f);
MailerData data = new MailerData(f,org,wgs,contacts);
// perform the action
mailerService.sendToFormAuthor(f);
mailerService.sendToFormAuthor(data);
// verify that it was sent
List<Mail> sent = mailbox.getMessagesSentTo(TO_ADDRESS);
......@@ -71,10 +76,15 @@ class DefaultMailerServiceTest {
void sendToFormAuthor_anon() {
// set up form to submit through mock service
MembershipForm f = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
FormOrganization org = DtoHelper.generateOrg(f);
List<FormWorkingGroup> wgs = DtoHelper.generateWorkingGroups(f);
List<Contact> contacts = DtoHelper.generateContacts(f);
MailerData data = new MailerData(f,org,wgs,contacts);
// verify that it failed to send due to state exception
Assertions.assertThrows(IllegalStateException.class, () -> {
mailerService.sendToFormAuthor(f);
mailerService.sendToFormAuthor(data);
});
// verify that no messages were sent
Assertions.assertEquals(0, mailbox.getTotalMessagesSent());
......@@ -101,8 +111,9 @@ class DefaultMailerServiceTest {
List<FormWorkingGroup> wgs = DtoHelper.generateWorkingGroups(f);
List<Contact> contacts = DtoHelper.generateContacts(f);
MailerData data = new MailerData(f,org,wgs,contacts);
// perform the action
mailerService.sendToMembershipTeam(f, org, wgs, contacts);
mailerService.sendToMembershipTeam(data);
// verify that it was sent
List<Mail> sent = mailbox.getMessagesSentTo(membershipMailbox);
......@@ -126,19 +137,16 @@ class DefaultMailerServiceTest {
// perform the action
Assertions.assertThrows(IllegalStateException.class, () -> {
mailerService.sendToMembershipTeam(null, org, wgs, contacts);
});
Assertions.assertThrows(IllegalStateException.class, () -> {
mailerService.sendToMembershipTeam(f, null, wgs, contacts);
mailerService.sendToMembershipTeam(new MailerData(null, org, wgs, contacts));
});
Assertions.assertThrows(IllegalStateException.class, () -> {
mailerService.sendToMembershipTeam(f, org, null, contacts);
mailerService.sendToMembershipTeam(new MailerData(f, null, wgs, contacts));
});
Assertions.assertThrows(IllegalStateException.class, () -> {
mailerService.sendToMembershipTeam(f, org, wgs, null);
mailerService.sendToMembershipTeam(new MailerData(f, org, wgs, null));
});
Assertions.assertThrows(IllegalStateException.class, () -> {
mailerService.sendToMembershipTeam(f, org, wgs, Collections.emptyList());
mailerService.sendToMembershipTeam(new MailerData(f, org, wgs, Collections.emptyList()));
});
// verify that no messages were sent
List<Mail> sent = mailbox.getMessagesSentTo(membershipMailbox);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment