-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Send optional notifications via email #474
Conversation
We would appreciate it if you could provide us with more info about this issue/pr! It will help solve the problem faster. |
f032f80
to
95c333c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this. It mostly looks good to me. Please find a few comments below.
We should also have this configured in the protocol per assessment (or through the notification endpoint so the app can enable/disable it in the future). For now, I have pushed a quick version of this; please take a look and feel free to make any changes.
@@ -40,6 +40,18 @@ spring.quartz.properties.auto-startup=true | |||
#spring.quartz.properties.org.quartz.jobStore.useProperties = true | |||
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate | |||
|
|||
# EMAIL SETTINGS | |||
# needed when notifications via email are enabled | |||
notification.email.enabled=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please disable by default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, indeed. Changed as per your suggestion.
@@ -64,6 +64,14 @@ logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr( | |||
# Firebase Cloud Messaging | |||
fcmserver.fcmsender=org.radarbase.fcm.downstream.AdminSdkFcmSender | |||
|
|||
# EMAIL SETTINGS | |||
# needed when notifications via email are enabled | |||
notification.email.enabled=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please disable by default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, can you add radar.notification.email.enabled=true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed!
"The option to send notifications via email is enabled. " + | ||
"Email Address is required for sending email notifications. " + | ||
"Please provide a valid email address."); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should be a requirement for adding a user, as this can be added later using the update endpoint. Moreover, I am adding the ability to customise email sending per assessment block in the protocol as I don't like enabling this for the whole deployment, so this can be disabled for some tasks/projects even when the environment property is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a warn log should be enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, good point. I did not know about the update endpoint. I will log a warning instead.
// Note: at present there are FcmTransmitter and EmailNotificationTransmitter. | ||
// Only the Fcm transmitter (sneakily) emits Exceptions caught by the JobExecutionException. | ||
// As a result Fcm notifications are leading the job success/failure status. | ||
notificationTransmitters.forEach(t -> t.send(notification)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not add another try/catch here (and for data one, too), so it can progress through the rest of the transmitters even if one fails? You can initialise a variable (List<Exception> exceptions
) before the for loop and add all the exceptions to it during the processing without breaking the loop and then in finally
block check if there were any exceptions during the processing and raise the throw new JobExecutionException(e);
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reworked the error handling logic as per your suggestion. Can you confirm this solution is inline with you idea?
|
||
private static SimpleMailMessage createEmailFromNotification(Notification notification) { | ||
SimpleMailMessage message = new SimpleMailMessage(); | ||
message.setFrom(notification.getUser().getEmailAddress()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be the from
value specified in the spring properties?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch, indeed. Fixed...
# EMAIL SETTINGS | ||
# needed when notifications via email are enabled | ||
notification.email.enabled=true | ||
[email protected] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly [email protected]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed!
@@ -61,13 +60,13 @@ public class MessageJob implements Job { | |||
private final transient UserService userService; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i presume this is not needed in this class anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I removed this reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes. Looks nice—just a few minor final comments.
notification.getUser().getSubjectId()); | ||
return; | ||
} | ||
if (notification.isEmailEnabled()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need this if statement for enabling email per assessment through the protocol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I do not understand what you did with the protocol. I cannot find the place where the NotificationProtocol is informed whether to use or not use email.
I have the feeling that this protocol-based mechanism is a parallel track next to the conditional bean creation of the EmailTranmitterService:
@Component
@ConditionalOnProperty(value = "radar.notification.email.enabled", havingValue = "true")
public class EmailNotificationTransmitter implements NotificationTransmitter {
And would this be not a more correct implementation if I understand your proposal well?
@Override
public void send(Notification notification) throws EmailMessageTransmitException {
if (notification.isEmailEnabled()) {
if (notification.getUser().getEmailAddress() == null || notification.getUser().getEmailAddress().isBlank()) {
log.warn("Could not transmit a notification via email because subject {} has no email address.",
notification.getUser().getSubjectId());
return;
}
try {
emailSender.send(createEmailFromNotification(notification));
} catch (Exception e) {
log.error("Could not transmit a notification via email", e);
throw new EmailMessageTransmitException("Could not transmit a notification via email", e);
}
}
}
Note the isEmailEnabled check as first thing in the block?
I am fine with readding this line, but I also would like to make sure that I understand it and change the other code accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I do not understand what you did with the protocol. I cannot find the place where the NotificationProtocol is informed whether to use or not use email.
Apologies, my bad. I should have included a rationale with the code. The information about whether to use email will come from the project protocol. This will be in the notifications key (e.g. here) in each assessment's protocol block.
This is to make the email configuration more granular than enabling it to be deployment-wide. This is because of these reasons -
- Each project may not want this functionality. With protocols, this can be enabled/disabled per project.
- Within a project, some assessments might be prioritised; a requirement could be that email should only be sent for these priority assessments; others can have disabled email and just have push notifications.
- Multiple assessments could be scheduled simultaneously in the protocol with the same notification message and title. However, only one notification is usually required for these assessments (to avoid bombarding the user with superfluous notifications). This is achieved by the protocol's Notification block, where we can disable notifications for similar tasks and leave only one enabled (this was recently implemented in Notification scheduling updates #458). A similar arrangement should be made for email notifications.
We are moving task and protocol scheduling from the aRMT app to the AppServer, so if you are still using scheduling in the app, you might need a minor update there. This would involve reading the new email block in the protocols, and passing this info (emailEnabled
) when adding notifications to the Appserver (the endpoint should support this as I have also updated the DTO).
Alternatively, you can enable the scheduling through the Appserver using a remote config property, which @mpgxvii can confirm. The Appsever automatically reads the protocol files and schedules tasks and notifications for all the users in the database.
Note the isEmailEnabled check as first thing in the block?
Yes, you are correct. That is a better approach. If the email is not enabled for this notification (signified by the assessment in the protocol), then we don't execute the email-sending code. You can log a debug message if it was not executed due to emailEnabled=false, which can help debug issues with email sending.
I hope this was clear, but if not, please feel free to Slack me, and we can discuss this further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reintroduced the notification.isEmailEnabled()
conditional. We decided to keep both the notification-level and application-level configuration option to enable notifications via email.
|
||
// Here handle the exceptions that occurred while transmitting the message via the | ||
// transmitters. At present, only the FcmTransmitter affects the job execution state. | ||
if (exceptions.stream().anyMatch(e -> e instanceof FcmMessageTransmitException)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also keep the top-level try and catch (in case the error is somewhere outside the inner try/catch) and run this code in the finally
block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reintroduced the top level try-catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PMD complains about throwing in a finally block. Now that we have the top-level try/catch, we don't really need the finally block, so I will remove it.
|
||
@Override | ||
public void send(Notification notification) throws EmailMessageTransmitException { | ||
if (notification.getUser().getEmailAddress() == null || notification.getUser().getEmailAddress().isBlank()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can move try to the top here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
try { | ||
fcmSender.send(createMessageFromDataMessage(dataMessage)); | ||
} catch (FirebaseMessagingException exc) { | ||
handleFcmException(exc, dataMessage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please raise FcmMessageTransmitException (encapsulate the FirebaseMessagingException exc) here too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think I fully understand. You suggest that the FirebaseMessageingException will also resulti in a FcmMessageTransmitException? That is different from the orgiginal implementation AFAIKS:
In MessageJob:
} catch (FirebaseMessagingException exc) {
log.error("Error occurred when sending downstream message.", exc);
// TODO: update the data message status using event
if (message != null) {
handleErrorCode(exc.getErrorCode(), message);
handleFCMErrorCode(exc.getMessagingErrorCode(), message);
}
} catch (Exception e) {
throw new JobExecutionException(e);
}
Note that FirebaseMessagingException does NOT result in the JobExecutionException.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, agreed. Let's keep the original functionality for now. I was just thinking that if the notification was not sent successfully, that should maybe count towards the unsuccessful job so the notification event lifecycle can be updated with failure, but I think we can tackle it in another issue.
try { | ||
fcmSender.send(createMessageFromNotification(notification)); | ||
} catch (FirebaseMessagingException exc) { | ||
handleFcmException(exc, notification); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please raise FcmMessageTransmitException (encapsulate the FirebaseMessagingException exc) here too
} | ||
} catch (Exception e) { | ||
throw new JobExecutionException(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can keep still keep this try/catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was re-introduced.
7661b94
to
d5361c2
Compare
d5361c2
to
e516583
Compare
e516583
to
eb9fcf9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks
Proposal
This PR implements the optional sending of notifications via email alongside push notifications. This feature is controlled by the new application property:
Implementation details
radar.notification.email.enabled=true
and no email address provided at registration of new user, an warning is logged.radar.notification.email.enabled=true
and there is no email provided for the User, this is logged as a warning, but no Exception is thrown.