Skip to content

Commit c07f3a8

Browse files
authored
Merge pull request #9186 from QualitativeDataRepository/IQSS/9185-contact_email_updates
IQSS/9185 contact email updates
2 parents a82cd90 + cd2f196 commit c07f3a8

14 files changed

Lines changed: 332 additions & 196 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Contact Email Improvements
2+
3+
Email sent from the contact forms to the contact(s) for a collection, dataset, or datafile can now optionally be cc'd to a support email address. The support email address can be changed from the default :SystemEmail address to a separate :SupportEmail address. When multiple contacts are listed, the system will now send one email to all contacts (with the optional cc if configured) instead of separate emails to each contact. Contact names with a comma that refer to Organizations will no longer have the name parts reversed in the email greeting. A new protected feedback API has been added.
4+
5+
## Backward Incompatibilities
6+
7+
When there are multiple contacts, the system will now send one email with all of the contacts in the To: header instead of sending one email to each contact (with no indication that others have been notified).
8+
9+
## New JVM/MicroProfile Settings
10+
11+
dataverse.mail.support-email - allows a separate email, distinct from the :SystemEmail to be used as the to address in emails from the contact form/ feedback api.
12+
dataverse.mail.cc-support-on-contact-emails - include the support email address as a CC: entry when contact/feedback emails are sent to the contacts for a collection, dataset, or datafile.

doc/sphinx-guides/source/api/native-api.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,8 @@ The fully expanded example above (without environment variables) looks like this
16181618
16191619
The people who need to review the dataset (often curators or journal editors) can check their notifications periodically via API to see if any new datasets have been submitted for review and need their attention. See the :ref:`Notifications` section for details. Alternatively, these curators can simply check their email or notifications to know when datasets have been submitted (or resubmitted) for review.
16201620

1621+
.. _return-a-dataset:
1622+
16211623
Return a Dataset to Author
16221624
~~~~~~~~~~~~~~~~~~~~~~~~~~
16231625

@@ -1645,6 +1647,8 @@ The fully expanded example above (without environment variables) looks like this
16451647
16461648
The review process can sometimes resemble a tennis match, with the authors submitting and resubmitting the dataset over and over until the curators are satisfied. Each time the curators send a "reason for return" via API, that reason is persisted into the database, stored at the dataset version level.
16471649

1650+
The :ref:`send-feedback` API call may be useful as a way to move the conversation to email. However, note that these emails go to contacts (versus authors) and there is no database record of the email contents. (:ref:`dataverse.mail.cc-support-on-contact-email` will send a copy of these emails to the support email address which would provide a record.)
1651+
16481652
Link a Dataset
16491653
~~~~~~~~~~~~~~
16501654

@@ -4497,3 +4501,29 @@ A curl example using allowing access to a dataset's metadata
44974501
44984502
Please see :ref:`dataverse.api.signature-secret` for the configuration option to add a shared secret, enabling extra
44994503
security.
4504+
4505+
.. _send-feedback:
4506+
4507+
Send Feedback To Contact(s)
4508+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
4509+
4510+
This API call allows sending an email to the contacts for a collection, dataset, or datafile or to the support email address when no object is specified.
4511+
The call is protected by the normal /admin API protections (limited to localhost or requiring a separate key), but does not otherwise limit the sending of emails.
4512+
Administrators should be sure only trusted applications have access to avoid the potential for spam.
4513+
4514+
The call is a POST with a JSON object as input with four keys:
4515+
- "targetId" - the id of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional)
4516+
- "subject" - the email subject line
4517+
- "body" - the email body to send
4518+
- "fromEmail" - the email to list in the reply-to field. (Dataverse always sends mail from the system email, but does it "on behalf of" and with a reply-to for the specified user.)
4519+
4520+
A curl example using an ``ID``
4521+
4522+
.. code-block:: bash
4523+
4524+
export SERVER_URL=http://localhost
4525+
export JSON='{"targetId":24, "subject":"Data Question", "body":"Please help me understand your data. Thank you!", "fromEmail":"dataverseSupport@mailinator.com"}'
4526+
4527+
curl -X POST -H 'Content-Type:application/json' -d "$JSON" $SERVER_URL/api/admin/feedback
4528+
4529+
Note that this call could be useful in coordinating with dataset authors (assuming they are also contacts) as an alternative/addition to the functionality provided by :ref:`return-a-dataset`.

doc/sphinx-guides/source/installation/config.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2349,6 +2349,27 @@ See :ref:`discovery-sign-posting` for details.
23492349

23502350
Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_SIGNPOSTING_LEVEL1_ITEM_LIMIT``.
23512351

2352+
dataverse.mail.support-email
2353+
++++++++++++++++++++++++++++
2354+
2355+
This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address for Contact Forms and Feedback API. This address is used as the To address when the Contact form is launched from the Support entry in the top navigation bar and, if configured via :ref:`dataverse.mail.cc-support-on-contact-email`, as a CC address when the form is launched from a Dataverse/Dataset Contact button.
2356+
This allows configuration of a no-reply email address for :ref:`systemEmail` while allowing feedback to go to/be cc'd to the support email address, which would normally accept replies. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email.
2357+
2358+
Note that only the email address is required, which you can supply without the ``<`` and ``>`` signs, but if you include the text, it's the way to customize the name of your support team, which appears in the "from" address in emails as well as in help text in the UI. If you don't include the text, the installation name (see :ref:`Branding Your Installation`) will appear in the "from" address.
2359+
2360+
Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_MAIL_SUPPORT_EMAIL``.
2361+
2362+
.. _dataverse.mail.cc-support-on-contact-email:
2363+
2364+
dataverse.mail.cc-support-on-contact-email
2365+
++++++++++++++++++++++++++++++++++++++++++
2366+
2367+
If this setting is true, the contact forms and feedback API will cc the system (:SupportEmail if set, :SystemEmail if not) when sending email to the collection, dataset, or datafile contacts.
2368+
A CC line is added to the contact form when this setting is true so that users are aware that the cc will occur.
2369+
The default is false.
2370+
2371+
Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_MAIL_CC_SUPPORT_ON_CONTACT_EMAIL``.
2372+
23522373

23532374
.. _feature-flags:
23542375

@@ -3831,3 +3852,4 @@ A true(default)/false option determining whether datafiles listed on the dataset
38313852
A true/false (default) option determining whether the dataset datafile table display includes checkboxes enabling users to turn folder ordering and/or category ordering (if an order is defined by :CategoryOrder) on and off dynamically.
38323853

38333854
.. _supported MicroProfile Config API source: https://docs.payara.fish/community/docs/Technical%20Documentation/MicroProfile/Config/Overview.html
3855+

src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -81,37 +81,6 @@ public class MailServiceBean implements java.io.Serializable {
8181
public MailServiceBean() {
8282
}
8383

84-
public void sendMail(String host, String reply, String to, String subject, String messageText) {
85-
Properties props = System.getProperties();
86-
props.put("mail.smtp.host", host);
87-
Session session = Session.getDefaultInstance(props, null);
88-
89-
try {
90-
MimeMessage msg = new MimeMessage(session);
91-
String[] recipientStrings = to.split(",");
92-
InternetAddress[] recipients = new InternetAddress[recipientStrings.length];
93-
try {
94-
InternetAddress fromAddress = getSystemAddress();
95-
setContactDelegation(reply, fromAddress);
96-
msg.setFrom(fromAddress);
97-
msg.setReplyTo(new Address[] {new InternetAddress(reply, charset)});
98-
for (int i = 0; i < recipients.length; i++) {
99-
recipients[i] = new InternetAddress(recipientStrings[i], "", charset);
100-
}
101-
} catch (UnsupportedEncodingException ex) {
102-
logger.severe(ex.getMessage());
103-
}
104-
msg.setRecipients(Message.RecipientType.TO, recipients);
105-
msg.setSubject(subject, charset);
106-
msg.setText(messageText, charset);
107-
Transport.send(msg, recipients);
108-
} catch (AddressException ae) {
109-
ae.printStackTrace(System.out);
110-
} catch (MessagingException me) {
111-
me.printStackTrace(System.out);
112-
}
113-
}
114-
11584
@Resource(name = "mail/notifyMailSession")
11685
private Session session;
11786

@@ -177,11 +146,7 @@ public InternetAddress getSystemAddress() {
177146
}
178147

179148
//@Resource(name="mail/notifyMailSession")
180-
public void sendMail(String from, String to, String subject, String messageText) {
181-
sendMail(from, to, subject, messageText, new HashMap<>());
182-
}
183-
184-
public void sendMail(String reply, String to, String subject, String messageText, Map<Object, Object> extraHeaders) {
149+
public void sendMail(String reply, String to, String cc, String subject, String messageText) {
185150
try {
186151
MimeMessage msg = new MimeMessage(session);
187152
// Always send from system address to avoid email being blocked
@@ -202,18 +167,12 @@ public void sendMail(String reply, String to, String subject, String messageText
202167
msg.setSentDate(new Date());
203168
msg.setRecipients(Message.RecipientType.TO,
204169
InternetAddress.parse(to, false));
170+
if (cc != null) {
171+
msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false));
172+
}
205173
msg.setSubject(subject, charset);
206174
msg.setText(messageText, charset);
207175

208-
if (extraHeaders != null) {
209-
for (Object key : extraHeaders.keySet()) {
210-
String headerName = key.toString();
211-
String headerValue = extraHeaders.get(key).toString();
212-
213-
msg.addHeader(headerName, headerValue);
214-
}
215-
}
216-
217176
Transport.send(msg);
218177
} catch (AddressException ae) {
219178
ae.printStackTrace(System.out);

src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import edu.harvard.iq.dataverse.branding.BrandingUtil;
44
import edu.harvard.iq.dataverse.feedback.Feedback;
55
import edu.harvard.iq.dataverse.feedback.FeedbackUtil;
6+
import edu.harvard.iq.dataverse.settings.JvmSettings;
67
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
78
import edu.harvard.iq.dataverse.util.BundleUtil;
89
import edu.harvard.iq.dataverse.util.MailUtil;
910
import edu.harvard.iq.dataverse.util.SystemConfig;
10-
import java.util.List;
11+
import java.util.Optional;
1112
import java.util.Random;
1213
import java.util.logging.Logger;
1314
import javax.ejb.EJB;
@@ -62,7 +63,7 @@ public class SendFeedbackDialog implements java.io.Serializable {
6263
* Either the dataverse or the dataset that the message is pertaining to. If
6364
* there is no recipient, this is a general feedback message.
6465
*/
65-
private DvObject recipient;
66+
private DvObject feedbackTarget;
6667

6768
/**
6869
* :SystemEmail (the main support address for an installation).
@@ -97,11 +98,11 @@ public void initUserInput(ActionEvent ae) {
9798
userMessage = "";
9899
messageSubject = "";
99100
Random random = new Random();
100-
op1 = new Long(random.nextInt(10));
101-
op2 = new Long(random.nextInt(10));
101+
op1 = Long.valueOf(random.nextInt(10));
102+
op2 = Long.valueOf(random.nextInt(10));
102103
userSum = null;
103-
String systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail);
104-
systemAddress = MailUtil.parseSystemAddress(systemEmail);
104+
String supportEmail = JvmSettings.SUPPORT_EMAIL.lookupOptional().orElse(settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail));
105+
systemAddress = MailUtil.parseSystemAddress(supportEmail);
105106
}
106107

107108
public Long getOp1() {
@@ -129,19 +130,27 @@ public void setUserSum(Long userSum) {
129130
}
130131

131132
public String getMessageTo() {
132-
if (recipient == null) {
133+
if (feedbackTarget == null) {
133134
return BrandingUtil.getSupportTeamName(systemAddress);
134-
} else if (recipient.isInstanceofDataverse()) {
135-
return ((Dataverse) recipient).getDisplayName() + " " + BundleUtil.getStringFromBundle("contact.contact");
135+
} else if (feedbackTarget.isInstanceofDataverse()) {
136+
return ((Dataverse) feedbackTarget).getDisplayName() + " " + BundleUtil.getStringFromBundle("contact.contact");
136137
} else {
137138
return BundleUtil.getStringFromBundle("dataset") + " " + BundleUtil.getStringFromBundle("contact.contact");
138139
}
139140
}
141+
142+
public String getMessageCC() {
143+
if (ccSupport()) {
144+
return BrandingUtil.getSupportTeamName(systemAddress);
145+
}
146+
return null;
147+
}
148+
140149

141150
public String getFormHeader() {
142-
if (recipient == null) {
151+
if (feedbackTarget == null) {
143152
return BrandingUtil.getContactHeader(systemAddress);
144-
} else if (recipient.isInstanceofDataverse()) {
153+
} else if (feedbackTarget.isInstanceofDataverse()) {
145154
return BundleUtil.getStringFromBundle("contact.dataverse.header");
146155
} else {
147156
return BundleUtil.getStringFromBundle("contact.dataset.header");
@@ -173,11 +182,11 @@ public String loggedInUserEmail() {
173182
}
174183

175184
public DvObject getRecipient() {
176-
return recipient;
185+
return feedbackTarget;
177186
}
178187

179188
public void setRecipient(DvObject recipient) {
180-
this.recipient = recipient;
189+
this.feedbackTarget = recipient;
181190
}
182191

183192
public void validateUserSum(FacesContext context, UIComponent component, Object value) throws ValidatorException {
@@ -200,16 +209,26 @@ public void validateUserEmail(FacesContext context, UIComponent component, Objec
200209
public String sendMessage() {
201210
String installationBrandName = BrandingUtil.getInstallationBrandName();
202211
String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress);
203-
List<Feedback> feedbacks = FeedbackUtil.gatherFeedback(recipient, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName);
204-
if (feedbacks.isEmpty()) {
212+
213+
Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName, ccSupport());
214+
if (feedback==null) {
205215
logger.warning("No feedback has been sent!");
206216
return null;
207217
}
208-
for (Feedback feedback : feedbacks) {
209218
logger.fine("sending feedback: " + feedback);
210-
mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getSubject(), feedback.getBody());
211-
}
219+
mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody());
212220
return null;
213221
}
222+
223+
public boolean ccSupport() {
224+
return ccSupport(feedbackTarget);
225+
}
226+
227+
public static boolean ccSupport(DvObject feedbackTarget) {
228+
//Setting is enabled and this isn't already a direct message to support (no feedbackTarget)
229+
Optional<Boolean> ccSupport = JvmSettings.CC_SUPPORT_ON_CONTACT_EMAIL.lookupOptional(Boolean.class);
230+
231+
return feedbackTarget!=null && ccSupport.isPresent() &&ccSupport.get();
232+
}
214233

215234
}

src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
import edu.harvard.iq.dataverse.DataverseSession;
44
import edu.harvard.iq.dataverse.DvObject;
5-
import edu.harvard.iq.dataverse.DvObjectServiceBean;
5+
import edu.harvard.iq.dataverse.MailServiceBean;
6+
import edu.harvard.iq.dataverse.SendFeedbackDialog;
67
import edu.harvard.iq.dataverse.branding.BrandingUtil;
78
import edu.harvard.iq.dataverse.feedback.Feedback;
89
import edu.harvard.iq.dataverse.feedback.FeedbackUtil;
9-
import java.util.List;
10+
import edu.harvard.iq.dataverse.settings.JvmSettings;
11+
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
12+
import edu.harvard.iq.dataverse.util.MailUtil;
13+
1014
import javax.ejb.EJB;
1115
import javax.json.Json;
1216
import javax.json.JsonArrayBuilder;
@@ -17,34 +21,47 @@
1721
import javax.ws.rs.POST;
1822
import javax.ws.rs.Path;
1923
import javax.ws.rs.core.Response;
24+
import javax.ws.rs.core.Response.Status;
2025

2126
@Path("admin/feedback")
2227
public class FeedbackApi extends AbstractApiBean {
2328

24-
@EJB
25-
DvObjectServiceBean dvObjectSvc;
26-
29+
@EJB MailServiceBean mailService;
30+
31+
/**
32+
* This method mimics the contact form and sends an email to the contacts of the
33+
* specified Collection/Dataset/DataFile, optionally ccing the support email
34+
* address, or to the support email address when there is no target object.
35+
*
36+
* !!!!! This should not be moved outside the /admin path unless/until some form
37+
* of captcha or other spam-prevention mechanism is added. As is, it allows an
38+
* unauthenticated user (with access to the /admin api path) to send email from
39+
* anyone to any contacts in Dataverse. (It also does not do much to validate
40+
* user input (e.g. to strip potentially malicious html, etc.)!!!!
41+
**/
2742
@POST
2843
public Response submitFeedback(JsonObject jsonObject) throws AddressException {
29-
JsonNumber jsonNumber = jsonObject.getJsonNumber("id");
30-
DvObject recipient = null;
44+
JsonNumber jsonNumber = jsonObject.getJsonNumber("targetId");
45+
DvObject feedbackTarget = null;
3146
if (jsonNumber != null) {
32-
recipient = dvObjectSvc.findDvObject(jsonNumber.longValue());
47+
feedbackTarget = dvObjSvc.findDvObject(jsonNumber.longValue());
48+
if(feedbackTarget==null) {
49+
return error(Status.BAD_REQUEST, "Feedback target object not found");
50+
}
3351
}
3452
DataverseSession dataverseSession = null;
3553
String userMessage = jsonObject.getString("body");
36-
String systemEmail = "support@librascholar.edu";
37-
InternetAddress systemAddress = new InternetAddress(systemEmail);
54+
String systemEmail = JvmSettings.SUPPORT_EMAIL.lookupOptional().orElse(settingsSvc.getValueForKey(SettingsServiceBean.Key.SystemEmail));
55+
InternetAddress systemAddress = MailUtil.parseSystemAddress(systemEmail);
3856
String userEmail = jsonObject.getString("fromEmail");
3957
String messageSubject = jsonObject.getString("subject");
4058
String baseUrl = systemConfig.getDataverseSiteUrl();
4159
String installationBrandName = BrandingUtil.getInstallationBrandName();
4260
String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress);
4361
JsonArrayBuilder jab = Json.createArrayBuilder();
44-
List<Feedback> feedbacks = FeedbackUtil.gatherFeedback(recipient, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName);
45-
feedbacks.forEach((feedback) -> {
46-
jab.add(feedback.toJsonObjectBuilder());
47-
});
62+
Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, SendFeedbackDialog.ccSupport(feedbackTarget));
63+
jab.add(feedback.toJsonObjectBuilder());
64+
mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody());
4865
return ok(jab);
4966
}
5067
}

0 commit comments

Comments
 (0)