Skip to content

Commit

Permalink
[RHCLOUD-29776] Introduce recipients limit per email (RedHatInsights#…
Browse files Browse the repository at this point in the history
…2377)


Co-authored-by: Gwenneg Lepage <[email protected]>
  • Loading branch information
g-duval and gwenneg authored Dec 5, 2023
1 parent dcf37f2 commit e831203
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 103 deletions.
6 changes: 5 additions & 1 deletion .rhcicd/clowdapp-connector-email.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ objects:
value: ${BACKOFFICE_CLIENT_ENV}
- name: NOTIFICATIONS_CONNECTOR_USER_PROVIDER_BOP_URL
value: ${BACKOFFICE_SCHEME}://${BACKOFFICE_HOST}
- name: NOTIFICATIONS_CONNECTOR_MAX_RECIPIENTS_PER_EMAIL
value: ${NOTIFICATIONS_CONNECTOR_MAX_RECIPIENTS_PER_EMAIL}
- name: QUARKUS_HTTP_PORT
value: ${QUARKUS_HTTP_PORT}
- name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL
Expand Down Expand Up @@ -191,7 +193,9 @@ parameters:
- name: NOTIFICATIONS_LOG_LEVEL
description: Log level of Notifications
value: INFO

- name: NOTIFICATIONS_CONNECTOR_MAX_RECIPIENTS_PER_EMAIL
description: If an email has more recipients (to/cc/bcc) than this value, it will be split into several emails with the same content but fewer recipients
value: "50"
- name: QUARKUS_HTTP_PORT
description: Quarkus HTTP server port, defaulting to the default Clowder private port
value: "9000"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.redhat.cloud.notifications.connector.EngineToConnectorRouteBuilder;
import com.redhat.cloud.notifications.connector.email.config.EmailConnectorConfig;
import com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty;
import com.redhat.cloud.notifications.connector.email.constants.Routes;
import com.redhat.cloud.notifications.connector.email.metrics.EmailMetricsProcessor;
import com.redhat.cloud.notifications.connector.email.processors.bop.BOPRequestPreparer;
Expand All @@ -18,7 +19,6 @@
import static com.redhat.cloud.notifications.connector.ConnectorToEngineRouteBuilder.SUCCESS;
import static com.redhat.cloud.notifications.connector.ExchangeProperty.ID;
import static com.redhat.cloud.notifications.connector.ExchangeProperty.ORG_ID;
import static com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty.EMAIL_RECIPIENTS;
import static com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty.FILTERED_USERS;
import static com.redhat.cloud.notifications.connector.http.SslTrustAllManager.getSslContextParameters;
import static org.apache.camel.LoggingLevel.INFO;
Expand Down Expand Up @@ -65,28 +65,31 @@ public void configureRoutes() {
.process(recipientsResolverRequestPreparer)
.to(emailConnectorConfig.getRecipientsResolverServiceURL() + "/internal/recipients-resolver")
.process(recipientsResolverResponseProcessor)
.to(direct(Routes.SEND_EMAIL_BOP));
.choice().when(shouldSkipEmail())
.log(INFO, getClass().getName(), "Skipped Email notification because the recipients list was empty [orgId=${exchangeProperty." + ORG_ID + "}, historyId=${exchangeProperty." + ID + "}]")
.otherwise()
.to(direct(Routes.SPLIT_AND_SEND))
.end()
.to(direct(SUCCESS));

from(direct(Routes.SPLIT_AND_SEND))
.routeId(Routes.SPLIT_AND_SEND)
.split(simpleF("${exchangeProperty.%s}", ExchangeProperty.FILTERED_USERS))
.to(direct(Routes.SEND_EMAIL_BOP))
.end();

from(direct(Routes.SEND_EMAIL_BOP))
.routeId(Routes.SEND_EMAIL_BOP)
.choice()
.when(shouldSkipEmail())
// TODO Lower this log level to DEBUG later.
.log(INFO, getClass().getName(), "Skipped Email notification because the recipients list was empty [orgId=${exchangeProperty." + ORG_ID + "}, historyId=${exchangeProperty." + ID + "}]")
.otherwise()
// Clear all the headers that may come from the previous route.
.removeHeaders("*")
.process(this.BOPRequestPreparer)
.to(bopEndpoint)
.log(INFO, getClass().getName(), "Sent Email notification [orgId=${exchangeProperty." + ORG_ID + "}, historyId=${exchangeProperty." + ID + "}]")
.process(emailMetricsProcessor)
.end()
.to(direct(SUCCESS));
// Clear all the headers that may come from the previous route.
.removeHeaders("*")
.process(this.BOPRequestPreparer)
.to(bopEndpoint)
.log(INFO, getClass().getName(), "Sent Email notification [orgId=${exchangeProperty." + ORG_ID + "}, historyId=${exchangeProperty." + ID + "}]")
.process(emailMetricsProcessor);
}

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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class EmailConnectorConfig extends HttpConnectorConfig {
private static final String BOP_ENV = "notifications.connector.user-provider.bop.env";
private static final String BOP_URL = "notifications.connector.user-provider.bop.url";

private static final String MAX_RECIPIENTS_PER_EMAIL = "notifications.connector.max-recipients-per-email";

private static final String RECIPIENTS_RESOLVER_USER_SERVICE_URL = "notifications.connector.recipients-resolver.url";

@ConfigProperty(name = BOP_API_TOKEN)
Expand All @@ -38,6 +40,9 @@ public class EmailConnectorConfig extends HttpConnectorConfig {
@ConfigProperty(name = RECIPIENTS_RESOLVER_USER_SERVICE_URL)
String recipientsResolverServiceURL;

@ConfigProperty(name = MAX_RECIPIENTS_PER_EMAIL, defaultValue = "50")
int maxRecipientsPerEmail;

@Override
protected Map<String, Object> getLoggedConfiguration() {
Map<String, Object> config = super.getLoggedConfiguration();
Expand All @@ -50,6 +55,7 @@ protected Map<String, Object> getLoggedConfiguration() {
config.put(BOP_ENV, this.bopEnv);
config.put(BOP_URL, this.bopURL);
config.put(RECIPIENTS_RESOLVER_USER_SERVICE_URL, recipientsResolverServiceURL);
config.put(MAX_RECIPIENTS_PER_EMAIL, maxRecipientsPerEmail);

/*
* /!\ WARNING /!\
Expand Down Expand Up @@ -78,4 +84,8 @@ public String getBopURL() {
public String getRecipientsResolverServiceURL() {
return recipientsResolverServiceURL;
}

public int getMaxRecipientsPerEmail() {
return maxRecipientsPerEmail;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
public class Routes {

public static final String SEND_EMAIL_BOP = "send-email-bop";

public static final String SPLIT_AND_SEND = "split-and-send";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty;
import com.redhat.cloud.notifications.connector.email.model.bop.Email;
import com.redhat.cloud.notifications.connector.email.model.bop.SendEmailsRequest;
import com.redhat.cloud.notifications.connector.email.model.settings.User;
import io.vertx.core.json.JsonObject;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
Expand All @@ -14,8 +13,6 @@

import java.util.Set;

import static java.util.stream.Collectors.toSet;

@ApplicationScoped
public class BOPRequestPreparer implements Processor {

Expand All @@ -30,12 +27,7 @@ public class BOPRequestPreparer implements Processor {
public void process(final Exchange exchange) {
final String subject = exchange.getProperty(ExchangeProperty.RENDERED_SUBJECT, String.class);
final String body = exchange.getProperty(ExchangeProperty.RENDERED_BODY, String.class);

final Set<String> recipients;
final Set<User> users = exchange.getProperty(ExchangeProperty.FILTERED_USERS, Set.class);
recipients = users.stream().map(User::getEmail).collect(toSet());
Set<String> emails = exchange.getProperty(ExchangeProperty.EMAIL_RECIPIENTS, Set.class);
recipients.addAll(emails);
Set<String> recipients = exchange.getMessage().getBody(Set.class);
exchange.setProperty(ExchangeProperty.RECIPIENTS_SIZE, recipients.size());

// Prepare the email to be sent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.redhat.cloud.notifications.connector.email.config.EmailConnectorConfig;
import com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty;
import com.redhat.cloud.notifications.connector.email.model.settings.User;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toSet;

@ApplicationScoped
public class RecipientsResolverResponseProcessor implements Processor {

@Inject
ObjectMapper objectMapper;

@Inject
EmailConnectorConfig emailConnectorConfig;

/**
* Processes the response from the recipients-resolver service. Grabs the users from the
* response.
Expand All @@ -28,8 +36,20 @@ public class RecipientsResolverResponseProcessor implements Processor {
@Override
public void process(final Exchange exchange) throws JsonProcessingException {
final String body = exchange.getMessage().getBody(String.class);
final List<User> recipientsList = Arrays.asList(this.objectMapper.readValue(body, User[].class));
final Set<String> recipientsList = Arrays.asList(this.objectMapper.readValue(body, User[].class))
.stream().map(User::getEmail).collect(toSet());

Set<String> emails = exchange.getProperty(ExchangeProperty.EMAIL_RECIPIENTS, Set.class);
recipientsList.addAll(emails);

// We have to remove one from the limit, because a default recipient (like [email protected]) will be automatically added
exchange.setProperty(ExchangeProperty.FILTERED_USERS, partition(recipientsList, emailConnectorConfig.getMaxRecipientsPerEmail() - 1));
}

exchange.setProperty(ExchangeProperty.FILTERED_USERS, new HashSet<>(recipientsList));
private static Set<List<String>> partition(Set<String> collection, int n) {
AtomicInteger counter = new AtomicInteger();
return collection.stream()
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / n))
.values().stream().collect(Collectors.toSet());
}
}
1 change: 1 addition & 0 deletions connector-email/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ notifications.connector.user-provider.bop.env=changeme
notifications.connector.user-provider.bop.url=https://backoffice-proxy.apps.ext.spoke.preprod.us-west-2.aws.paas.redhat.com

notifications.connector.recipients-resolver.url=${clowder.endpoints.notifications-recipients-resolver-service.url:http://localhost:9008}
%test.notifications.connector.max-recipients-per-email=4
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import com.redhat.cloud.notifications.connector.email.config.EmailConnectorConfig;
import com.redhat.cloud.notifications.connector.http.SslTrustAllManager;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import jakarta.inject.Inject;
import org.apache.camel.Endpoint;
import org.apache.camel.quarkus.test.CamelQuarkusTestSupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

@QuarkusTest
@TestProfile(EmailRouteBuilderTest.class)
public class EmailRouteBuilderTest extends CamelQuarkusTestSupport {
@Inject
EmailConnectorConfig emailConnectorConfig;
Expand Down

This file was deleted.

Loading

0 comments on commit e831203

Please sign in to comment.