Skip to content

Commit

Permalink
RHCLOUD-29047 | feature: specify sender and default recipient (RedHa…
Browse files Browse the repository at this point in the history
…tInsights#2328)


Co-authored-by: Gwenneg Lepage <[email protected]>
  • Loading branch information
MikelAlejoBR and gwenneg authored Nov 15, 2023
1 parent 299199b commit 927c01e
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ public void extract(final Exchange exchange, final JsonObject cloudEventData) {
exchange.setProperty(ExchangeProperty.SUBSCRIBERS, subscribers);
exchange.setProperty(ExchangeProperty.UNSUBSCRIBERS, unsubscribers);
exchange.setProperty(ExchangeProperty.EMAIL_RECIPIENTS, emails);
exchange.setProperty(ExchangeProperty.EMAIL_SENDER, cloudEventData.getString("email_sender"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ public void configureRoute() throws Exception {

private Predicate shouldSkipEmail() {
return exchange -> exchange.getProperty(FILTERED_USERS, Set.class).isEmpty() &&
(!emailConnectorConfig.isSkipBopUsersResolution() ||
exchange.getProperty(EMAIL_RECIPIENTS, Set.class).isEmpty());
(!emailConnectorConfig.isSkipBopUsersResolution() ||
exchange.getProperty(EMAIL_RECIPIENTS, Set.class).isEmpty());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ public class ExchangeProperty {
* retrieval page.
*/
public static final String ELEMENTS_COUNT = "elements_count";
/**
* Email recipients initially included in the payload received by notifications-engine.
* The subscriptions of these recipients are not checked.
* They are simply added to the list of recipients retrieved from notifications-recipients-resolver.
*/
public static final String EMAIL_RECIPIENTS = "email_recipients";
/**
* Holds the email's sender that will be specified for sending the email.
*/
public static final String EMAIL_SENDER = "email_sender";
/**
* Holds the filtered users. It is used in order to avoid the set of
* cached users from being modified.
Expand Down Expand Up @@ -60,10 +70,4 @@ public class ExchangeProperty {
* notification through email.
*/
public static final String USERS = "users";
/**
* Email recipients initially included in the payload received by notifications-engine.
* The subscriptions of these recipients are not checked.
* They are simply added to the list of recipients retrieved from notifications-recipients-resolver.
*/
public static final String EMAIL_RECIPIENTS = "email_recipients";
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.redhat.cloud.notifications.connector.email.model.bop;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;

import java.util.HashSet;
import java.util.Set;

import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;

@JsonAutoDetect(fieldVisibility = ANY)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SendEmailsRequest {

private final Set<Email> emails = new HashSet<>();
private final boolean skipUsersResolution = true;
private final String emailSender;
private final String defaultRecipient;

public void addEmail(Email email) {
emails.add(email);
public SendEmailsRequest(final Set<Email> emails, final String emailSender, final String defaultRecipient) {
this.emails.addAll(emails);
this.emailSender = emailSender;
this.defaultRecipient = defaultRecipient;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public void process(final Exchange exchange) {
recipients = users.stream().map(User::getUsername).collect(toSet());
}

// Prepare the email to be sent.
final Email email = new Email(
subject,
body,
Expand All @@ -50,8 +51,11 @@ public void process(final Exchange exchange) {

JsonObject bopBody;
if (emailConnectorConfig.isSkipBopUsersResolution()) {
final SendEmailsRequest request = new SendEmailsRequest();
request.addEmail(email);
final SendEmailsRequest request = new SendEmailsRequest(
Set.of(email),
exchange.getProperty(ExchangeProperty.EMAIL_SENDER, String.class),
exchange.getProperty(ExchangeProperty.EMAIL_SENDER, String.class)
);
bopBody = JsonObject.mapFrom(request);
} else {
final Emails emails = new Emails();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.UUID;

import static com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty.EMAIL_RECIPIENTS;
import static com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty.EMAIL_SENDER;
import static com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty.RECIPIENT_SETTINGS;
import static com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty.RENDERED_BODY;
import static com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty.RENDERED_SUBJECT;
Expand Down Expand Up @@ -73,13 +74,16 @@ void testExtract() {
final String emailBody = "fake email body";
final String emailSubject = "fake email subject";

final String emailSender = "\"Red Hat Insights\" [email protected]";

// Prepare the JSON object.
final JsonObject payload = new JsonObject();
payload.put("recipient_settings", recipientSettingsList);
payload.put("subscribers", subscribers);
payload.put("unsubscribers", unsubscribers);
payload.put("email_body", emailBody);
payload.put("email_subject", emailSubject);
payload.put("email_sender", emailSender);
payload.put("subscribed_by_default", true);

final Exchange exchange = this.createExchangeWithBody("");
Expand All @@ -96,6 +100,7 @@ void testExtract() {
assertEquals(subscribers, exchange.getProperty(SUBSCRIBERS, Set.class));
assertEquals(unsubscribers, exchange.getProperty(UNSUBSCRIBERS, Set.class));
assertEquals(Set.of("[email protected]", "[email protected]", "[email protected]"), exchange.getProperty(EMAIL_RECIPIENTS, Set.class));
assertEquals(emailSender, exchange.getProperty(EMAIL_SENDER, String.class));
assertTrue(exchange.getProperty(SUBSCRIBED_BY_DEFAULT, boolean.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ void testProcess(boolean skipBopUsersResolution) {
if (skipBopUsersResolution) {
assertEquals(5, actualEmail.getJsonArray("bccList").size());
assertTrue(Set.of("a-email", "b-email", "c-email", "[email protected]", "[email protected]").stream()
.allMatch(bcc -> actualEmail.getJsonArray("bccList").contains(bcc)));
.allMatch(bcc -> actualEmail.getJsonArray("bccList").contains(bcc)));
} else {
assertEquals(3, actualEmail.getJsonArray("bccList").size());
assertTrue(Set.of("a", "b", "c").stream()
.allMatch(bcc -> actualEmail.getJsonArray("bccList").contains(bcc)));
.allMatch(bcc -> actualEmail.getJsonArray("bccList").contains(bcc)));
assertTrue(Set.of("[email protected]", "[email protected]").stream()
.noneMatch(bcc -> actualEmail.getJsonArray("bccList").contains(bcc)));
.noneMatch(bcc -> actualEmail.getJsonArray("bccList").contains(bcc)));
}
assertEquals("html", actualEmail.getString("bodyType"));
if (skipBopUsersResolution) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.redhat.cloud.notifications.processors.email;

import com.redhat.cloud.notifications.models.Event;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class EmailActorsResolver {
/**
* Standard "Red Hat Insights" sender that the vast majority of the
* ConsoleDot applications will use.
*/
public static final String RH_INSIGHTS_SENDER = "\"Red Hat Insights\" [email protected]";

/**
* Determines which sender should be set in the email from the given event.
* When sending emails we will use the sender for both the sender itself
* and the default recipient —the one that appears in the "to" field—.
* @param event the event to determine the sender and the default
* recipients from.
* @return the sender that should be used for the given event.
*/
public String getEmailSender(final Event event) {
return RH_INSIGHTS_SENDER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public class EmailProcessor extends SystemEndpointTypeProcessor {
@Inject
EndpointRepository endpointRepository;

@Inject
EmailActorsResolver emailActorsResolver;

@Inject
EmailSubscriptionTypeProcessor emailSubscriptionTypeProcessor;

Expand All @@ -47,7 +50,6 @@ public class EmailProcessor extends SystemEndpointTypeProcessor {

@Override
public void process(final Event event, final List<Endpoint> endpoints) {

// Generate an aggregation if the event supports it.
this.emailSubscriptionTypeProcessor.generateAggregationWhereDue(event);

Expand Down Expand Up @@ -101,6 +103,7 @@ public void process(final Event event, final List<Endpoint> endpoints) {
final EmailNotification emailNotification = new EmailNotification(
body,
subject,
this.emailActorsResolver.getEmailSender(event),
event.getOrgId(),
recipientSettings,
subscribers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public class EmailSubscriptionTypeProcessor extends SystemEndpointTypeProcessor
@Inject
BaseTransformer baseTransformer;

@Inject
EmailActorsResolver emailActorsResolver;

@Inject
EmailSender emailSender;

Expand Down Expand Up @@ -325,7 +328,9 @@ private void processAggregateEmailsByAggregationKey(AggregationCommand aggregati

// Prepare all the data to be sent to the connector.
final EmailNotification emailNotification = new EmailNotification(
bodyStr, subjectStr,
bodyStr,
subjectStr,
this.emailActorsResolver.getEmailSender(event),
event.getOrgId(),
recipientSettings,
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,29 @@

/**
* Represents the data structure that the email connector is expecting.
* @param emailBody the rendered body of the email to be sent.
* @param emailSubject the rendered subject of the email to be sent.
* @param orgId the organization ID associated with the triggered
* event.
* @param recipientSettings the collection of recipient settings extracted from
* both the event and the related endpoints to the
* event.
* @param subscribers the list of usernames who subscribed to the event type.
* @param unsubscribers the list of usernames who unsubscribed from the event type.
* @param subscribedByDefault true if the event type is subscribed by default.
* @param emailBody the rendered body of the email to be sent.
* @param emailSubject the rendered subject of the email to be sent.
* @param emailSender the sender that will appear in the email when
* the user receives it.
* @param orgId the organization ID associated with the
* triggered event.
* @param recipientSettings the collection of recipient settings extracted
* from both the event and the related endpoints
* to the event.
* @param subscribers the list of usernames who subscribed to the
* event type.
* @param unsubscribers the list of usernames who unsubscribed from the
* event type.
* @param subscribedByDefault true if the event type is subscribed by
* default.
*/
public record EmailNotification(
@JsonProperty("email_body") String emailBody,
@JsonProperty("email_subject") String emailSubject,
@JsonProperty("orgId") String orgId,
@JsonProperty("recipient_settings") Collection<RecipientSettings> recipientSettings,
@JsonProperty("subscribers") Collection<String> subscribers,
@JsonProperty("unsubscribers") Collection<String> unsubscribers,
@JsonProperty("subscribed_by_default") boolean subscribedByDefault
@JsonProperty("email_body") String emailBody,
@JsonProperty("email_subject") String emailSubject,
@JsonProperty("email_sender") String emailSender,
@JsonProperty("orgId") String orgId,
@JsonProperty("recipient_settings") Collection<RecipientSettings> recipientSettings,
@JsonProperty("subscribers") Collection<String> subscribers,
@JsonProperty("unsubscribers") Collection<String> unsubscribers,
@JsonProperty("subscribed_by_default") boolean subscribedByDefault
) { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.redhat.cloud.notifications.processors.email;

import com.redhat.cloud.notifications.models.Event;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

@QuarkusTest
public class EmailActorsResolverTest {
@Inject
EmailActorsResolver emailActorsResolver;

/**
* Tests that the default "RH Insights" sender is returned for every event.
*/
@Test
void testGetEmailSender() {
Assertions.assertEquals(EmailActorsResolver.RH_INSIGHTS_SENDER, this.emailActorsResolver.getEmailSender(new Event()), "unexpected email sender returned from the function under test");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public class EmailProcessorTest {
@InjectMock
ConnectorSender connectorSender;

@InjectMock
EmailActorsResolver emailActorsResolver;

@Inject
EmailProcessor emailProcessor;

Expand Down Expand Up @@ -340,6 +343,10 @@ void testSuccess() {
endpoint.setId(UUID.randomUUID());
Mockito.when(this.endpointRepository.getOrCreateDefaultSystemSubscription(event.getAccountId(), event.getOrgId(), EndpointType.EMAIL_SUBSCRIPTION)).thenReturn(endpoint);

// Mock the sender and the default recipients of the email
final String stubbedSender = "Red Hat Insights [email protected]";
Mockito.when(this.emailActorsResolver.getEmailSender(Mockito.any())).thenReturn(stubbedSender);

// Call the processor under test.
this.emailProcessor.process(event, endpoints);

Expand Down Expand Up @@ -368,6 +375,7 @@ void testSuccess() {
final String resultEmailBody = payload.getString("email_body");
final String resultEmailSubject = payload.getString("email_subject");
final String resultOrgId = payload.getString("orgId");
final String resultEmailSender = payload.getString("email_sender");
final Set<String> resultSubscribers = payload.getJsonArray("subscribers").stream().map(String.class::cast).collect(toSet());
final Set<RecipientSettings> resultRecipientSettings = payload.getJsonArray("recipient_settings")
.stream()
Expand All @@ -386,6 +394,7 @@ void testSuccess() {

Assertions.assertEquals(stubbedRenderedBody, resultEmailBody, "the rendered email's body from the email notification does not match the stubbed email body");
Assertions.assertEquals(stubbedRenderedSubject, resultEmailSubject, "the rendered email's subject from the email notification does not match the stubbed email subject");
Assertions.assertEquals(stubbedSender, resultEmailSender, "the rendered email's sender from the email notification does not match the stubbed sender");
Assertions.assertEquals(event.getOrgId(), resultOrgId, "the organization ID from the email notification does not match the one set in the stubbed event");
Assertions.assertEquals(Set.copyOf(subscribers), resultSubscribers, "the subscribers set in the email notification do not match the stubbed ones");

Expand Down

0 comments on commit 927c01e

Please sign in to comment.