From b7c8c69fb6cbf9722111c7c08f017ce9360ae244 Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 28 Oct 2022 13:27:43 -0400 Subject: [PATCH 01/35] fixes --- .../export/openaire/OpenAireExportUtil.java | 143 ++++-------------- 1 file changed, 30 insertions(+), 113 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java b/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java index bea3858a60e..e5855b18750 100644 --- a/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java @@ -24,6 +24,7 @@ import edu.harvard.iq.dataverse.api.dto.DatasetVersionDTO; import edu.harvard.iq.dataverse.api.dto.FieldDTO; import edu.harvard.iq.dataverse.api.dto.MetadataBlockDTO; +import edu.harvard.iq.dataverse.util.PersonOrOrgUtil; import edu.harvard.iq.dataverse.util.json.JsonUtil; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -249,72 +250,26 @@ public static void writeCreatorsElement(XMLStreamWriter xmlw, DatasetVersionDTO if (StringUtils.isNotBlank(creatorName)) { creator_check = writeOpenTag(xmlw, "creators", creator_check); xmlw.writeStartElement("creator"); // - - boolean nameType_check = false; + Map creator_map = new HashMap(); - if ((StringUtils.containsIgnoreCase(nameIdentifierScheme, "orcid"))) { + JsonObject creatorObj = PersonOrOrgUtil.getPersonOrOrganization(creatorName, false, + StringUtils.containsIgnoreCase(nameIdentifierScheme, "orcid")); + + // creatorName=, + if (creatorObj.getBoolean("isPerson")) { creator_map.put("nameType", "Personal"); - nameType_check = true; - } - // ToDo - the algorithm to determine if this is a Person or Organization here - // has been abstracted into a separate - // edu.harvard.iq.dataverse.util.PersonOrOrgUtil class that could be used here - // to avoid duplication/variants of the algorithm - creatorName = Cleanup.normalize(creatorName); - // Datacite algorithm, https://github.com/IQSS/dataverse/issues/2243#issuecomment-358615313 - if (creatorName.contains(",")) { - String givenName = FirstNames.getInstance().getFirstName(creatorName); - boolean isOrganization = Organizations.getInstance().isOrganization(creatorName); - - // creatorName=, - if (givenName != null && !isOrganization) { - // givenName ok - creator_map.put("nameType", "Personal"); - nameType_check = true; - } else if (isOrganization) { - creator_map.put("nameType", "Organizational"); - nameType_check = false; - } - writeFullElement(xmlw, null, "creatorName", creator_map, creatorName, language); - - if ((nameType_check) && (!creatorName.replaceFirst(",", "").contains(","))) { - // creatorName=, - String[] fullName = creatorName.split(", "); - if (fullName.length == 2) { - givenName = fullName[1]; - String familyName = fullName[0]; - - writeFullElement(xmlw, null, "givenName", null, givenName, language); - writeFullElement(xmlw, null, "familyName", null, familyName, language); - } else { - // It's possible to get here if "Smith," is entered as an author name. - logger.info("Unable to write givenName and familyName based on creatorName '" + creatorName + "'."); - } - } } else { - String givenName = FirstNames.getInstance().getFirstName(creatorName); - boolean isOrganization = Organizations.getInstance().isOrganization(creatorName); - - if (givenName != null && !isOrganization) { - // givenName ok, creatorName= - creator_map.put("nameType", "Personal"); - nameType_check = true; - writeFullElement(xmlw, null, "creatorName", creator_map, creatorName, language); - - String familyName = ""; - if (givenName.length() + 1 < creatorName.length()) { - familyName = creatorName.substring(givenName.length() + 1); - } - - writeFullElement(xmlw, null, "givenName", null, givenName, language); - writeFullElement(xmlw, null, "familyName", null, familyName, language); - } else { - // default - if (isOrganization) { - creator_map.put("nameType", "Organizational"); - } - writeFullElement(xmlw, null, "creatorName", creator_map, creatorName, language); - } + creator_map.put("nameType", "Organizational"); + } + writeFullElement(xmlw, null, "creatorName", creator_map, + creatorObj.getString("fullName"), language); + if (creatorObj.containsKey("givenName")) { + writeFullElement(xmlw, null, "givenName", null, creatorObj.getString("givenName"), + language); + } + if (creatorObj.containsKey("familyName")) { + writeFullElement(xmlw, null, "familyName", null, creatorObj.getString("familyName"), + language); } if (StringUtils.isNotBlank(nameIdentifier)) { @@ -709,61 +664,23 @@ public static void writeContributorElement(XMLStreamWriter xmlw, String contribu boolean nameType_check = false; Map contributor_map = new HashMap(); - // ToDo - the algorithm to determine if this is a Person or Organization here - // has been abstracted into a separate - // edu.harvard.iq.dataverse.util.PersonOrOrgUtil class that could be used here - // to avoid duplication/variants of the algorithm + JsonObject contributorObj = PersonOrOrgUtil.getPersonOrOrganization(contributorName, + ("ContactPerson".equals(contributorType) && !isValidEmailAddress(contributorName)), false); - contributorName = Cleanup.normalize(contributorName); - // Datacite algorithm, https://github.com/IQSS/dataverse/issues/2243#issuecomment-358615313 - if (contributorName.contains(",")) { - String givenName = FirstNames.getInstance().getFirstName(contributorName); - boolean isOrganization = Organizations.getInstance().isOrganization(contributorName); - - // contributorName=, - if (givenName != null && !isOrganization) { - // givenName ok + if (contributorObj.getBoolean("isPerson")) { + if(contributorObj.containsKey("givenName")) { contributor_map.put("nameType", "Personal"); - nameType_check = true; - // re: the above toDo - the ("ContactPerson".equals(contributorType) && - // !isValidEmailAddress(contributorName)) clause in the next line could/should - // be sent as the OrgIfTied boolean parameter - } else if (isOrganization || ("ContactPerson".equals(contributorType) && !isValidEmailAddress(contributorName))) { - contributor_map.put("nameType", "Organizational"); - } - writeFullElement(xmlw, null, "contributorName", contributor_map, contributorName, language); - - if ((nameType_check) && (!contributorName.replaceFirst(",", "").contains(","))) { - // contributorName=, - String[] fullName = contributorName.split(", "); - givenName = fullName[1]; - String familyName = fullName[0]; - - writeFullElement(xmlw, null, "givenName", null, givenName, language); - writeFullElement(xmlw, null, "familyName", null, familyName, language); } } else { - String givenName = FirstNames.getInstance().getFirstName(contributorName); - boolean isOrganization = Organizations.getInstance().isOrganization(contributorName); - - if (givenName != null && !isOrganization) { - contributor_map.put("nameType", "Personal"); - writeFullElement(xmlw, null, "contributorName", contributor_map, contributorName, language); - - String familyName = ""; - if (givenName.length() + 1 < contributorName.length()) { - familyName = contributorName.substring(givenName.length() + 1); - } + contributor_map.put("nameType", "Organizational"); + } + writeFullElement(xmlw, null, "contributorName", contributor_map, contributorName, language); - writeFullElement(xmlw, null, "givenName", null, givenName, language); - writeFullElement(xmlw, null, "familyName", null, familyName, language); - } else { - // default - if (isOrganization || ("ContactPerson".equals(contributorType) && !isValidEmailAddress(contributorName))) { - contributor_map.put("nameType", "Organizational"); - } - writeFullElement(xmlw, null, "contributorName", contributor_map, contributorName, language); - } + if (contributorObj.containsKey("givenName")) { + writeFullElement(xmlw, null, "givenName", null, contributorObj.getString("givenName"), language); + } + if (contributorObj.containsKey("familyName")) { + writeFullElement(xmlw, null, "familyName", null, contributorObj.getString("familyName"), language); } if (StringUtils.isNotBlank(contributorAffiliation)) { From 93f09e20413be11edabf76a15651fdc9435ad5bd Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 28 Oct 2022 14:01:25 -0400 Subject: [PATCH 02/35] Refactor - move classes to Util --- .../iq/dataverse/export/openaire/Cleanup.java | 28 ------------------ .../{export/openaire => util}/FirstNames.java | 6 ++-- .../openaire => util}/Organizations.java | 7 ++--- .../iq/dataverse/util/PersonOrOrgUtil.java | 5 +--- .../harvard/iq/dataverse/util/StringUtil.java | 21 ++++++++++++++ .../iq/dataverse/export/CleanupTest.java | 29 ------------------- .../{export => util}/FirstNameTest.java | 6 ++-- .../{export => util}/OrganizationsTest.java | 6 ++-- .../dataverse/util/PersonOrOrgUtilTest.java | 1 - .../iq/dataverse/util/StringUtilTest.java | 18 ++++++++++++ 10 files changed, 54 insertions(+), 73 deletions(-) delete mode 100644 src/main/java/edu/harvard/iq/dataverse/export/openaire/Cleanup.java rename src/main/java/edu/harvard/iq/dataverse/{export/openaire => util}/FirstNames.java (98%) rename src/main/java/edu/harvard/iq/dataverse/{export/openaire => util}/Organizations.java (97%) delete mode 100644 src/test/java/edu/harvard/iq/dataverse/export/CleanupTest.java rename src/test/java/edu/harvard/iq/dataverse/{export => util}/FirstNameTest.java (94%) rename src/test/java/edu/harvard/iq/dataverse/{export => util}/OrganizationsTest.java (96%) diff --git a/src/main/java/edu/harvard/iq/dataverse/export/openaire/Cleanup.java b/src/main/java/edu/harvard/iq/dataverse/export/openaire/Cleanup.java deleted file mode 100644 index 508f441bc03..00000000000 --- a/src/main/java/edu/harvard/iq/dataverse/export/openaire/Cleanup.java +++ /dev/null @@ -1,28 +0,0 @@ -package edu.harvard.iq.dataverse.export.openaire; - -import org.apache.commons.lang3.StringUtils; - -/** - * - * @author francesco.cadili@4science.it - */ -public class Cleanup { - - /** - * Normalize sentence - * - * @param sentence full name or organization name - * @return normalize string value - */ - static public String normalize(String sentence) { - if (StringUtils.isBlank(sentence)) { - return ""; - } - - sentence = sentence.trim() - .replaceAll(", *", ", ") - .replaceAll(" +", " "); - - return sentence; - } -} diff --git a/src/main/java/edu/harvard/iq/dataverse/export/openaire/FirstNames.java b/src/main/java/edu/harvard/iq/dataverse/util/FirstNames.java similarity index 98% rename from src/main/java/edu/harvard/iq/dataverse/export/openaire/FirstNames.java rename to src/main/java/edu/harvard/iq/dataverse/util/FirstNames.java index 6a7bfe400f0..d82aa5e59b2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/export/openaire/FirstNames.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/FirstNames.java @@ -1,4 +1,4 @@ -package edu.harvard.iq.dataverse.export.openaire; +package edu.harvard.iq.dataverse.util; import java.io.BufferedReader; import java.io.IOException; @@ -9,10 +9,10 @@ import java.util.logging.Level; /** - * + * Used by PersonOrOrgUtil * @author francesco.cadili@4science.it */ -public class FirstNames { +class FirstNames { private static FirstNames instance = null; diff --git a/src/main/java/edu/harvard/iq/dataverse/export/openaire/Organizations.java b/src/main/java/edu/harvard/iq/dataverse/util/Organizations.java similarity index 97% rename from src/main/java/edu/harvard/iq/dataverse/export/openaire/Organizations.java rename to src/main/java/edu/harvard/iq/dataverse/util/Organizations.java index d08ea723176..475afdb48b8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/export/openaire/Organizations.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/Organizations.java @@ -1,6 +1,5 @@ -package edu.harvard.iq.dataverse.export.openaire; +package edu.harvard.iq.dataverse.util; -import edu.harvard.iq.dataverse.util.StringUtil; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -15,10 +14,10 @@ import opennlp.tools.util.Span; /** - * + * Used by PersonOrOrgUtil * @author francesco.cadili@4science.it */ -public class Organizations { +class Organizations { private static Organizations instance = null; diff --git a/src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java b/src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java index 3a8088aac77..27ecd3d136e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java @@ -9,9 +9,6 @@ import javax.json.JsonObjectBuilder; import javax.json.JsonString; -import edu.harvard.iq.dataverse.export.openaire.Cleanup; -import edu.harvard.iq.dataverse.export.openaire.FirstNames; -import edu.harvard.iq.dataverse.export.openaire.Organizations; import edu.harvard.iq.dataverse.util.json.JsonUtil; import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; @@ -69,7 +66,7 @@ public class PersonOrOrgUtil { * @return */ public static JsonObject getPersonOrOrganization(String name, boolean organizationIfTied, boolean isPerson) { - name = Cleanup.normalize(name); + name = StringUtil.normalize(name); String givenName = null; String familyName = null; diff --git a/src/main/java/edu/harvard/iq/dataverse/util/StringUtil.java b/src/main/java/edu/harvard/iq/dataverse/util/StringUtil.java index 750358b12d7..33c87563104 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/StringUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/StringUtil.java @@ -20,6 +20,8 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; /** @@ -198,4 +200,23 @@ private static SecretKeySpec generateKeyFromString(final String secKey) throws U SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); return secretKeySpec; } + + /** + * Normalize sentence + * + * @author francesco.cadili@4science.it + * + * + * @param sentence full name or organization name + * @return normalize string value + */ + static public String normalize(String sentence) { + if (StringUtils.isBlank(sentence)) { + return ""; + } + + sentence = sentence.trim().replaceAll(", *", ", ").replaceAll(" +", " "); + + return sentence; + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/export/CleanupTest.java b/src/test/java/edu/harvard/iq/dataverse/export/CleanupTest.java deleted file mode 100644 index 88dd3d12ffb..00000000000 --- a/src/test/java/edu/harvard/iq/dataverse/export/CleanupTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package edu.harvard.iq.dataverse.export; - -import edu.harvard.iq.dataverse.export.openaire.Cleanup; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - * @author francesco.cadili@4science.it - */ -public class CleanupTest { - - /** - * full name or organization name cleanup. - * - * Name is composed of: - * - */ - @Test - public void testNormalize() { - assertEquals(Cleanup.normalize(" Francesco "), "Francesco"); - assertEquals(Cleanup.normalize("Francesco Cadili "), "Francesco Cadili"); - assertEquals(Cleanup.normalize(" Cadili,Francesco"), "Cadili, Francesco"); - assertEquals(Cleanup.normalize("Cadili, Francesco "), "Cadili, Francesco"); - assertEquals(Cleanup.normalize(null), ""); - - // TODO: organization examples... - } -} diff --git a/src/test/java/edu/harvard/iq/dataverse/export/FirstNameTest.java b/src/test/java/edu/harvard/iq/dataverse/util/FirstNameTest.java similarity index 94% rename from src/test/java/edu/harvard/iq/dataverse/export/FirstNameTest.java rename to src/test/java/edu/harvard/iq/dataverse/util/FirstNameTest.java index 1b935b0406e..972a06ef99e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/export/FirstNameTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/FirstNameTest.java @@ -1,7 +1,9 @@ -package edu.harvard.iq.dataverse.export; +package edu.harvard.iq.dataverse.util; -import edu.harvard.iq.dataverse.export.openaire.FirstNames; import org.junit.Test; + +import edu.harvard.iq.dataverse.util.FirstNames; + import static org.junit.Assert.*; /** diff --git a/src/test/java/edu/harvard/iq/dataverse/export/OrganizationsTest.java b/src/test/java/edu/harvard/iq/dataverse/util/OrganizationsTest.java similarity index 96% rename from src/test/java/edu/harvard/iq/dataverse/export/OrganizationsTest.java rename to src/test/java/edu/harvard/iq/dataverse/util/OrganizationsTest.java index 2552e217cef..3b6cf4a7242 100644 --- a/src/test/java/edu/harvard/iq/dataverse/export/OrganizationsTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/OrganizationsTest.java @@ -1,7 +1,9 @@ -package edu.harvard.iq.dataverse.export; +package edu.harvard.iq.dataverse.util; -import edu.harvard.iq.dataverse.export.openaire.Organizations; import org.junit.Test; + +import edu.harvard.iq.dataverse.util.Organizations; + import static org.junit.Assert.*; /** diff --git a/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java index b22f18ca787..ee0d804a444 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java @@ -1,6 +1,5 @@ package edu.harvard.iq.dataverse.util; -import edu.harvard.iq.dataverse.export.openaire.Organizations; import edu.harvard.iq.dataverse.util.json.JsonUtil; import org.junit.Ignore; diff --git a/src/test/java/edu/harvard/iq/dataverse/util/StringUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/util/StringUtilTest.java index ff505d2ff09..aa2d20362cb 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/StringUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/StringUtilTest.java @@ -354,5 +354,23 @@ public void testNonEmpty_emptyString() { String expected = ""; assertFalse(StringUtil.nonEmpty(expected)); } + + /** + * full name or organization name cleanup. + * + * @author francesco.cadili@4science.it + * + * Name is composed of: + */ + @Test + public void testNormalize() { + assertEquals(StringUtil.normalize(" Francesco "), "Francesco"); + assertEquals(StringUtil.normalize("Francesco Cadili "), "Francesco Cadili"); + assertEquals(StringUtil.normalize(" Cadili,Francesco"), "Cadili, Francesco"); + assertEquals(StringUtil.normalize("Cadili, Francesco "), "Cadili, Francesco"); + assertEquals(StringUtil.normalize(null), ""); + + // TODO: organization examples... + } } } From 9d29441505858db2d38e4d9b890af91a218f6ffb Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 22 Nov 2022 13:29:33 -0500 Subject: [PATCH 03/35] initial cc in contact form email --- .../harvard/iq/dataverse/MailServiceBean.java | 13 +- .../iq/dataverse/SendFeedbackDialog.java | 43 ++-- .../harvard/iq/dataverse/api/FeedbackApi.java | 20 +- .../iq/dataverse/feedback/Feedback.java | 15 +- .../iq/dataverse/feedback/FeedbackUtil.java | 206 +++++++++++------- .../settings/SettingsServiceBean.java | 7 +- src/main/java/propertyFiles/Bundle.properties | 3 +- src/main/webapp/contactFormFragment.xhtml | 6 + .../dataverse/feedback/FeedbackUtilTest.java | 78 +++---- tests/data/datasetContacts1.json | 2 +- 10 files changed, 238 insertions(+), 155 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java index 2bfd342d899..87339f64508 100644 --- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java @@ -81,7 +81,7 @@ public class MailServiceBean implements java.io.Serializable { public MailServiceBean() { } - public void sendMail(String host, String reply, String to, String subject, String messageText) { +/* public void sendMail(String host, String reply, String to, String subject, String messageText) { Properties props = System.getProperties(); props.put("mail.smtp.host", host); Session session = Session.getDefaultInstance(props, null); @@ -111,7 +111,7 @@ public void sendMail(String host, String reply, String to, String subject, Strin me.printStackTrace(System.out); } } - +*/ @Resource(name = "mail/notifyMailSession") private Session session; @@ -177,11 +177,11 @@ public InternetAddress getSystemAddress() { } //@Resource(name="mail/notifyMailSession") - public void sendMail(String from, String to, String subject, String messageText) { - sendMail(from, to, subject, messageText, new HashMap<>()); +/* public void sendMail(String from, String to, String cc, String subject, String messageText) { + sendMail(from, to, cc, subject, messageText, new HashMap<>()); } - - public void sendMail(String reply, String to, String subject, String messageText, Map extraHeaders) { +*/ + public void sendMail(String reply, String to, String cc, String subject, String messageText, Map extraHeaders) { try { MimeMessage msg = new MimeMessage(session); // Always send from system address to avoid email being blocked @@ -202,6 +202,7 @@ public void sendMail(String reply, String to, String subject, String messageText msg.setSentDate(new Date()); msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false)); + msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false)); msg.setSubject(subject, charset); msg.setText(messageText, charset); diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index 363972b48c3..d5faadea20b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -62,7 +62,7 @@ public class SendFeedbackDialog implements java.io.Serializable { * Either the dataverse or the dataset that the message is pertaining to. If * there is no recipient, this is a general feedback message. */ - private DvObject recipient; + private DvObject feedbackTarget; /** * :SystemEmail (the main support address for an installation). @@ -92,13 +92,14 @@ public String getUserEmail() { return userEmail; } + @SuppressWarnings("deprecation") public void initUserInput(ActionEvent ae) { userEmail = ""; userMessage = ""; messageSubject = ""; Random random = new Random(); - op1 = new Long(random.nextInt(10)); - op2 = new Long(random.nextInt(10)); + op1 = Long.valueOf(random.nextInt(10)); + op2 = Long.valueOf(random.nextInt(10)); userSum = null; String systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail); systemAddress = MailUtil.parseSystemAddress(systemEmail); @@ -129,19 +130,27 @@ public void setUserSum(Long userSum) { } public String getMessageTo() { - if (recipient == null) { + if (feedbackTarget == null) { return BrandingUtil.getSupportTeamName(systemAddress); - } else if (recipient.isInstanceofDataverse()) { - return ((Dataverse) recipient).getDisplayName() + " " + BundleUtil.getStringFromBundle("contact.contact"); + } else if (feedbackTarget.isInstanceofDataverse()) { + return ((Dataverse) feedbackTarget).getDisplayName() + " " + BundleUtil.getStringFromBundle("contact.contact"); } else { return BundleUtil.getStringFromBundle("dataset") + " " + BundleUtil.getStringFromBundle("contact.contact"); } } + + public String getMessageCC() { + if (ccSupport()) { + return BrandingUtil.getSupportTeamName(systemAddress); + } + return null; + } + public String getFormHeader() { - if (recipient == null) { + if (feedbackTarget == null) { return BrandingUtil.getContactHeader(systemAddress); - } else if (recipient.isInstanceofDataverse()) { + } else if (feedbackTarget.isInstanceofDataverse()) { return BundleUtil.getStringFromBundle("contact.dataverse.header"); } else { return BundleUtil.getStringFromBundle("contact.dataset.header"); @@ -173,11 +182,11 @@ public String loggedInUserEmail() { } public DvObject getRecipient() { - return recipient; + return feedbackTarget; } public void setRecipient(DvObject recipient) { - this.recipient = recipient; + this.feedbackTarget = recipient; } public void validateUserSum(FacesContext context, UIComponent component, Object value) throws ValidatorException { @@ -200,16 +209,20 @@ public void validateUserEmail(FacesContext context, UIComponent component, Objec public String sendMessage() { String installationBrandName = BrandingUtil.getInstallationBrandName(); String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress); - List feedbacks = FeedbackUtil.gatherFeedback(recipient, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName); - if (feedbacks.isEmpty()) { + + Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName, ccSupport()); + if (feedback==null) { logger.warning("No feedback has been sent!"); return null; } - for (Feedback feedback : feedbacks) { logger.fine("sending feedback: " + feedback); - mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getSubject(), feedback.getBody()); - } + mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody(), null); return null; } + + private boolean ccSupport() { + //Setting is enabled and this isn't already a direct message to support (no feedbackObject + return feedbackTarget!=null &&settingsService.isTrueForKey(SettingsServiceBean.Key.CCSupportOnContactEmails, false); + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java index d9a94ee340b..aea5bdb3bca 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java @@ -2,12 +2,11 @@ import edu.harvard.iq.dataverse.DataverseSession; import edu.harvard.iq.dataverse.DvObject; -import edu.harvard.iq.dataverse.DvObjectServiceBean; import edu.harvard.iq.dataverse.branding.BrandingUtil; import edu.harvard.iq.dataverse.feedback.Feedback; import edu.harvard.iq.dataverse.feedback.FeedbackUtil; -import java.util.List; -import javax.ejb.EJB; +import edu.harvard.iq.dataverse.settings.SettingsServiceBean; + import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonNumber; @@ -21,15 +20,12 @@ @Path("admin/feedback") public class FeedbackApi extends AbstractApiBean { - @EJB - DvObjectServiceBean dvObjectSvc; - @POST public Response submitFeedback(JsonObject jsonObject) throws AddressException { JsonNumber jsonNumber = jsonObject.getJsonNumber("id"); - DvObject recipient = null; + DvObject feedbackTarget = null; if (jsonNumber != null) { - recipient = dvObjectSvc.findDvObject(jsonNumber.longValue()); + feedbackTarget = dvObjSvc.findDvObject(jsonNumber.longValue()); } DataverseSession dataverseSession = null; String userMessage = jsonObject.getString("body"); @@ -41,10 +37,10 @@ public Response submitFeedback(JsonObject jsonObject) throws AddressException { String installationBrandName = BrandingUtil.getInstallationBrandName(); String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress); JsonArrayBuilder jab = Json.createArrayBuilder(); - List feedbacks = FeedbackUtil.gatherFeedback(recipient, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - feedbacks.forEach((feedback) -> { - jab.add(feedback.toJsonObjectBuilder()); - }); + boolean ccSupport=feedbackTarget!=null &&settingsSvc.isTrueForKey(SettingsServiceBean.Key.CCSupportOnContactEmails, false); + Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, ccSupport); + jab.add(feedback.toJsonObjectBuilder()); + return ok(jab); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/feedback/Feedback.java b/src/main/java/edu/harvard/iq/dataverse/feedback/Feedback.java index e8677869496..c9acb491aa2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/feedback/Feedback.java +++ b/src/main/java/edu/harvard/iq/dataverse/feedback/Feedback.java @@ -3,16 +3,20 @@ import javax.json.Json; import javax.json.JsonObjectBuilder; +import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; + public class Feedback { private final String fromEmail; private final String toEmail; + private final String ccEmail; private final String subject; private final String body; - public Feedback(String fromEmail, String toEmail, String subject, String body) { + public Feedback(String fromEmail, String toEmail, String ccEmail, String subject, String body) { this.fromEmail = fromEmail; this.toEmail = toEmail; + this.ccEmail=ccEmail; this.subject = subject; this.body = body; } @@ -24,6 +28,10 @@ public String getFromEmail() { public String getToEmail() { return toEmail; } + + public String getCcEmail() { + return ccEmail; + } public String getSubject() { return subject; @@ -35,13 +43,14 @@ public String getBody() { @Override public String toString() { - return "Feedback{" + "fromEmail=" + fromEmail + ", toEmail=" + toEmail + ", subject=" + subject + ", body=" + body + '}'; + return "Feedback{" + "fromEmail=" + fromEmail + ", toEmail=" + toEmail + ", ccEmail=" + ccEmail + ", subject=" + subject + ", body=" + body + '}'; } public JsonObjectBuilder toJsonObjectBuilder() { - return Json.createObjectBuilder() + return new NullSafeJsonBuilder() .add("fromEmail", fromEmail) .add("toEmail", toEmail) + .add("ccEmail", ccEmail) .add("subject", subject) .add("body", body); } diff --git a/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java b/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java index 8b23d68f4b7..c6bb33136ea 100644 --- a/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java @@ -10,10 +10,14 @@ import edu.harvard.iq.dataverse.DataverseSession; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.util.BundleUtil; +import edu.harvard.iq.dataverse.util.PersonOrOrgUtil; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Logger; + +import javax.json.JsonObject; import javax.mail.internet.InternetAddress; public class FeedbackUtil { @@ -22,87 +26,118 @@ public class FeedbackUtil { private static final String NO_DATASET_CONTACT_INTRO = BundleUtil.getStringFromBundle("contact.context.dataset.noContact"); - // TODO: consider changing "recipient" into an object called something like FeedbackTarget - public static List gatherFeedback(DvObject recipient, DataverseSession dataverseSession, String messageSubject, String userMessage, InternetAddress systemAddress, String userEmail, String dataverseSiteUrl, String installationBrandName, String supportTeamName) { + public static Feedback gatherFeedback(DvObject feedbackTarget, DataverseSession dataverseSession, String messageSubject, String userMessage, InternetAddress systemAddress, String userEmail, String dataverseSiteUrl, String installationBrandName, String supportTeamName, boolean ccSupport) { String systemEmail = null; if (systemAddress != null) { systemEmail = systemAddress.getAddress(); } logger.fine("systemAddress: " + systemAddress); - List feedbacks = new ArrayList<>(); + Feedback feedback = null; if (isLoggedIn(dataverseSession)) { userEmail = loggedInUserEmail(dataverseSession); } - if (recipient != null) { + String contextIntro; + String contextEnding; + String contactEmails; + String ccEmails = ccSupport ? systemEmail : null; + + if (feedbackTarget != null) { messageSubject = BundleUtil.getStringFromBundle("contact.context.subject.dvobject", Arrays.asList(installationBrandName, messageSubject)); - if (recipient.isInstanceofDataverse()) { - Dataverse dataverse = (Dataverse) recipient; - String dataverseContextEnding = BundleUtil.getStringFromBundle("contact.context.dataverse.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, dataverse.getAlias(), supportTeamName, systemEmail)); - List dataverseContacts = getDataverseContacts(dataverse); - for (DvObjectContact dataverseContact : dataverseContacts) { - String placeHolderIfDataverseContactsGetNames = ""; - String dataverseContextIntro = BundleUtil.getStringFromBundle("contact.context.dataverse.intro", Arrays.asList(placeHolderIfDataverseContactsGetNames, userEmail, installationBrandName, dataverse.getAlias())); - Feedback feedback = new Feedback(userEmail, dataverseContact.getEmail(), messageSubject, dataverseContextIntro + userMessage + dataverseContextEnding); - feedbacks.add(feedback); + + String contactGreeting; + + if (feedbackTarget.isInstanceofDataverse()) { + // Dataverse target + Dataverse dataverse = (Dataverse) feedbackTarget; + contextEnding = BundleUtil.getStringFromBundle("contact.context.dataverse.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, dataverse.getAlias(), supportTeamName, systemEmail)); + List contacts = getDataverseContacts(dataverse); + List contactEmailList = new ArrayList(); + for (DvObjectContact contact : contacts) { + contactEmailList.add(contact.getEmail()); } - if (!feedbacks.isEmpty()) { - return feedbacks; + if (!contactEmailList.isEmpty()) { + contactEmails = String.join(",", contactEmailList); + // Dataverse contacts do not have a name, just email address + contactGreeting = ""; + contextIntro = BundleUtil.getStringFromBundle("contact.context.dataverse.intro", Arrays.asList(contactGreeting, userEmail, installationBrandName, dataverse.getAlias())); } else { - String dataverseContextIntroError = BundleUtil.getStringFromBundle("contact.context.dataverse.noContact"); - Feedback feedback = new Feedback(userEmail, systemEmail, messageSubject, dataverseContextIntroError + userMessage + dataverseContextEnding); - feedbacks.add(feedback); - return feedbacks; + // No contacts + contextIntro = BundleUtil.getStringFromBundle("contact.context.dataverse.noContact"); + contactEmails = systemEmail; + ccEmails = null; } - } else if (recipient.isInstanceofDataset()) { - Dataset dataset = (Dataset) recipient; + } else if (feedbackTarget.isInstanceofDataset()) { + // Dataset target + Dataset dataset = (Dataset) feedbackTarget; String datasetTitle = dataset.getLatestVersion().getTitle(); - String datasetPid = dataset.getGlobalIdString(); - String datasetContextEnding = BundleUtil.getStringFromBundle("contact.context.dataset.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, dataset.getGlobalIdString(), supportTeamName, systemEmail)); - List datasetContacts = getDatasetContacts(dataset); - for (DvObjectContact datasetContact : datasetContacts) { - String contactFullName = getGreeting(datasetContact); - String datasetContextIntro = BundleUtil.getStringFromBundle("contact.context.dataset.intro", Arrays.asList(contactFullName, userEmail, installationBrandName, datasetTitle, datasetPid)); - Feedback feedback = new Feedback(userEmail, datasetContact.getEmail(), messageSubject, datasetContextIntro + userMessage + datasetContextEnding); - feedbacks.add(feedback); + String datasetPid = dataset.getGlobalId().asString(); + contextEnding = BundleUtil.getStringFromBundle("contact.context.dataset.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, datasetPid, supportTeamName, systemEmail)); + List contacts = getDatasetContacts(dataset); + List contactEmailList = new ArrayList(); + List contactNameList = new ArrayList(); + + for (DvObjectContact contact : contacts) { + String name = getContactName(contact); + if (name != null) { + contactNameList.add(name); + } + contactEmailList.add(contact.getEmail()); } - if (!feedbacks.isEmpty()) { - return feedbacks; + if (!contactEmailList.isEmpty()) { + contactEmails = String.join(",", contactEmailList); + contactGreeting = getGreeting(contactNameList); + + contextIntro = BundleUtil.getStringFromBundle("contact.context.dataset.intro", Arrays.asList(contactGreeting, userEmail, installationBrandName, datasetTitle, datasetPid)); } else { - // TODO: Add more of an intro for the person receiving the system email in this "no dataset contact" scenario? - Feedback feedback = new Feedback(userEmail, systemEmail, messageSubject, NO_DATASET_CONTACT_INTRO + userMessage + datasetContextEnding); - feedbacks.add(feedback); - return feedbacks; + // No contacts + // TODO: Add more of an intro for the person receiving the system email in this + // "no dataset contact" scenario? + contextIntro = NO_DATASET_CONTACT_INTRO; + contactEmails = systemEmail; + ccEmails = null; } } else { - DataFile datafile = (DataFile) recipient; + // DataFile target + DataFile datafile = (DataFile) feedbackTarget; String datasetTitle = datafile.getOwner().getLatestVersion().getTitle(); - String datasetPid = datafile.getOwner().getGlobalIdString(); + String datasetPid = datafile.getOwner().getGlobalId().asString(); String filename = datafile.getFileMetadatas().get(0).getLabel(); - List datasetContacts = getDatasetContacts(datafile.getOwner()); - String fileContextEnding = BundleUtil.getStringFromBundle("contact.context.file.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, datafile.getId().toString(), supportTeamName, systemEmail)); - for (DvObjectContact datasetContact : datasetContacts) { - String contactFullName = getGreeting(datasetContact); - String fileContextIntro = BundleUtil.getStringFromBundle("contact.context.file.intro", Arrays.asList(contactFullName, userEmail, installationBrandName, filename, datasetTitle, datasetPid)); - Feedback feedback = new Feedback(userEmail, datasetContact.getEmail(), messageSubject, fileContextIntro + userMessage + fileContextEnding); - feedbacks.add(feedback); + List contacts = getDatasetContacts(datafile.getOwner()); + contextEnding = BundleUtil.getStringFromBundle("contact.context.file.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, datafile.getId().toString(), supportTeamName, systemEmail)); + List contactEmailList = new ArrayList(); + List contactNameList = new ArrayList(); + + for (DvObjectContact contact : contacts) { + String name = getContactName(contact); + if (name != null) { + contactNameList.add(name); + } + contactEmailList.add(contact.getEmail()); } - if (!feedbacks.isEmpty()) { - return feedbacks; + if (!contactEmailList.isEmpty()) { + contactEmails = String.join(",", contactEmailList); + contactGreeting = getGreeting(contactNameList); + + contextIntro = BundleUtil.getStringFromBundle("contact.context.file.intro", Arrays.asList(contactGreeting, userEmail, installationBrandName, filename, datasetTitle, datasetPid)); } else { - // TODO: Add more of an intro for the person receiving the system email in this "no dataset contact" scenario? - Feedback feedback = new Feedback(userEmail, systemEmail, messageSubject, NO_DATASET_CONTACT_INTRO + userMessage + fileContextEnding); - feedbacks.add(feedback); - return feedbacks; + // No contacts + // TODO: Add more of an intro for the person receiving the system email in this + // "no dataset contact" scenario? + contextIntro = NO_DATASET_CONTACT_INTRO; + contactEmails = systemEmail; + ccEmails = null; } } } else { + // No target messageSubject = BundleUtil.getStringFromBundle("contact.context.subject.support", Arrays.asList(installationBrandName, messageSubject)); - String noDvObjectContextIntro = BundleUtil.getStringFromBundle("contact.context.support.intro", Arrays.asList(supportTeamName, userEmail)); - String noDvObjectContextEnding = BundleUtil.getStringFromBundle("contact.context.support.ending", Arrays.asList("")); - Feedback feedback = new Feedback(userEmail, systemEmail, messageSubject, noDvObjectContextIntro + userMessage + noDvObjectContextEnding); - feedbacks.add(feedback); - return feedbacks; + contextIntro = BundleUtil.getStringFromBundle("contact.context.support.intro", Arrays.asList(supportTeamName, userEmail)); + contextEnding = BundleUtil.getStringFromBundle("contact.context.support.ending", Arrays.asList("")); + contactEmails = systemEmail; + ccEmails = null; } + feedback = new Feedback(userEmail, contactEmails, ccEmails, messageSubject, contextIntro + userMessage + contextEnding); + return feedback; } private static boolean isLoggedIn(DataverseSession dataverseSession) { @@ -156,30 +191,53 @@ private static List getDatasetContacts(Dataset dataset) { } /** - * When contacts are people we suggest that they be stored as "Simpson, - * Homer" so the idea of this method is that it returns "Homer Simpson", if - * it can. + * When contacts are people we suggest that they be stored as "Simpson, Homer" + * so the idea of this method is that it returns "Homer Simpson", if it can. * * Contacts don't necessarily have to be people, however. They can be - * organizations. We ran into similar trouble (but for authors) when - * implementing Schema.org JSON-LD support. See getJsonLd on DatasetVersion. - * Some day it might be nice to store whether an author or a contact is a - * person or an organization. + * organizations. This method uses the algorithm to detect whether an entry is a + * Person or Organization and relies on it to create a full name, i.e. removing + * the comma and reversion the order of names for a Person but not changing the + * string for an Organization. + */ + private static String getContactName(DvObjectContact dvObjectContact) { + String contactName = dvObjectContact.getName(); + String name = null; + if (contactName != null) { + JsonObject entity = PersonOrOrgUtil.getPersonOrOrganization(contactName, false, false); + if (entity.getBoolean("isPerson")) { + name = entity.getString("givenName") + " " + entity.getString("familyName"); + } else { + name = entity.getString("fullName"); + } + } + return name; + + } + + /** + * Concatenates names using commas and a final 'and' and creates the greeting + * string, e.g. "Hello Homer Simpson, Bart Simson, and Marge Simpson" */ - private static String getGreeting(DvObjectContact dvObjectContact) { - logger.fine("dvObjectContact: " + dvObjectContact); - try { - String name = dvObjectContact.getName(); - logger.fine("dvObjectContact name: " + name); - String lastFirstString = dvObjectContact.getName(); - String[] lastFirstParts = lastFirstString.split(","); - String last = lastFirstParts[0]; - String first = lastFirstParts[1]; - return BundleUtil.getStringFromBundle("contact.context.dataset.greeting.helloFirstLast", Arrays.asList(first.trim(), last.trim())); - } catch (Exception ex) { - logger.warning("problem in getGreeting: " + ex); + private static String getGreeting(List contactNameList) { + int size = contactNameList.size(); + String nameString; + String finalName = null; + // Treat the final name separately + switch (size) { + case 0: return BundleUtil.getStringFromBundle("contact.context.dataset.greeting.organization"); + case 1: + nameString = contactNameList.get(0); + break; + case 2: + nameString = contactNameList.get(0) + " and " + contactNameList.get(1); + break; + default: + finalName = contactNameList.remove(size - 1); + nameString = String.join(",", contactNameList) + ", and " + finalName; } + return BundleUtil.getStringFromBundle("contact.context.dataset.greeting.helloFirstLast", Arrays.asList(nameString)); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index 102772bdcf3..61a7393d9ed 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -567,7 +567,12 @@ Whether Harvesting (OAI) service is enabled /** * A compound setting for disabling signup for remote Auth providers: */ - AllowRemoteAuthSignUp + AllowRemoteAuthSignUp, + /* + * Whether the repository support team should be cc'd on requests created in the + * contact form. Default is false. + */ + CCSupportOnContactEmails ; @Override diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index b19e80020ba..a92865c44fa 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -145,6 +145,7 @@ contact.header=Contact {0} contact.dataverse.header=Email Dataverse Contact contact.dataset.header=Email Dataset Contact contact.to=To +contact.cc=CC contact.support=Support contact.from=From contact.from.required=User email is required. @@ -169,7 +170,7 @@ contact.context.subject.support={0} support request: {1} contact.context.dataverse.intro={0}You have just been sent the following message from {1} via the {2} hosted dataverse named "{3}":\n\n---\n\n contact.context.dataverse.ending=\n\n---\n\n{0}\n{1}\n\nGo to dataverse {2}/dataverse/{3}\n\nYou received this email because you have been listed as a contact for the dataverse. If you believe this was an error, please contact {4} at {5}. To respond directly to the individual who sent the message, simply reply to this email. contact.context.dataverse.noContact=There is no contact address on file for this dataverse so this message is being sent to the system address.\n\n -contact.context.dataset.greeting.helloFirstLast=Hello {0} {1}, +contact.context.dataset.greeting.helloFirstLast=Hello {0}, contact.context.dataset.greeting.organization=Attention Dataset Contact: contact.context.dataset.intro={0}\n\nYou have just been sent the following message from {1} via the {2} hosted dataset titled "{3}" ({4}):\n\n---\n\n contact.context.dataset.ending=\n\n---\n\n{0}\n{1}\n\nGo to dataset {2}/dataset.xhtml?persistentId={3}\n\nYou received this email because you have been listed as a contact for the dataset. If you believe this was an error, please contact {4} at {5}. To respond directly to the individual who sent the message, simply reply to this email. diff --git a/src/main/webapp/contactFormFragment.xhtml b/src/main/webapp/contactFormFragment.xhtml index 264fde98545..58f48f73177 100644 --- a/src/main/webapp/contactFormFragment.xhtml +++ b/src/main/webapp/contactFormFragment.xhtml @@ -16,6 +16,12 @@

#{sendFeedbackDialog.getMessageTo()}

+
+ +
+

#{sendFeedbackDialog.getMessageCC()}

+
+
diff --git a/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java index 2c91eebbc83..40188010d18 100644 --- a/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java @@ -181,9 +181,8 @@ public void testGatherFeedbackOnDataverse() { String messageSubject = "nice dataverse"; String userMessage = "Let's talk!"; System.out.println("first gather feedback"); - List feedbacks1 = FeedbackUtil.gatherFeedback(dataverse, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - Feedback feedback = feedbacks1.get(0); - assertEquals(installationBrandName + " contact: " + messageSubject, feedback.getSubject()); + Feedback feedback1 = FeedbackUtil.gatherFeedback(dataverse, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, true); + assertEquals(installationBrandName + " contact: " + messageSubject, feedback1.getSubject()); String expectedBody = "You have just been sent the following message from " + userEmail + " " + "via the " + installationBrandName + " hosted dataverse named \"dvAlias1\":\n\n" @@ -197,23 +196,26 @@ public void testGatherFeedbackOnDataverse() { + "If you believe this was an error, please contact " + supportTeamName + " at " + systemEmail + ". " + "To respond directly to the individual who sent the message, simply reply to this email."; - System.out.println("body:\n\n" + feedback.getBody()); - assertEquals(expectedBody, feedback.getBody()); - assertEquals("dvContact1@librascholar.edu", feedback.getToEmail()); - assertEquals("personClickingContactOrSupportButton@example.com", feedback.getFromEmail()); - JsonObject jsonObject = feedback.toJsonObjectBuilder().build(); + System.out.println("body:\n\n" + feedback1.getBody()); + assertEquals(expectedBody, feedback1.getBody()); + assertEquals("dvContact1@librascholar.edu,dvContact2@librascholar.edu", feedback1.getToEmail()); + assertEquals(systemEmail, feedback1.getCcEmail()); + + assertEquals("personClickingContactOrSupportButton@example.com", feedback1.getFromEmail()); + JsonObject jsonObject = feedback1.toJsonObjectBuilder().build(); System.out.println("json: " + jsonObject); assertEquals("personClickingContactOrSupportButton@example.com", jsonObject.getString("fromEmail")); - assertEquals("dvContact1@librascholar.edu", jsonObject.getString("toEmail")); + assertEquals("dvContact1@librascholar.edu,dvContact2@librascholar.edu", jsonObject.getString("toEmail")); + assertEquals(systemEmail, jsonObject.getString("ccEmail")); assertEquals(installationBrandName + " contact: " + "nice dataverse", jsonObject.getString("subject")); dataverse.setDataverseContacts(new ArrayList<>()); System.out.println("second gather feedback"); - List feedbacks2 = FeedbackUtil.gatherFeedback(dataverse, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - System.out.println("feedbacks2: " + feedbacks2); - feedback = feedbacks2.get(0); - assertEquals("support@librascholar.edu", feedback.getToEmail()); - System.out.println("body:\n\n" + feedback.getBody()); - assertTrue(feedback.getBody().startsWith("There is no contact address on file for this dataverse so this message is being sent to the system address.")); + Feedback feedback2 = FeedbackUtil.gatherFeedback(dataverse, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); + System.out.println("feedbacks2: " + feedback2); + assertEquals(systemEmail, feedback2.getToEmail()); + assertEquals(null, feedback2.getCcEmail()); + System.out.println("body:\n\n" + feedback2.getBody()); + assertTrue(feedback2.getBody().startsWith("There is no contact address on file for this collection so this message is being sent to the system address.")); } @Test @@ -236,17 +238,18 @@ public void testGatherFeedbackOnDataset() { DataverseSession dataverseSession = null; String messageSubject = "nice dataset"; String userMessage = "Let's talk!"; - List feedbacks = FeedbackUtil.gatherFeedback(dataset, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - System.out.println("feedbacks: " + feedbacks); - assertEquals(2, feedbacks.size()); - Feedback feedback = feedbacks.get(0); + Feedback feedback = FeedbackUtil.gatherFeedback(dataset, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, true); + System.out.println("feedbacks: " + feedback); System.out.println("Subject: " + feedback.getSubject()); System.out.println("Body: " + feedback.getBody()); System.out.println("From: " + feedback.getFromEmail()); System.out.println("To: " + feedback.getToEmail()); - assertEquals("ContactEmail1@mailinator.com", feedback.getToEmail()); + System.out.println("CC: " + feedback.getCcEmail()); + + assertEquals("ContactEmail1@mailinator.com,ContactEmail2@mailinator.com", feedback.getToEmail()); + assertEquals(systemEmail, feedback.getCcEmail()); assertEquals(installationBrandName + " contact: " + messageSubject, feedback.getSubject()); - String expected = "Hello Tom Brady,\n\n" + String expected = "Hello Tom Brady and Homer Simpson,\n\n" // FIXME: change from "personClickingContactOrSupportButton@example.com" to "Homer Simpson" or whatever (add to contact form). + "You have just been sent the following message from " + userEmail + " " + "via the " + installationBrandName + " hosted dataset " @@ -284,15 +287,15 @@ public void testGatherFeedbackOnDatasetNoContacts() { DataverseSession dataverseSession = null; String messageSubject = "nice dataset"; String userMessage = "Let's talk!"; - List feedbacks = FeedbackUtil.gatherFeedback(dataset, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - System.out.println("feedbacks: " + feedbacks); - assertEquals(1, feedbacks.size()); - Feedback feedback = feedbacks.get(0); + Feedback feedback = FeedbackUtil.gatherFeedback(dataset, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); System.out.println("Subject: " + feedback.getSubject()); System.out.println("Body: " + feedback.getBody()); System.out.println("From: " + feedback.getFromEmail()); System.out.println("To: " + feedback.getToEmail()); + System.out.println("CC: " + feedback.getCcEmail()); + assertEquals(systemEmail, feedback.getToEmail()); + assertEquals(null, feedback.getCcEmail()); assertEquals(installationBrandName + " contact: " + messageSubject, feedback.getSubject()); String expected = "There is no contact address on file for this dataset so this message is being sent to the system address.\n\n" // FIXME: Add more context for person who receives systemEmail messages. @@ -357,8 +360,7 @@ public void testGatherFeedbackOnFile() { String messageSubject = "nice file"; String userMessage = "Let's talk!"; - List feedbacks = FeedbackUtil.gatherFeedback(dataFile, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - Feedback feedback = feedbacks.get(0); + Feedback feedback = FeedbackUtil.gatherFeedback(dataFile, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); System.out.println("feedback: " + feedback); System.out.println("Subject: " + feedback.getSubject()); System.out.println("Body: " + feedback.getBody()); @@ -425,8 +427,7 @@ public void testGatherFeedbackOnFileNoContacts() { String messageSubject = "nice file"; String userMessage = "Let's talk!"; - List feedbacks = FeedbackUtil.gatherFeedback(dataFile, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - Feedback feedback = feedbacks.get(0); + Feedback feedback = FeedbackUtil.gatherFeedback(dataFile, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); System.out.println("feedback: " + feedback); System.out.println("Subject: " + feedback.getSubject()); System.out.println("Body: " + feedback.getBody()); @@ -443,9 +444,7 @@ public void testGatherFeedbackFromSupportButtonNullSession() { String messageSubject = "I'm clicking the support button."; String userMessage = "Help!"; DvObject nullDvObject = null; - List feedbacks1 = FeedbackUtil.gatherFeedback(nullDvObject, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - System.out.println("feedbacks1: " + feedbacks1); - Feedback feedback = feedbacks1.get(0); + Feedback feedback = FeedbackUtil.gatherFeedback(nullDvObject, dataverseSessionNull, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); assertEquals(installationBrandName + " support request: " + messageSubject, feedback.getSubject()); String expectedBody = "LibraScholar SWAT Team,\n\n" @@ -460,15 +459,11 @@ public void testGatherFeedbackFromSupportButtonNullSession() { assertEquals("support@librascholar.edu", feedback.getToEmail()); assertEquals("personClickingContactOrSupportButton@example.com", feedback.getFromEmail()); InternetAddress nullSystemAddress = null; - List feedbacks2 = FeedbackUtil.gatherFeedback(nullDvObject, dataverseSessionNull, messageSubject, userMessage, nullSystemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - assertEquals(1, feedbacks2.size()); - feedback = feedbacks2.get(0); - assertEquals(null, feedback.getToEmail()); + Feedback feedback2 = FeedbackUtil.gatherFeedback(nullDvObject, dataverseSessionNull, messageSubject, userMessage, nullSystemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); + assertEquals(null, feedback2.getToEmail()); String nullUserMessage = null; - List feedbacks3 = FeedbackUtil.gatherFeedback(nullDvObject, dataverseSessionNull, messageSubject, nullUserMessage, nullSystemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - assertEquals(1, feedbacks3.size()); - feedback = feedbacks3.get(0); - assertEquals(null, feedback.getToEmail()); + Feedback feedback3 = FeedbackUtil.gatherFeedback(nullDvObject, dataverseSessionNull, messageSubject, nullUserMessage, nullSystemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); + assertEquals(null, feedback3.getToEmail()); } @Test @@ -479,8 +474,7 @@ public void testGatherFeedbackFromSupportButtonLoggedIn() { String messageSubject = "I'm clicking the support button."; String userMessage = "Help!"; DvObject dvObject = null; - List feedbacks = FeedbackUtil.gatherFeedback(dvObject, dataverseSessionAuthenticated, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName); - Feedback feedback = feedbacks.get(0); + Feedback feedback = FeedbackUtil.gatherFeedback(dvObject, dataverseSessionAuthenticated, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, false); assertEquals(messageSubject, feedback.getSubject()); assertEquals("Help!", feedback.getBody()); assertEquals("support@librascholar.edu", feedback.getToEmail()); diff --git a/tests/data/datasetContacts1.json b/tests/data/datasetContacts1.json index 3953fc6daef..8c4a7cb2b11 100644 --- a/tests/data/datasetContacts1.json +++ b/tests/data/datasetContacts1.json @@ -60,7 +60,7 @@ "typeName": "datasetContactName", "multiple": false, "typeClass": "primitive", - "value": "LastContact2, FirstContact2" + "value": "Simpson, Homer" }, "datasetContactAffiliation": { "typeName": "datasetContactAffiliation", From 666fdc32f0cdad3526a5f52a0a6b4d7cd5b2f903 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 22 Nov 2022 13:48:28 -0500 Subject: [PATCH 04/35] add support email setting --- .../edu/harvard/iq/dataverse/SendFeedbackDialog.java | 5 ++++- .../iq/dataverse/settings/SettingsServiceBean.java | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index d5faadea20b..e376bc01b08 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -101,7 +101,10 @@ public void initUserInput(ActionEvent ae) { op1 = Long.valueOf(random.nextInt(10)); op2 = Long.valueOf(random.nextInt(10)); userSum = null; - String systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail); + String systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SupportEmail); + if(systemEmail==null) { + systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail); + } systemAddress = MailUtil.parseSystemAddress(systemEmail); } diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index 61a7393d9ed..85bccf52749 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -568,9 +568,15 @@ Whether Harvesting (OAI) service is enabled * A compound setting for disabling signup for remote Auth providers: */ AllowRemoteAuthSignUp, + /** + * Optional email address to use with the support form/ feedback api emails. Default is to use the SystemEmail account. + * + */ + + SupportEmail, /* - * Whether the repository support team should be cc'd on requests created in the - * contact form. Default is false. + * Whether the repository support team should be CC'd on requests created in the + * contact form, when email is sent TO the listed Dataverse, Dataset, or DataFile contacts. Default is false. */ CCSupportOnContactEmails ; From 67ecb2dcd2299e34502b668f961e8fe421351322 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 22 Nov 2022 14:46:35 -0500 Subject: [PATCH 05/35] minimal feedback API method --- .../harvard/iq/dataverse/api/FeedbackApi.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java index aea5bdb3bca..23852911acf 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java @@ -2,11 +2,14 @@ import edu.harvard.iq.dataverse.DataverseSession; import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.MailServiceBean; import edu.harvard.iq.dataverse.branding.BrandingUtil; import edu.harvard.iq.dataverse.feedback.Feedback; import edu.harvard.iq.dataverse.feedback.FeedbackUtil; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; +import edu.harvard.iq.dataverse.util.MailUtil; +import javax.ejb.EJB; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonNumber; @@ -16,21 +19,40 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; @Path("admin/feedback") public class FeedbackApi extends AbstractApiBean { + @EJB MailServiceBean mailService; + + /** + * This method mimics the contact form and sends an email to the contacts of the + * specified Collection/Dataset/DataFile, optionally ccing the support email + * address, or to the support email address when there is no target object. + * + * !!!!! This should not be moved outside the /admin path unless/until some form of + * captcha or other spam-prevention mechanism is added. As is, it allows an + * unauthenticated user (with access to the /admin api path) to send email from + * anyone to any contacts in Dataverse.!!!! + **/ @POST public Response submitFeedback(JsonObject jsonObject) throws AddressException { JsonNumber jsonNumber = jsonObject.getJsonNumber("id"); DvObject feedbackTarget = null; if (jsonNumber != null) { feedbackTarget = dvObjSvc.findDvObject(jsonNumber.longValue()); + if(feedbackTarget==null) { + return error(Status.BAD_REQUEST, "Feedback target object not found"); + } } DataverseSession dataverseSession = null; String userMessage = jsonObject.getString("body"); - String systemEmail = "support@librascholar.edu"; - InternetAddress systemAddress = new InternetAddress(systemEmail); + String systemEmail = settingsSvc.getValueForKey(SettingsServiceBean.Key.SupportEmail); + if(systemEmail==null) { + systemEmail = settingsSvc.getValueForKey(SettingsServiceBean.Key.SystemEmail); + } + InternetAddress systemAddress = MailUtil.parseSystemAddress(systemEmail); String userEmail = jsonObject.getString("fromEmail"); String messageSubject = jsonObject.getString("subject"); String baseUrl = systemConfig.getDataverseSiteUrl(); @@ -40,7 +62,7 @@ public Response submitFeedback(JsonObject jsonObject) throws AddressException { boolean ccSupport=feedbackTarget!=null &&settingsSvc.isTrueForKey(SettingsServiceBean.Key.CCSupportOnContactEmails, false); Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, ccSupport); jab.add(feedback.toJsonObjectBuilder()); - + mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody(), null); return ok(jab); } } From 4fac803c4f52059b6d69b626939ad2ed4bdebdc4 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 22 Nov 2022 16:47:04 -0500 Subject: [PATCH 06/35] handle null cc --- src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java index 87339f64508..94cb710c906 100644 --- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java @@ -202,7 +202,9 @@ public void sendMail(String reply, String to, String cc, String subject, String msg.setSentDate(new Date()); msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false)); - msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false)); + if (cc != null) { + msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false)); + } msg.setSubject(subject, charset); msg.setText(messageText, charset); From e0b7f6ff38835982c0b616dbab3fef84d9ee630c Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 22 Nov 2022 16:47:27 -0500 Subject: [PATCH 07/35] more comment --- .../java/edu/harvard/iq/dataverse/api/FeedbackApi.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java index 23852911acf..0f005650c75 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java @@ -31,11 +31,12 @@ public class FeedbackApi extends AbstractApiBean { * specified Collection/Dataset/DataFile, optionally ccing the support email * address, or to the support email address when there is no target object. * - * !!!!! This should not be moved outside the /admin path unless/until some form of - * captcha or other spam-prevention mechanism is added. As is, it allows an + * !!!!! This should not be moved outside the /admin path unless/until some form + * of captcha or other spam-prevention mechanism is added. As is, it allows an * unauthenticated user (with access to the /admin api path) to send email from - * anyone to any contacts in Dataverse.!!!! - **/ + * anyone to any contacts in Dataverse. (It also does not do much to validate + * user input (e.g. to strip potentially malicious html, etc.)!!!! + **/ @POST public Response submitFeedback(JsonObject jsonObject) throws AddressException { JsonNumber jsonNumber = jsonObject.getJsonNumber("id"); From 497349839b0aca788f3c7422c5d66e3f7aa955f4 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 23 Nov 2022 12:18:06 -0500 Subject: [PATCH 08/35] docs, release notes --- .../9185-contact-email-updates.md | 12 ++++++++++ doc/sphinx-guides/source/api/native-api.rst | 23 ++++++++++++++++++- .../source/installation/config.rst | 17 ++++++++++++++ .../harvard/iq/dataverse/api/FeedbackApi.java | 2 +- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 doc/release-notes/9185-contact-email-updates.md diff --git a/doc/release-notes/9185-contact-email-updates.md b/doc/release-notes/9185-contact-email-updates.md new file mode 100644 index 00000000000..8f5833f6fa6 --- /dev/null +++ b/doc/release-notes/9185-contact-email-updates.md @@ -0,0 +1,12 @@ +## Contact Email Improvements + +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. + +## Backward Incompatibilities + +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). + +## New Settings + +:SupportEmail - allows a separate email, distinct from the :SystemEmail to be used as the from address in emails from the contact form/ feedback api. +:CCSupportOnContactEmails - include the support email address as a CC: entry when contact/feedback emails are sent to the contacts for a collection, dataset, or datafile. \ No newline at end of file diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index caa78245c84..17cf764f3ab 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -4078,5 +4078,26 @@ The fully expanded example above (without environment variables) looks like this curl -X DELETE https://demo.dataverse.org/api/admin/template/24 - +Send Feedback To Contact(s) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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. +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. +Administrators should be sure only trusted applications have access to avoid the potential for spam. + +The call is a POST with a JSON object as input with four keys: +- "targetId" - the id of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional) +- "subject" - the email subject line +- "body" - the email body to send +- "fromEmail" - the email to list in the from field. + +A curl example using an ``ID`` + +.. code-block:: bash + + export SERVER_URL=https://demo.dataverse.org + export JSON='{"targetId":24, "subject":"Data Question", "body":"Please help me understand your data. Thank you!", "fromEmail":"dataverseSupport@mailinator.com"}' + + curl -X POST -H 'Content-Type:application/json' -d "$JSON" targetId$SERVER_URL/api/admin/feedback + diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 9b570e59e52..bf266c819a0 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -3090,3 +3090,20 @@ The interval in seconds between Dataverse calls to Globus to check on upload pro +++++++++++++++++++++++++ A true/false option to add a Globus transfer option to the file download menu which is not yet fully supported in the dataverse-globus app. See :ref:`globus-support` for details. + +:SupportEmail ++++++++++++++ + +This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address that emails from the Contact Forms and Feedback API are sent from. +This allows configuration of a no-reply email address for :ref:`systemEmail` why allow replies to the SupportEmail address. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email. + +``curl -X PUT -d 'LibraScholar Support Team ' http://localhost:8080/api/admin/settings/:SupportEmail`` + +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. + + +:CCSupportOnContactEmails ++++++++++++++++++++++++++ + +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. +A CC line is added to the contact form when this setting is true so that users are aware that the cc will occur. diff --git a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java index 0f005650c75..89fa28ad20b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java @@ -39,7 +39,7 @@ public class FeedbackApi extends AbstractApiBean { **/ @POST public Response submitFeedback(JsonObject jsonObject) throws AddressException { - JsonNumber jsonNumber = jsonObject.getJsonNumber("id"); + JsonNumber jsonNumber = jsonObject.getJsonNumber("targetId"); DvObject feedbackTarget = null; if (jsonNumber != null) { feedbackTarget = dvObjSvc.findDvObject(jsonNumber.longValue()); From 47397c1f9debf71f04246aa4a18cb55e8d67a6fc Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 23 Nov 2022 12:50:24 -0500 Subject: [PATCH 09/35] add PersonOrOrgUtil from #9089 --- .../iq/dataverse/util/PersonOrOrgUtil.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java diff --git a/src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java b/src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java new file mode 100644 index 00000000000..a98528d69db --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtil.java @@ -0,0 +1,101 @@ +package edu.harvard.iq.dataverse.util; + +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +import edu.harvard.iq.dataverse.export.openaire.Cleanup; +import edu.harvard.iq.dataverse.export.openaire.FirstNames; +import edu.harvard.iq.dataverse.export.openaire.Organizations; +import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; + +/** + * + * @author qqmyers + * + * Adapted from earlier code in OpenAireExportUtil + * + * Implements an algorithm derived from code at DataCite to determine + * whether a name is that of a Person or Organization and, if the + * former, to pull out the given and family names. + * + * Adds a parameter that can improve accuracy, e.g. for curated + * repositories, allowing the code to assume that all Person entries are + * in , order. + * + * Possible ToDo - one could also allow local configuration of specific + * words that will automatically categorize one-off cases that the + * algorithm would otherwise mis-categorize. For example, the code + * appears to not recognize names ending in "Project" as an + * Organization. + * + */ + +public class PersonOrOrgUtil { + + static boolean assumeCommaInPersonName = false; + + static { + setAssumeCommaInPersonName(Boolean.parseBoolean(System.getProperty("dataverse.personOrOrg.assumeCommaInPersonName", "false"))); + } + + public static JsonObject getPersonOrOrganization(String name, boolean organizationIfTied, boolean isPerson) { + name = Cleanup.normalize(name); + + String givenName = null; + String familyName = null; + // adapted from a Datacite algorithm, + // https://github.com/IQSS/dataverse/issues/2243#issuecomment-358615313 + boolean isOrganization = !isPerson && Organizations.getInstance().isOrganization(name); + // ToDo - could add a check of stop words to handle problem cases, i.e. if name + // contains something in that list, it is an org + if (name.contains(",")) { + givenName = FirstNames.getInstance().getFirstName(name); + // contributorName=, + if (givenName != null && !isOrganization) { + // givenName ok + isOrganization = false; + // contributor_map.put("nameType", "Personal"); + if (!name.replaceFirst(",", "").contains(",")) { + // contributorName=, + String[] fullName = name.split(", "); + givenName = fullName[1]; + familyName = fullName[0]; + } + } else if (isOrganization || organizationIfTied) { + isOrganization = true; + givenName = null; + } + + } else { + if (assumeCommaInPersonName && !isPerson) { + isOrganization = true; + } else { + givenName = FirstNames.getInstance().getFirstName(name); + + if (givenName != null && !isOrganization) { + isOrganization = false; + if (givenName.length() + 1 < name.length()) { + familyName = name.substring(givenName.length() + 1); + } + } else { + // default + if (isOrganization || organizationIfTied) { + isOrganization = true; + } + } + } + } + JsonObjectBuilder job = new NullSafeJsonBuilder(); + job.add("fullName", name); + job.add("givenName", givenName); + job.add("familyName", familyName); + job.add("isPerson", !isOrganization); + return job.build(); + + } + + public static void setAssumeCommaInPersonName(boolean assume) { + assumeCommaInPersonName = assume; + } + +} From 0c9c7652af8df64c262a51ae0f0fd8f0be530914 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 23 Nov 2022 12:54:02 -0500 Subject: [PATCH 10/35] fix text --- .../edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java index 40188010d18..bd339bbdd5f 100644 --- a/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java @@ -215,7 +215,7 @@ public void testGatherFeedbackOnDataverse() { assertEquals(systemEmail, feedback2.getToEmail()); assertEquals(null, feedback2.getCcEmail()); System.out.println("body:\n\n" + feedback2.getBody()); - assertTrue(feedback2.getBody().startsWith("There is no contact address on file for this collection so this message is being sent to the system address.")); + assertTrue(feedback2.getBody().startsWith("There is no contact address on file for this dataset so this message is being sent to the system address.")); } @Test From 7b7d9f5d9649e283063078d059f97ced1bacab2b Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 23 Nov 2022 13:12:21 -0500 Subject: [PATCH 11/35] typo --- .../edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java index bd339bbdd5f..47e0f6da20e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/feedback/FeedbackUtilTest.java @@ -215,7 +215,7 @@ public void testGatherFeedbackOnDataverse() { assertEquals(systemEmail, feedback2.getToEmail()); assertEquals(null, feedback2.getCcEmail()); System.out.println("body:\n\n" + feedback2.getBody()); - assertTrue(feedback2.getBody().startsWith("There is no contact address on file for this dataset so this message is being sent to the system address.")); + assertTrue(feedback2.getBody().startsWith("There is no contact address on file for this dataverse so this message is being sent to the system address.")); } @Test From 02e683c821a759b9e5ef640e7f61c04020534752 Mon Sep 17 00:00:00 2001 From: qqmyers Date: Tue, 25 Apr 2023 12:34:50 -0400 Subject: [PATCH 12/35] add release note re: doing reExportAll to update old schema.org exports --- doc/release-notes/9100-schema.org-updates.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/release-notes/9100-schema.org-updates.md diff --git a/doc/release-notes/9100-schema.org-updates.md b/doc/release-notes/9100-schema.org-updates.md new file mode 100644 index 00000000000..c76a0cc38e5 --- /dev/null +++ b/doc/release-notes/9100-schema.org-updates.md @@ -0,0 +1,3 @@ +Changes made in v5.13 and v5.14 in multiple PRs to improve the embedded Schema.org metadata in dataset pages will only be propagated to the Schema.Org JSON-LD metadata export if a reExportAll() is done. + +The 5.14 release notes should include the standard instructions for doing a reExportAll after updating the code. \ No newline at end of file From 609d958792b14788701d2048ecd97b8bd6338d4b Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 26 Apr 2023 14:16:18 -0400 Subject: [PATCH 13/35] changes per review request --- doc/sphinx-guides/source/api/native-api.rst | 7 ++++ .../source/installation/config.rst | 40 +++++++++++-------- .../iq/dataverse/SendFeedbackDialog.java | 9 ++--- .../iq/dataverse/settings/JvmSettings.java | 5 +++ .../settings/SettingsServiceBean.java | 22 ++++------ 5 files changed, 46 insertions(+), 37 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 8e99028da9e..6572512f1ec 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1618,6 +1618,8 @@ The fully expanded example above (without environment variables) looks like this 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. +.. _return-a-dataset: + Return a Dataset to Author ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1645,6 +1647,8 @@ The fully expanded example above (without environment variables) looks like this 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. +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-emails` will send a copy of these emails to the support email address which would provide a record.) + Link a Dataset ~~~~~~~~~~~~~~ @@ -4498,6 +4502,8 @@ A curl example using allowing access to a dataset's metadata Please see :ref:`dataverse.api.signature-secret` for the configuration option to add a shared secret, enabling extra security. +.. _send-feedback: + Send Feedback To Contact(s) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -4520,3 +4526,4 @@ A curl example using an ``ID`` curl -X POST -H 'Content-Type:application/json' -d "$JSON" targetId$SERVER_URL/api/admin/feedback +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`. diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 37cec90374f..b32d6e6ac95 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2349,6 +2349,29 @@ See :ref:`discovery-sign-posting` for details. Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_SIGNPOSTING_LEVEL1_ITEM_LIMIT``. +dataverse.mail.support-email +++++++++++++++++++++++++++++ + +This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address that emails from the Contact Forms and Feedback API are sent from. +This allows configuration of a no-reply email address for :ref:`systemEmail` why allow replies to the SupportEmail address. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email. + +``curl -X PUT -d 'LibraScholar Support Team ' http://localhost:8080/api/admin/settings/:SupportEmail`` + +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. + +Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_MAIL_SUPPORT_EMAIL``. + +.. _dataverse.mail.cc-support-on-contact-emails: + +dataverse.mail.cc-support-on-contact-emails ++++++++++++++++++++++++++++++++++++++++++++ + +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. +A CC line is added to the contact form when this setting is true so that users are aware that the cc will occur. +The default is false. + +Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_MAIL_CC_SUPPORT_ON_CONTACT_EMAILS``. + .. _feature-flags: @@ -3813,20 +3836,3 @@ To use the current GDCC version directly: .. _supported MicroProfile Config API source: https://docs.payara.fish/community/docs/Technical%20Documentation/MicroProfile/Config/Overview.html -:SupportEmail -+++++++++++++ - -This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address that emails from the Contact Forms and Feedback API are sent from. -This allows configuration of a no-reply email address for :ref:`systemEmail` why allow replies to the SupportEmail address. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email. - -``curl -X PUT -d 'LibraScholar Support Team ' http://localhost:8080/api/admin/settings/:SupportEmail`` - -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. - - -:CCSupportOnContactEmails -+++++++++++++++++++++++++ - -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. -A CC line is added to the contact form when this setting is true so that users are aware that the cc will occur. - diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index e376bc01b08..8dd233abf2f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -101,11 +101,8 @@ public void initUserInput(ActionEvent ae) { op1 = Long.valueOf(random.nextInt(10)); op2 = Long.valueOf(random.nextInt(10)); userSum = null; - String systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SupportEmail); - if(systemEmail==null) { - systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail); - } - systemAddress = MailUtil.parseSystemAddress(systemEmail); + String supportEmail = JvmSettings.SUPPORT_EMAIL.lookupOptional().orElse(settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail)); + systemAddress = MailUtil.parseSystemAddress(supportEmail); } public Long getOp1() { @@ -225,7 +222,7 @@ public String sendMessage() { private boolean ccSupport() { //Setting is enabled and this isn't already a direct message to support (no feedbackObject - return feedbackTarget!=null &&settingsService.isTrueForKey(SettingsServiceBean.Key.CCSupportOnContactEmails, false); + return feedbackTarget!=null && JvmSettings.CC_SUPPORT_ON_CONTACT_EMAILS.lookupOptional(Boolean.class, false); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java index 86130f5146e..8fd0c827c67 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java @@ -106,6 +106,11 @@ public enum JvmSettings { HANDLENET_KEY_PATH(SCOPE_PID_HANDLENET_KEY, "path", "dataverse.handlenet.admcredfile"), HANDLENET_KEY_PASSPHRASE(SCOPE_PID_HANDLENET_KEY, "passphrase", "dataverse.handlenet.admprivphrase"), + // MAIL SETTINGS + SCOPE_MAIL(PREFIX, "mail"), + SUPPORT_EMAIL(SCOPE_MAIL, "support-email"), + CC_SUPPORT_ON_CONTACT_EMAILS(SCOPE_MAIL, "cc-support-on-contact-email"), + ; private static final String SCOPE_SEPARATOR = "."; diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index 29ce61191a3..d84e18d5931 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -174,7 +174,12 @@ public enum Key { * */ SearchRespectPermissionRoot, - /** Solr hostname and port, such as "localhost:8983". */ + /** + * Solr hostname and port, such as "localhost:8983". + * @deprecated New installations should not use this database setting, but use {@link JvmSettings#SOLR_HOST} + * and {@link JvmSettings#SOLR_PORT}. + */ + @Deprecated(forRemoval = true, since = "2022-12-23") SolrHostColonPort, /** Enable full-text indexing in solr up to max file size */ SolrFullTextIndexing, //true or false (default) @@ -563,7 +568,7 @@ Whether Harvesting (OAI) service is enabled /* * Allow a custom JavaScript to control values of specific fields. */ - ControlledVocabularyCustomJavaScript, + ControlledVocabularyCustomJavaScript, /** * A compound setting for disabling signup for remote Auth providers: */ @@ -571,18 +576,7 @@ Whether Harvesting (OAI) service is enabled /** * The URL for the DvWebLoader tool (see github.com/gdcc/dvwebloader for details) */ - WebloaderUrl, - /** - * Optional email address to use with the support form/ feedback api emails. Default is to use the SystemEmail account. - * - */ - - SupportEmail, - /* - * Whether the repository support team should be CC'd on requests created in the - * contact form, when email is sent TO the listed Dataverse, Dataset, or DataFile contacts. Default is false. - */ - CCSupportOnContactEmails + WebloaderUrl ; From 6a5c9f99a58ebe4e54fcc35d77b623053981ab05 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 26 Apr 2023 14:18:22 -0400 Subject: [PATCH 14/35] update release note --- doc/release-notes/9185-contact-email-updates.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/release-notes/9185-contact-email-updates.md b/doc/release-notes/9185-contact-email-updates.md index 8f5833f6fa6..97216407c8b 100644 --- a/doc/release-notes/9185-contact-email-updates.md +++ b/doc/release-notes/9185-contact-email-updates.md @@ -6,7 +6,7 @@ Email sent from the contact forms to the contact(s) for a collection, dataset, o 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). -## New Settings +## New JVM/MicroProfile Settings -:SupportEmail - allows a separate email, distinct from the :SystemEmail to be used as the from address in emails from the contact form/ feedback api. -:CCSupportOnContactEmails - include the support email address as a CC: entry when contact/feedback emails are sent to the contacts for a collection, dataset, or datafile. \ No newline at end of file +dataverse.mail.support-email - allows a separate email, distinct from the :SystemEmail to be used as the from address in emails from the contact form/ feedback api. +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. \ No newline at end of file From 0ddd6a1cd8814ad4d88ee4f61da50d0083267247 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 26 Apr 2023 14:50:41 -0400 Subject: [PATCH 15/35] fix compile issues/cleanup --- .../harvard/iq/dataverse/SendFeedbackDialog.java | 14 +++++++++----- .../edu/harvard/iq/dataverse/api/FeedbackApi.java | 10 ++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index 8dd233abf2f..f51069ee0b9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -3,11 +3,13 @@ import edu.harvard.iq.dataverse.branding.BrandingUtil; import edu.harvard.iq.dataverse.feedback.Feedback; import edu.harvard.iq.dataverse.feedback.FeedbackUtil; +import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.MailUtil; import edu.harvard.iq.dataverse.util.SystemConfig; import java.util.List; +import java.util.Optional; import java.util.Random; import java.util.logging.Logger; import javax.ejb.EJB; @@ -140,7 +142,7 @@ public String getMessageTo() { } public String getMessageCC() { - if (ccSupport()) { + if (ccSupport(feedbackTarget)) { return BrandingUtil.getSupportTeamName(systemAddress); } return null; @@ -210,7 +212,7 @@ public String sendMessage() { String installationBrandName = BrandingUtil.getInstallationBrandName(); String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress); - Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName, ccSupport()); + Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName, ccSupport(feedbackTarget)); if (feedback==null) { logger.warning("No feedback has been sent!"); return null; @@ -220,9 +222,11 @@ public String sendMessage() { return null; } - private boolean ccSupport() { - //Setting is enabled and this isn't already a direct message to support (no feedbackObject - return feedbackTarget!=null && JvmSettings.CC_SUPPORT_ON_CONTACT_EMAILS.lookupOptional(Boolean.class, false); + public static boolean ccSupport(DvObject feedbackTarget) { + //Setting is enabled and this isn't already a direct message to support (no feedbackTarget) + Optional ccSupport = JvmSettings.CC_SUPPORT_ON_CONTACT_EMAILS.lookupOptional(Boolean.class); + + return feedbackTarget!=null && ccSupport.isPresent() &&ccSupport.get(); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java index 89fa28ad20b..a1984677043 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java @@ -3,9 +3,11 @@ import edu.harvard.iq.dataverse.DataverseSession; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.MailServiceBean; +import edu.harvard.iq.dataverse.SendFeedbackDialog; import edu.harvard.iq.dataverse.branding.BrandingUtil; import edu.harvard.iq.dataverse.feedback.Feedback; import edu.harvard.iq.dataverse.feedback.FeedbackUtil; +import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.MailUtil; @@ -49,10 +51,7 @@ public Response submitFeedback(JsonObject jsonObject) throws AddressException { } DataverseSession dataverseSession = null; String userMessage = jsonObject.getString("body"); - String systemEmail = settingsSvc.getValueForKey(SettingsServiceBean.Key.SupportEmail); - if(systemEmail==null) { - systemEmail = settingsSvc.getValueForKey(SettingsServiceBean.Key.SystemEmail); - } + String systemEmail = JvmSettings.SUPPORT_EMAIL.lookupOptional().orElse(settingsSvc.getValueForKey(SettingsServiceBean.Key.SystemEmail)); InternetAddress systemAddress = MailUtil.parseSystemAddress(systemEmail); String userEmail = jsonObject.getString("fromEmail"); String messageSubject = jsonObject.getString("subject"); @@ -60,8 +59,7 @@ public Response submitFeedback(JsonObject jsonObject) throws AddressException { String installationBrandName = BrandingUtil.getInstallationBrandName(); String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress); JsonArrayBuilder jab = Json.createArrayBuilder(); - boolean ccSupport=feedbackTarget!=null &&settingsSvc.isTrueForKey(SettingsServiceBean.Key.CCSupportOnContactEmails, false); - Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, ccSupport); + Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, SendFeedbackDialog.ccSupport(feedbackTarget)); jab.add(feedback.toJsonObjectBuilder()); mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody(), null); return ok(jab); From bb7818c392d9c27e9a59e18723734c431f11f799 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Fri, 28 Apr 2023 14:34:30 -0400 Subject: [PATCH 16/35] Remove commented out methods and unused code per review --- .../harvard/iq/dataverse/MailServiceBean.java | 46 +------------------ 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java index 1f678b27abc..2f8c2b6d6b4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java @@ -81,37 +81,6 @@ public class MailServiceBean implements java.io.Serializable { public MailServiceBean() { } -/* public void sendMail(String host, String reply, String to, String subject, String messageText) { - Properties props = System.getProperties(); - props.put("mail.smtp.host", host); - Session session = Session.getDefaultInstance(props, null); - - try { - MimeMessage msg = new MimeMessage(session); - String[] recipientStrings = to.split(","); - InternetAddress[] recipients = new InternetAddress[recipientStrings.length]; - try { - InternetAddress fromAddress = getSystemAddress(); - setContactDelegation(reply, fromAddress); - msg.setFrom(fromAddress); - msg.setReplyTo(new Address[] {new InternetAddress(reply, charset)}); - for (int i = 0; i < recipients.length; i++) { - recipients[i] = new InternetAddress(recipientStrings[i], "", charset); - } - } catch (UnsupportedEncodingException ex) { - logger.severe(ex.getMessage()); - } - msg.setRecipients(Message.RecipientType.TO, recipients); - msg.setSubject(subject, charset); - msg.setText(messageText, charset); - Transport.send(msg, recipients); - } catch (AddressException ae) { - ae.printStackTrace(System.out); - } catch (MessagingException me) { - me.printStackTrace(System.out); - } - } -*/ @Resource(name = "mail/notifyMailSession") private Session session; @@ -177,11 +146,7 @@ public InternetAddress getSystemAddress() { } //@Resource(name="mail/notifyMailSession") -/* public void sendMail(String from, String to, String cc, String subject, String messageText) { - sendMail(from, to, cc, subject, messageText, new HashMap<>()); - } -*/ - public void sendMail(String reply, String to, String cc, String subject, String messageText, Map extraHeaders) { + public void sendMail(String reply, String to, String cc, String subject, String messageText) { try { MimeMessage msg = new MimeMessage(session); // Always send from system address to avoid email being blocked @@ -208,15 +173,6 @@ public void sendMail(String reply, String to, String cc, String subject, String msg.setSubject(subject, charset); msg.setText(messageText, charset); - if (extraHeaders != null) { - for (Object key : extraHeaders.keySet()) { - String headerName = key.toString(); - String headerValue = extraHeaders.get(key).toString(); - - msg.addHeader(headerName, headerValue); - } - } - Transport.send(msg); } catch (AddressException ae) { ae.printStackTrace(System.out); From 6251f7f0bf21ee7fc521d172474a2d680a1c5cd9 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Fri, 28 Apr 2023 14:39:31 -0400 Subject: [PATCH 17/35] remove null param --- src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index f51069ee0b9..93d63742e9a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -218,7 +218,7 @@ public String sendMessage() { return null; } logger.fine("sending feedback: " + feedback); - mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody(), null); + mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody()); return null; } From 8c0cceaf48bad5f2c43e24ebc9f73649227a3beb Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Fri, 28 Apr 2023 14:41:46 -0400 Subject: [PATCH 18/35] and in FeedbackApi --- src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java index a1984677043..53829cf09cc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java @@ -61,7 +61,7 @@ public Response submitFeedback(JsonObject jsonObject) throws AddressException { JsonArrayBuilder jab = Json.createArrayBuilder(); Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, SendFeedbackDialog.ccSupport(feedbackTarget)); jab.add(feedback.toJsonObjectBuilder()); - mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody(), null); + mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody()); return ok(jab); } } From 9d8132a7c999e15bceaedd5c365b8cfa05d18f7b Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Thu, 4 May 2023 17:48:49 -0400 Subject: [PATCH 19/35] fixes/adds per QA --- doc/sphinx-guides/source/api/native-api.rst | 6 +++--- .../java/edu/harvard/iq/dataverse/SendFeedbackDialog.java | 4 +--- .../edu/harvard/iq/dataverse/feedback/FeedbackUtil.java | 2 +- .../java/edu/harvard/iq/dataverse/settings/JvmSettings.java | 2 +- src/main/webapp/contactFormFragment.xhtml | 2 +- .../edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java | 4 ++++ 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 6572512f1ec..e2564ede24e 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1647,7 +1647,7 @@ The fully expanded example above (without environment variables) looks like this 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. -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-emails` will send a copy of these emails to the support email address which would provide a record.) +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.) Link a Dataset ~~~~~~~~~~~~~~ @@ -4521,9 +4521,9 @@ A curl example using an ``ID`` .. code-block:: bash - export SERVER_URL=https://demo.dataverse.org + export SERVER_URL=http://localhost export JSON='{"targetId":24, "subject":"Data Question", "body":"Please help me understand your data. Thank you!", "fromEmail":"dataverseSupport@mailinator.com"}' - curl -X POST -H 'Content-Type:application/json' -d "$JSON" targetId$SERVER_URL/api/admin/feedback + curl -X POST -H 'Content-Type:application/json' -d "$JSON" $SERVER_URL/api/admin/feedback 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`. diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index 93d63742e9a..fb6f5c7ee83 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -8,7 +8,6 @@ import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.MailUtil; import edu.harvard.iq.dataverse.util.SystemConfig; -import java.util.List; import java.util.Optional; import java.util.Random; import java.util.logging.Logger; @@ -94,7 +93,6 @@ public String getUserEmail() { return userEmail; } - @SuppressWarnings("deprecation") public void initUserInput(ActionEvent ae) { userEmail = ""; userMessage = ""; @@ -224,7 +222,7 @@ public String sendMessage() { public static boolean ccSupport(DvObject feedbackTarget) { //Setting is enabled and this isn't already a direct message to support (no feedbackTarget) - Optional ccSupport = JvmSettings.CC_SUPPORT_ON_CONTACT_EMAILS.lookupOptional(Boolean.class); + Optional ccSupport = JvmSettings.CC_SUPPORT_ON_CONTACT_EMAIL.lookupOptional(Boolean.class); return feedbackTarget!=null && ccSupport.isPresent() &&ccSupport.get(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java b/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java index c6bb33136ea..750a3923806 100644 --- a/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java @@ -205,7 +205,7 @@ private static String getContactName(DvObjectContact dvObjectContact) { String name = null; if (contactName != null) { JsonObject entity = PersonOrOrgUtil.getPersonOrOrganization(contactName, false, false); - if (entity.getBoolean("isPerson")) { + if (entity.getBoolean("isPerson") && entity.containsKey("givenName") && entity.containsKey("familyName")) { name = entity.getString("givenName") + " " + entity.getString("familyName"); } else { name = entity.getString("fullName"); diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java index 8fd0c827c67..49fcb7e64f6 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java @@ -109,7 +109,7 @@ public enum JvmSettings { // MAIL SETTINGS SCOPE_MAIL(PREFIX, "mail"), SUPPORT_EMAIL(SCOPE_MAIL, "support-email"), - CC_SUPPORT_ON_CONTACT_EMAILS(SCOPE_MAIL, "cc-support-on-contact-email"), + CC_SUPPORT_ON_CONTACT_EMAIL(SCOPE_MAIL, "cc-support-on-contact-email"), ; diff --git a/src/main/webapp/contactFormFragment.xhtml b/src/main/webapp/contactFormFragment.xhtml index 58f48f73177..51b460814b1 100644 --- a/src/main/webapp/contactFormFragment.xhtml +++ b/src/main/webapp/contactFormFragment.xhtml @@ -19,7 +19,7 @@
-

#{sendFeedbackDialog.getMessageCC()}

+

#{sendFeedbackDialog.getMessageCC(sendFeedbackDialog.getRecipient())}

diff --git a/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java index b22f18ca787..97d7ab45c7f 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/PersonOrOrgUtilTest.java @@ -86,6 +86,10 @@ public void testName() { verifyIsPerson("Francesco", "Francesco", null); // test only family name verifyIsPerson("Cadili", null, null); + + verifyIsPerson("kcjim11, kcjim11", null, null); + + verifyIsPerson("Bartholomew 3, James", "James", "Bartholomew 3"); } private void verifyIsOrganization(String fullName) { From 42b9ff0ea6b7b46ae80ef0ae7d51e9c440749769 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Thu, 4 May 2023 17:55:53 -0400 Subject: [PATCH 20/35] add note about support email needing to be a valid from address --- doc/sphinx-guides/source/installation/config.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index b32d6e6ac95..34b73d80c44 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2353,12 +2353,14 @@ dataverse.mail.support-email ++++++++++++++++++++++++++++ This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address that emails from the Contact Forms and Feedback API are sent from. -This allows configuration of a no-reply email address for :ref:`systemEmail` why allow replies to the SupportEmail address. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email. +This allows configuration of a no-reply email address for :ref:`systemEmail` while allowing replies to the SupportEmail address. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email. ``curl -X PUT -d 'LibraScholar Support Team ' http://localhost:8080/api/admin/settings/:SupportEmail`` 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. +Also note that your mail server must be configured to send email from the support address (as well as the system address). + Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_MAIL_SUPPORT_EMAIL``. .. _dataverse.mail.cc-support-on-contact-emails: From 6de83a60ba67114a486e141249ca2168ac6bca81 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Thu, 4 May 2023 17:59:53 -0400 Subject: [PATCH 21/35] remove bad curl example --- doc/sphinx-guides/source/installation/config.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 34b73d80c44..af7eb3294aa 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2355,8 +2355,6 @@ dataverse.mail.support-email This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address that emails from the Contact Forms and Feedback API are sent from. This allows configuration of a no-reply email address for :ref:`systemEmail` while allowing replies to the SupportEmail address. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email. -``curl -X PUT -d 'LibraScholar Support Team ' http://localhost:8080/api/admin/settings/:SupportEmail`` - 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. Also note that your mail server must be configured to send email from the support address (as well as the system address). From 7f1d46922951441f789a8a9b753386628aa9243c Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Thu, 4 May 2023 18:07:10 -0400 Subject: [PATCH 22/35] not plural --- doc/sphinx-guides/source/installation/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index af7eb3294aa..388b26d63cd 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2361,7 +2361,7 @@ Also note that your mail server must be configured to send email from the suppor Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_MAIL_SUPPORT_EMAIL``. -.. _dataverse.mail.cc-support-on-contact-emails: +.. _dataverse.mail.cc-support-on-contact-email: dataverse.mail.cc-support-on-contact-emails +++++++++++++++++++++++++++++++++++++++++++ From 9b99535f3d23857f2e866101a85e8d68a9d12f5c Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Fri, 5 May 2023 14:11:04 -0400 Subject: [PATCH 23/35] fix missing method --- .../java/edu/harvard/iq/dataverse/SendFeedbackDialog.java | 6 +++--- src/main/webapp/contactFormFragment.xhtml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index fb6f5c7ee83..b4d052c824c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -140,7 +140,7 @@ public String getMessageTo() { } public String getMessageCC() { - if (ccSupport(feedbackTarget)) { + if (ccSupport()) { return BrandingUtil.getSupportTeamName(systemAddress); } return null; @@ -210,7 +210,7 @@ public String sendMessage() { String installationBrandName = BrandingUtil.getInstallationBrandName(); String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress); - Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName, ccSupport(feedbackTarget)); + Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName, ccSupport()); if (feedback==null) { logger.warning("No feedback has been sent!"); return null; @@ -220,7 +220,7 @@ public String sendMessage() { return null; } - public static boolean ccSupport(DvObject feedbackTarget) { + public boolean ccSupport() { //Setting is enabled and this isn't already a direct message to support (no feedbackTarget) Optional ccSupport = JvmSettings.CC_SUPPORT_ON_CONTACT_EMAIL.lookupOptional(Boolean.class); diff --git a/src/main/webapp/contactFormFragment.xhtml b/src/main/webapp/contactFormFragment.xhtml index 51b460814b1..58f48f73177 100644 --- a/src/main/webapp/contactFormFragment.xhtml +++ b/src/main/webapp/contactFormFragment.xhtml @@ -19,7 +19,7 @@
-

#{sendFeedbackDialog.getMessageCC(sendFeedbackDialog.getRecipient())}

+

#{sendFeedbackDialog.getMessageCC()}

From 5727d7ff929d2fd1a1510ecb7a3b7a637620e90e Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 5 May 2023 14:45:25 -0400 Subject: [PATCH 24/35] support both --- .../java/edu/harvard/iq/dataverse/SendFeedbackDialog.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java index b4d052c824c..13a7cc51357 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java +++ b/src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java @@ -221,6 +221,10 @@ public String sendMessage() { } public boolean ccSupport() { + return ccSupport(feedbackTarget); + } + + public static boolean ccSupport(DvObject feedbackTarget) { //Setting is enabled and this isn't already a direct message to support (no feedbackTarget) Optional ccSupport = JvmSettings.CC_SUPPORT_ON_CONTACT_EMAIL.lookupOptional(Boolean.class); From fb30dbf987787fbb66dd1478c713da2a2654fe3e Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 5 May 2023 14:46:25 -0400 Subject: [PATCH 25/35] only show cc when used --- src/main/webapp/contactFormFragment.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/contactFormFragment.xhtml b/src/main/webapp/contactFormFragment.xhtml index 58f48f73177..cb4eb3d0872 100644 --- a/src/main/webapp/contactFormFragment.xhtml +++ b/src/main/webapp/contactFormFragment.xhtml @@ -16,7 +16,7 @@

#{sendFeedbackDialog.getMessageTo()}

-
+

#{sendFeedbackDialog.getMessageCC()}

From efd5ac690e80ca8c80c1c60414bce8094d7fe33a Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 5 May 2023 15:48:17 -0400 Subject: [PATCH 26/35] doc updates per QA --- doc/release-notes/9185-contact-email-updates.md | 2 +- doc/sphinx-guides/source/installation/config.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/release-notes/9185-contact-email-updates.md b/doc/release-notes/9185-contact-email-updates.md index 97216407c8b..f813d010c2c 100644 --- a/doc/release-notes/9185-contact-email-updates.md +++ b/doc/release-notes/9185-contact-email-updates.md @@ -8,5 +8,5 @@ When there are multiple contacts, the system will now send one email with all of ## New JVM/MicroProfile Settings -dataverse.mail.support-email - allows a separate email, distinct from the :SystemEmail to be used as the from address in emails from the contact form/ feedback api. +dataverse.mail.support-email - allows a separate email, distinct from the :SystemEmail to be used as the reply-to address in emails from the contact form/ feedback api. 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. \ No newline at end of file diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 388b26d63cd..7d1ba37a397 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2363,14 +2363,14 @@ Can also be set via any `supported MicroProfile Config API source`_, e.g. the en .. _dataverse.mail.cc-support-on-contact-email: -dataverse.mail.cc-support-on-contact-emails -+++++++++++++++++++++++++++++++++++++++++++ +dataverse.mail.cc-support-on-contact-email +++++++++++++++++++++++++++++++++++++++++++ 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. A CC line is added to the contact form when this setting is true so that users are aware that the cc will occur. The default is false. -Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_MAIL_CC_SUPPORT_ON_CONTACT_EMAILS``. +Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_MAIL_CC_SUPPORT_ON_CONTACT_EMAIL``. .. _feature-flags: From 7e0b0b385ccff9ed5de9462c6a9df25d8a1123fa Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 5 May 2023 16:10:16 -0400 Subject: [PATCH 27/35] fix to not replyto/from --- doc/release-notes/9185-contact-email-updates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes/9185-contact-email-updates.md b/doc/release-notes/9185-contact-email-updates.md index f813d010c2c..3e03461a383 100644 --- a/doc/release-notes/9185-contact-email-updates.md +++ b/doc/release-notes/9185-contact-email-updates.md @@ -8,5 +8,5 @@ When there are multiple contacts, the system will now send one email with all of ## New JVM/MicroProfile Settings -dataverse.mail.support-email - allows a separate email, distinct from the :SystemEmail to be used as the reply-to address in emails from the contact form/ feedback api. +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. 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. \ No newline at end of file From 82c129ff7a598a9065ff3dd4964142495afd70df Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 5 May 2023 16:19:02 -0400 Subject: [PATCH 28/35] fix config docs --- doc/sphinx-guides/source/installation/config.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 7d1ba37a397..e5ef0db4cd6 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2352,13 +2352,11 @@ Can also be set via any `supported MicroProfile Config API source`_, e.g. the en dataverse.mail.support-email ++++++++++++++++++++++++++++ -This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address that emails from the Contact Forms and Feedback API are sent from. -This allows configuration of a no-reply email address for :ref:`systemEmail` while allowing replies to the SupportEmail address. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email. +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. +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. 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. -Also note that your mail server must be configured to send email from the support address (as well as the system address). - Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_MAIL_SUPPORT_EMAIL``. .. _dataverse.mail.cc-support-on-contact-email: From cd2f19684d1574f01494a084a6c48e0caa801106 Mon Sep 17 00:00:00 2001 From: qqmyers Date: Fri, 5 May 2023 16:35:42 -0400 Subject: [PATCH 29/35] tweak api language --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index e2564ede24e..072c39e7c2a 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -4515,7 +4515,7 @@ The call is a POST with a JSON object as input with four keys: - "targetId" - the id of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional) - "subject" - the email subject line - "body" - the email body to send -- "fromEmail" - the email to list in the from field. +- "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.) A curl example using an ``ID`` From 1ced8a1dafd5065c5a5057e5272814dc26f80783 Mon Sep 17 00:00:00 2001 From: Oliver Bertuch Date: Mon, 8 May 2023 08:02:36 +0200 Subject: [PATCH 30/35] ci(ct): do not try image pushes when secrets not available In case of pull requests from forks, a run does not have access to secrets. Thus we check if we can access the secrets, otherwise we skip the pushing part (which will rely on a comment trigger). --- .github/workflows/container_app_push.yml | 64 +++++++++++++++++++++--- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/.github/workflows/container_app_push.yml b/.github/workflows/container_app_push.yml index 2ea184c4f48..6b7c263b869 100644 --- a/.github/workflows/container_app_push.yml +++ b/.github/workflows/container_app_push.yml @@ -18,10 +18,11 @@ env: BASE_IMAGE_TAG: unstable REGISTRY: "" # Empty means default to Docker Hub PLATFORMS: "linux/amd64,linux/arm64" + MASTER_BRANCH_TAG: alpha jobs: build: - name: Build & deploy + name: "Build & Test" runs-on: ubuntu-latest permissions: contents: read @@ -49,12 +50,18 @@ jobs: - name: Build app container image with local architecture run: mvn -Pct package - # Note: Accessing, pushing tags etc. to DockerHub or GHCR will only succeed in upstream because secrets. + # TODO: add smoke / integration testing here - # Run this when triggered via push or schedule as reused workflow from base / maven unit tests - - if: ${{ github.event_name != 'pull_request' && github.ref_name == 'develop' }} - name: Push description to DockerHub - uses: peter-evans/dockerhub-description@v3 + hub-description: + needs: build + name: Push image description to Docker Hub + # Run this when triggered via push or schedule as reused workflow from base / maven unit tests. + # Excluding PRs here means we will have no trouble with secrets access. Also avoid runs in forks. + if: ${{ github.event_name != 'pull_request' && github.ref_name == 'develop' && github.repository_owner == 'IQSS' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: peter-evans/dockerhub-description@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -62,6 +69,45 @@ jobs: short-description: "Dataverse Application Container Image providing the executable" readme-filepath: ./src/main/docker/README.md + # Note: Accessing, pushing tags etc. to DockerHub or GHCR will only succeed in upstream because secrets. + # We check for them here and subsequent jobs can rely on this to decide if they shall run. + check-secrets: + needs: build + name: Check for Secrets Availability + runs-on: ubuntu-latest + outputs: + available: ${{ steps.secret-check.outputs.available }} + steps: + - id: secret-check + # perform secret check & put boolean result as an output + shell: bash + run: | + if [ "${{ secrets.DOCKERHUB_TOKEN }}" != '' ]; then + echo "available=true" >> $GITHUB_OUTPUT; + else + echo "available=false" >> $GITHUB_OUTPUT; + fi + + deploy: + needs: check-secrets + name: "Package & Publish" + runs-on: ubuntu-latest + # Only run this job if we have access to secrets. This is true for events like push/schedule which run in + # context of main repo, but for PRs only true if coming from the main repo! Forks have no secret access. + if: needs.check-secrets.outputs.available == 'true' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + java-version: "11" + distribution: 'adopt' + - uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + # Depending on context, we push to different targets. Login accordingly. - if: ${{ github.event_name != 'pull_request' }} name: Log in to Docker Hub registry uses: docker/login-action@v2 @@ -82,8 +128,8 @@ jobs: - name: Re-set image tag based on branch (if master) if: ${{ github.ref_name == 'master' }} run: | - echo "IMAGE_TAG=alpha" >> $GITHUB_ENV - echo "BASE_IMAGE_TAG=alpha" >> $GITHUB_ENV + echo "IMAGE_TAG=${{ env.MASTER_BRANCH_TAG }}" >> $GITHUB_ENV + echo "BASE_IMAGE_TAG=${{ env.MASTER_BRANCH_TAG }}" >> $GITHUB_ENV - name: Re-set image tag and container registry when on PR if: ${{ github.event_name == 'pull_request' }} run: | @@ -97,6 +143,8 @@ jobs: if: ${{ github.event_name == 'pull_request' }} with: header: app-registry-push + hide_and_recreate: true + hide_classify: "OUTDATED" message: | Pushed preview application image as [`ghcr.io/gdcc/dataverse:${{ env.IMAGE_TAG }}`](https://github.com/orgs/gdcc/packages/container/package/dataverse). Use it by referencing it with its full name as printed above. From 77af5c1083d416a1f8ccf3393c91cdcc5a8e3253 Mon Sep 17 00:00:00 2001 From: Oliver Bertuch Date: Mon, 8 May 2023 08:30:17 +0200 Subject: [PATCH 31/35] ci(ct): make preview image comment nicer to use with copy button --- .github/workflows/container_app_push.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/container_app_push.yml b/.github/workflows/container_app_push.yml index 6b7c263b869..ba9ecc28f47 100644 --- a/.github/workflows/container_app_push.yml +++ b/.github/workflows/container_app_push.yml @@ -146,5 +146,8 @@ jobs: hide_and_recreate: true hide_classify: "OUTDATED" message: | - Pushed preview application image as [`ghcr.io/gdcc/dataverse:${{ env.IMAGE_TAG }}`](https://github.com/orgs/gdcc/packages/container/package/dataverse). - Use it by referencing it with its full name as printed above. + :package: Pushed preview application image as + ``` + ghcr.io/gdcc/dataverse:${{ env.IMAGE_TAG }} + ``` + :ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container/package/dataverse). Use by referencing with full name as printed above, mind the registry name. From 21f18ef6be22699b6c2fd2c6139710435bc34636 Mon Sep 17 00:00:00 2001 From: Oliver Bertuch Date: Mon, 8 May 2023 10:27:45 +0200 Subject: [PATCH 32/35] ci(ct): add preview image push on command workflows --- .github/workflows/container_app_pr.yml | 83 +++++++++++++++++++++++ .github/workflows/pr_comment_commands.yml | 20 ++++++ 2 files changed, 103 insertions(+) create mode 100644 .github/workflows/container_app_pr.yml create mode 100644 .github/workflows/pr_comment_commands.yml diff --git a/.github/workflows/container_app_pr.yml b/.github/workflows/container_app_pr.yml new file mode 100644 index 00000000000..f5aa2d1d9b0 --- /dev/null +++ b/.github/workflows/container_app_pr.yml @@ -0,0 +1,83 @@ +--- +name: Preview Application Container Image + +on: + # We only run the push commands if we are asked to by an issue comment with the correct command. + # This workflow is always taken from the default branch and runs in repo context with access to secrets. + repository_dispatch: + types: [ push-image-command ] + +env: + IMAGE_TAG: unstable + BASE_IMAGE_TAG: unstable + PLATFORMS: "linux/amd64,linux/arm64" + +jobs: + deploy: + name: "Package & Push" + runs-on: ubuntu-latest + # Only run in upstream repo - avoid unnecessary runs in forks + if: ${{ github.repository_owner == 'IQSS' }} + steps: + # Checkout the pull request code as when merged + - uses: actions/checkout@v3 + with: + ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge' + - uses: actions/setup-java@v3 + with: + java-version: "11" + distribution: 'adopt' + - uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + # Note: Accessing, pushing tags etc. to GHCR will only succeed in upstream because secrets. + - name: Login to Github Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ secrets.GHCR_USERNAME }} + password: ${{ secrets.GHCR_TOKEN }} + + - name: Set up QEMU for multi-arch builds + uses: docker/setup-qemu-action@v2 + + # Get the image tag from either the command or default to branch name (Not used for now) + #- name: Get the target tag name + # id: vars + # run: | + # tag=${{ github.event.client_payload.slash_command.args.named.tag }} + # if [[ -z "$tag" ]]; then tag=$(echo "${{ github.event.client_payload.pull_request.head.ref }}" | tr '\\/_:&+,;#*' '-'); fi + # echo "IMAGE_TAG=$tag" >> $GITHUB_ENV + + # Set image tag to branch name of the PR + - name: Set image tag to branch name + run: | + echo "IMAGE_TAG=$(echo "${{ github.event.client_payload.pull_request.head.ref }}" | tr '\\/_:&+,;#*' '-')" >> $GITHUB_ENV + + - name: Deploy multi-arch application container image + run: mvn -Pct deploy -Dapp.image.tag=${{ env.IMAGE_TAG }} -Dbase.image.tag=${{ env.BASE_IMAGE_TAG }} -Ddocker.registry=ghcr.io -Ddocker.platforms=${{ env.PLATFORMS }} + + - uses: marocchino/sticky-pull-request-comment@v2 + with: + header: app-registry-push + hide_and_recreate: true + hide_classify: "OUTDATED" + number: ${{ github.event.client_payload.pull_request.number }} + message: | + :package: Pushed preview application image as + ``` + ghcr.io/gdcc/dataverse:${{ env.IMAGE_TAG }} + ``` + :ship: [See on GHCR](https://github.com/orgs/gdcc/packages/container/package/dataverse). Use by referencing with full name as printed above, mind the registry name. + + # Leave a note when things have gone sideways + - uses: peter-evans/create-or-update-comment@v3 + if: ${{ failure() }} + with: + issue-number: ${{ github.event.client_payload.pull_request.number }} + body: > + :package: Could not push preview image :disappointed:. + See [log](https://github.com/IQSS/dataverse/actions/runs/${{ github.run_id }}) for details. \ No newline at end of file diff --git a/.github/workflows/pr_comment_commands.yml b/.github/workflows/pr_comment_commands.yml new file mode 100644 index 00000000000..5ff75def623 --- /dev/null +++ b/.github/workflows/pr_comment_commands.yml @@ -0,0 +1,20 @@ +name: PR Comment Commands +on: + issue_comment: + types: [created] +jobs: + dispatch: + # Avoid being triggered by forks in upstream + if: ${{ github.repository_owner == 'IQSS' }} + runs-on: ubuntu-latest + steps: + - name: Dispatch + uses: peter-evans/slash-command-dispatch@v3 + with: + # This token belongs to @dataversebot and has sufficient scope. + token: ${{ secrets.GHCR_TOKEN }} + commands: | + push-image + repository: IQSS/dataverse + # Commenter must have at least write permission to repo to trigger dispatch + permission: write From c802b804369aecffe72db04a8671b3b7d474bdc7 Mon Sep 17 00:00:00 2001 From: Oliver Bertuch Date: Mon, 8 May 2023 13:40:08 +0200 Subject: [PATCH 33/35] ci(docs): fix RTFD build by adding a config file for it --- .readthedocs.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000000..90dba930c61 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,19 @@ +version: 2 + +# HTML is always built, these are additional formats only +formats: + - pdf + +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +python: + install: + requirements: doc/sphinx-guides/requirements.txt + + +sphinx: + configuration: doc/sphinx-guides/source/conf.py + fail_on_warning: true From b9c7395e0a48a2cc5086c80fe2e36370aea1ecdb Mon Sep 17 00:00:00 2001 From: Oliver Bertuch Date: Mon, 8 May 2023 13:42:04 +0200 Subject: [PATCH 34/35] fix(docs): make RTFD installs a list --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 90dba930c61..2e0a57ccca1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,7 +11,7 @@ build: python: install: - requirements: doc/sphinx-guides/requirements.txt + - requirements: doc/sphinx-guides/requirements.txt sphinx: From 96ca3c5a9f5d26b2c1c448088e8fe2317d131781 Mon Sep 17 00:00:00 2001 From: Oliver Bertuch Date: Mon, 8 May 2023 13:48:55 +0200 Subject: [PATCH 35/35] ci(docs): make RTFD install graphviz, too --- .readthedocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 2e0a57ccca1..cadaedc1448 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -8,6 +8,8 @@ build: os: ubuntu-22.04 tools: python: "3.10" + apt_packages: + - graphviz python: install: