Merge pull request #4980 from appsmithorg/feature/send-email-on-comment
Feature/send email on comment
This commit is contained in:
commit
8db64e6355
|
|
@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
|
@ -41,16 +42,16 @@ public class CommentController extends BaseController<CommentService, Comment, S
|
|||
@RequestParam String threadId,
|
||||
ServerWebExchange exchange) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return service.create(threadId, resource)
|
||||
return service.create(threadId, resource, exchange.getRequest().getHeaders().getOrigin())
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
||||
@PostMapping("/threads")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<CommentThread>> createThread(@Valid @RequestBody CommentThread resource,
|
||||
ServerWebExchange exchange) {
|
||||
@RequestHeader(name = "Origin") String originHeader) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return service.createThread(resource)
|
||||
return service.createThread(resource, originHeader)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
||||
|
|
@ -63,10 +64,10 @@ public class CommentController extends BaseController<CommentService, Comment, S
|
|||
@PutMapping("/threads/{threadId}")
|
||||
public Mono<ResponseDTO<CommentThread>> updateThread(
|
||||
@Valid @RequestBody CommentThread resource,
|
||||
@PathVariable String threadId
|
||||
@PathVariable String threadId, ServerWebExchange exchange
|
||||
) {
|
||||
log.debug("Going to update resource {}", resource.getClass().getName());
|
||||
return service.updateThread(threadId, resource)
|
||||
return service.updateThread(threadId, resource, exchange.getRequest().getHeaders().getOrigin())
|
||||
.map(updated -> new ResponseDTO<>(HttpStatus.ACCEPTED.value(), updated, null));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.appsmith.server.events;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public abstract class AbstractCommentEvent {
|
||||
private final String authorUserName;
|
||||
private final Organization organization;
|
||||
private final Application application;
|
||||
private final String originHeader;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.appsmith.server.events;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Comment;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class CommentAddedEvent extends AbstractCommentEvent {
|
||||
private final Comment comment;
|
||||
|
||||
public CommentAddedEvent(String authorUserName, Organization organization, Application application, String originHeader, Comment comment) {
|
||||
super(authorUserName, organization, application, originHeader);
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.appsmith.server.events;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.CommentThread;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class CommentThreadClosedEvent extends AbstractCommentEvent {
|
||||
private final CommentThread commentThread;
|
||||
|
||||
public CommentThreadClosedEvent(String authorUserName, Organization organization, Application application, String originHeader, CommentThread commentThread) {
|
||||
super(authorUserName, organization, application, originHeader);
|
||||
this.commentThread = commentThread;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,8 @@ package com.appsmith.server.events;
|
|||
|
||||
import com.appsmith.server.domains.User;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class UserChangedEvent {
|
||||
|
||||
private final User user;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.appsmith.server.helpers;
|
||||
|
||||
import com.appsmith.server.domains.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CommentUtils {
|
||||
/**
|
||||
* Checks whether provided user has been mentioned in the comment. Returns true if yes and false otherwise.
|
||||
* @param comment Comment objects
|
||||
* @param userEmail email address of the user
|
||||
* @return true or false based on the condition
|
||||
*/
|
||||
public static boolean isUserMentioned(Comment comment, String userEmail) {
|
||||
if(comment.getBody() != null && comment.getBody().getEntityMap() != null) {
|
||||
for(String key : comment.getBody().getEntityMap().keySet()) {
|
||||
Comment.Entity commentEntity = comment.getBody().getEntityMap().get(key);
|
||||
if(commentEntity != null && commentEntity.getType() != null
|
||||
&& commentEntity.getType().equals("mention")) {
|
||||
// this comment has a mention, check the provided user is mentioned or not
|
||||
if(commentEntity.getData() != null) {
|
||||
Comment.EntityData.Mention mention = commentEntity.getData().getMention();
|
||||
if(mention.getUser().getUsername().equals(userEmail)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<String> getCommentBody(Comment comment) {
|
||||
List<String> commentLines = new ArrayList<>();
|
||||
if(comment.getBody() != null && comment.getBody().getBlocks() != null) {
|
||||
for (Comment.Block block : comment.getBody().getBlocks()) {
|
||||
commentLines.add(block.getText());
|
||||
}
|
||||
}
|
||||
return commentLines;
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ public class EmailSender {
|
|||
REPLY_TO = makeReplyTo();
|
||||
}
|
||||
|
||||
public Mono<Boolean> sendMail(String to, String subject, String text, Map<String, String> params) {
|
||||
public Mono<Boolean> sendMail(String to, String subject, String text, Map<String, ? extends Object> params) {
|
||||
|
||||
/**
|
||||
* Creating a publisher which sends email in a blocking fashion, subscribing on the bounded elastic
|
||||
|
|
@ -122,7 +122,7 @@ public class EmailSender {
|
|||
* @return Template string with Mustache replacements applied.
|
||||
* @throws IOException bubbled from Mustache renderer.
|
||||
*/
|
||||
private String replaceEmailTemplate(String template, Map<String, String> params) throws IOException {
|
||||
private String replaceEmailTemplate(String template, Map<String, ? extends Object> params) throws IOException {
|
||||
MustacheFactory mf = new DefaultMustacheFactory();
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
Mustache mustache = mf.compile(template);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import java.util.List;
|
|||
|
||||
public interface CommentService extends CrudService<Comment, String> {
|
||||
|
||||
Mono<Comment> create(String threadId, Comment organization);
|
||||
Mono<Comment> create(String threadId, Comment organization, String originHeader);
|
||||
|
||||
Mono<CommentThread> createThread(CommentThread commentThread);
|
||||
Mono<CommentThread> createThread(CommentThread commentThread, String originHeader);
|
||||
|
||||
Mono<CommentThread> updateThread(String threadId, CommentThread commentThread);
|
||||
Mono<CommentThread> updateThread(String threadId, CommentThread commentThread, String originHeader);
|
||||
|
||||
Mono<List<CommentThread>> getThreadsByApplicationId(String applicationId);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,14 @@ import com.appsmith.server.domains.CommentThread;
|
|||
import com.appsmith.server.domains.CommentThreadNotification;
|
||||
import com.appsmith.server.domains.Notification;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.events.CommentAddedEvent;
|
||||
import com.appsmith.server.events.CommentThreadClosedEvent;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.repositories.CommentRepository;
|
||||
import com.appsmith.server.repositories.CommentThreadRepository;
|
||||
import com.appsmith.server.solutions.EmailEventHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
|
@ -52,6 +55,7 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
|
||||
private final PolicyGenerator policyGenerator;
|
||||
private final PolicyUtils policyUtils;
|
||||
private final EmailEventHandler emailEventHandler;
|
||||
|
||||
public CommentServiceImpl(
|
||||
Scheduler scheduler,
|
||||
|
|
@ -66,8 +70,8 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
ApplicationService applicationService,
|
||||
NotificationService notificationService,
|
||||
PolicyGenerator policyGenerator,
|
||||
PolicyUtils policyUtils
|
||||
) {
|
||||
PolicyUtils policyUtils,
|
||||
EmailEventHandler emailEventHandler) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.threadRepository = threadRepository;
|
||||
this.userService = userService;
|
||||
|
|
@ -76,14 +80,15 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
this.notificationService = notificationService;
|
||||
this.policyGenerator = policyGenerator;
|
||||
this.policyUtils = policyUtils;
|
||||
this.emailEventHandler = emailEventHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Comment> create(String threadId, Comment comment) {
|
||||
return create(threadId, comment, true);
|
||||
public Mono<Comment> create(String threadId, Comment comment, String originHeader) {
|
||||
return create(threadId, comment, originHeader, true);
|
||||
}
|
||||
|
||||
public Mono<Comment> create(String threadId, Comment comment, boolean shouldCreateNotification) {
|
||||
private Mono<Comment> create(String threadId, Comment comment, String originHeader, boolean shouldCreateNotification) {
|
||||
if (StringUtils.isWhitespace(comment.getAuthorName())) {
|
||||
// Error: User can't explicitly set the author name. It will be the currently logged in user.
|
||||
return Mono.empty();
|
||||
|
|
@ -125,36 +130,40 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
comment.setAuthorName(authorName);
|
||||
return Mono.zip(
|
||||
Mono.just(user),
|
||||
Mono.just(thread),
|
||||
repository.save(comment)
|
||||
);
|
||||
})
|
||||
.flatMap(tuple -> {
|
||||
final User user = tuple.getT1();
|
||||
final Comment savedComment = tuple.getT2();
|
||||
CommentThread commentThread = tuple.getT2();
|
||||
final Comment savedComment = tuple.getT3();
|
||||
Mono<Boolean> publishEmailMono = emailEventHandler.publish(
|
||||
comment.getAuthorUsername(), commentThread.getApplicationId(), comment, originHeader
|
||||
);
|
||||
|
||||
if (shouldCreateNotification) {
|
||||
final Set<String> usernames = policyUtils.findUsernamesWithPermission(
|
||||
savedComment.getPolicies(), AclPermission.READ_COMMENT);
|
||||
|
||||
List<Mono<Notification>> monos = new ArrayList<>();
|
||||
List<Mono<Notification>> notificationMonos = new ArrayList<>();
|
||||
for (String username : usernames) {
|
||||
if (!username.equals(user.getUsername())) {
|
||||
final CommentNotification notification = new CommentNotification();
|
||||
notification.setComment(savedComment);
|
||||
notification.setForUsername(username);
|
||||
monos.add(notificationService.create(notification));
|
||||
Mono<Notification> notificationMono = notificationService.create(notification);
|
||||
notificationMonos.add(notificationMono);
|
||||
}
|
||||
}
|
||||
|
||||
return Flux.concat(monos).then(Mono.just(savedComment));
|
||||
return Flux.concat(notificationMonos).then(publishEmailMono).thenReturn(savedComment);
|
||||
} else {
|
||||
return Mono.just(savedComment);
|
||||
return publishEmailMono.thenReturn(savedComment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<CommentThread> createThread(CommentThread commentThread) {
|
||||
public Mono<CommentThread> createThread(CommentThread commentThread, String originHeader) {
|
||||
// 1. Check if this user has permission on the application given by `commentThread.applicationId`.
|
||||
// 2. Save the comment thread and get it's id. This is the `threadId`.
|
||||
// 3. Pull the comment out of the list of comments, set it's `threadId` and save it separately.
|
||||
|
|
@ -211,7 +220,7 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
boolean isFirst = true;
|
||||
for (final Comment comment : thread.getComments()) {
|
||||
comment.setId(null);
|
||||
commentSaverMonos.add(create(thread.getId(), comment, !isFirst));
|
||||
commentSaverMonos.add(create(thread.getId(), comment, originHeader, !isFirst));
|
||||
isFirst = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -241,7 +250,6 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
monos.add(notificationService.create(notification));
|
||||
}
|
||||
}
|
||||
|
||||
return Flux.concat(monos).then(Mono.just(commentThread));
|
||||
});
|
||||
}
|
||||
|
|
@ -253,7 +261,7 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<CommentThread> updateThread(String threadId, CommentThread commentThread) {
|
||||
public Mono<CommentThread> updateThread(String threadId, CommentThread commentThread, String originHeader) {
|
||||
return Mono.zip(
|
||||
sessionUserService.getCurrentUser(),
|
||||
// Resolving, pinning and marking as read don't need manage permission on the thread.
|
||||
|
|
@ -301,7 +309,14 @@ public class CommentServiceImpl extends BaseService<CommentRepository, Comment,
|
|||
.updateById(threadId, commentThread, AclPermission.READ_THREAD)
|
||||
.flatMap(updatedThread -> {
|
||||
updatedThread.setIsViewed(true);
|
||||
return Mono.just(updatedThread);
|
||||
// send email if comment thread is resolved
|
||||
CommentThread.CommentThreadState resolvedState = commentThread.getResolvedState();
|
||||
if(resolvedState != null && resolvedState.getActive()) {
|
||||
return emailEventHandler.publish(user.getUsername(), updatedThread.getApplicationId(),
|
||||
updatedThread, originHeader).thenReturn(updatedThread);
|
||||
} else {
|
||||
return Mono.just(updatedThread);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Comment;
|
||||
import com.appsmith.server.domains.Notification;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface NotificationService extends CrudService<Notification, String> {
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ public class NotificationServiceImpl
|
|||
ReactiveMongoTemplate reactiveMongoTemplate,
|
||||
NotificationRepository repository,
|
||||
AnalyticsService analyticsService,
|
||||
SessionUserService sessionUserService
|
||||
) {
|
||||
SessionUserService sessionUserService) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.sessionUserService = sessionUserService;
|
||||
}
|
||||
|
|
@ -57,5 +56,4 @@ public class NotificationServiceImpl
|
|||
return sessionUserService.getCurrentUser()
|
||||
.flatMapMany(user -> repository.findByForUsername(user.getUsername()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ import static com.appsmith.server.acl.AclPermission.USER_MANAGE_ORGANIZATIONS;
|
|||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OrganizationServiceImpl extends BaseService<OrganizationRepository, Organization, String> implements OrganizationService {
|
||||
public class OrganizationServiceImpl extends BaseService<OrganizationRepository, Organization, String>
|
||||
implements OrganizationService {
|
||||
|
||||
private final PluginRepository pluginRepository;
|
||||
private final SessionUserService sessionUserService;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Comment;
|
||||
import com.appsmith.server.domains.CommentThread;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.events.CommentAddedEvent;
|
||||
import com.appsmith.server.events.CommentThreadClosedEvent;
|
||||
import com.appsmith.server.helpers.CommentUtils;
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class EmailEventHandler {
|
||||
private static final String COMMENT_ADDED_EMAIL_TEMPLATE = "email/commentAddedTemplate.html";
|
||||
private static final String USER_MENTIONED_EMAIL_TEMPLATE = "email/userTaggedInCommentTemplate.html";
|
||||
private static final String THREAD_RESOLVED_EMAIL_TEMPLATE = "email/commentResolvedTemplate.html";
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
private final EmailSender emailSender;
|
||||
private final OrganizationRepository organizationRepository;
|
||||
private final ApplicationRepository applicationRepository;
|
||||
|
||||
public Mono<Boolean> publish(String authorUserName, String applicationId, Comment comment, String originHeader) {
|
||||
return applicationRepository.findById(applicationId).flatMap(application -> {
|
||||
return organizationRepository.findById(application.getOrganizationId()).flatMap(organization -> {
|
||||
applicationEventPublisher.publishEvent(
|
||||
new CommentAddedEvent(authorUserName, organization, application, originHeader, comment)
|
||||
);
|
||||
return Mono.just(organization);
|
||||
});
|
||||
}).thenReturn(Boolean.TRUE);
|
||||
}
|
||||
|
||||
public Mono<Boolean> publish(String authorUserName, String applicationId, CommentThread thread, String originHeader) {
|
||||
return applicationRepository.findById(applicationId).flatMap(application -> {
|
||||
return organizationRepository.findById(application.getOrganizationId()).flatMap(organization -> {
|
||||
applicationEventPublisher.publishEvent(
|
||||
new CommentThreadClosedEvent(authorUserName, organization, application, originHeader, thread)
|
||||
);
|
||||
return Mono.just(organization);
|
||||
});
|
||||
}).thenReturn(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Async
|
||||
@EventListener
|
||||
public void handle(CommentAddedEvent event) {
|
||||
this.sendEmailForComment(
|
||||
event.getAuthorUserName(), event.getApplication(), event.getOrganization(),
|
||||
event.getComment(), event.getOriginHeader()
|
||||
).subscribeOn(Schedulers.elastic())
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
@Async
|
||||
@EventListener
|
||||
public void handle(CommentThreadClosedEvent event) {
|
||||
this.sendEmailForComment(
|
||||
event.getAuthorUserName(), event.getApplication(), event.getOrganization(),
|
||||
event.getCommentThread(), event.getOriginHeader()
|
||||
)
|
||||
.subscribeOn(Schedulers.elastic())
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private Mono<Boolean> getEmailSenderMono(UserRole receiverUserRole, CommentThread commentThread,
|
||||
String originHeader, Application application, Organization organization) {
|
||||
String receiverName = StringUtils.isEmpty(receiverUserRole.getName()) ? "User" : receiverUserRole.getName();
|
||||
String receiverEmail = receiverUserRole.getUsername();
|
||||
CommentThread.CommentThreadState resolvedState = commentThread.getResolvedState();
|
||||
Map<String, Object> templateParams = new HashMap<>();
|
||||
templateParams.put("App_User_Name", receiverName);
|
||||
templateParams.put("Commenter_Name", resolvedState.getAuthorName());
|
||||
templateParams.put("Application_Name", application.getName());
|
||||
templateParams.put("Organization_Name", organization.getName());
|
||||
templateParams.put("inviteUrl", originHeader);
|
||||
|
||||
String emailSubject = String.format(
|
||||
"%s has resolved comment in %s", resolvedState.getAuthorName(), application.getName()
|
||||
);
|
||||
return emailSender.sendMail(receiverEmail, emailSubject, THREAD_RESOLVED_EMAIL_TEMPLATE, templateParams);
|
||||
}
|
||||
|
||||
private Mono<Boolean> getEmailSenderMono(UserRole receiverUserRole, Comment comment, String originHeader,
|
||||
Application application, Organization organization) {
|
||||
String receiverName = StringUtils.isEmpty(receiverUserRole.getName()) ? "User" : receiverUserRole.getName();
|
||||
String receiverEmail = receiverUserRole.getUsername();
|
||||
|
||||
Map<String, Object> templateParams = new HashMap<>();
|
||||
templateParams.put("App_User_Name", receiverName);
|
||||
templateParams.put("Commenter_Name", comment.getAuthorName());
|
||||
templateParams.put("Application_Name", application.getName());
|
||||
templateParams.put("Organization_Name", organization.getName());
|
||||
templateParams.put("Comment_Body", CommentUtils.getCommentBody(comment));
|
||||
templateParams.put("inviteUrl", originHeader);
|
||||
|
||||
String emailTemplate = COMMENT_ADDED_EMAIL_TEMPLATE;
|
||||
String emailSubject = String.format(
|
||||
"New comment from %s in %s", comment.getAuthorName(), application.getName()
|
||||
);
|
||||
|
||||
// check if user has been mentioned in the comment
|
||||
if(CommentUtils.isUserMentioned(comment, receiverEmail)) {
|
||||
emailTemplate = USER_MENTIONED_EMAIL_TEMPLATE;
|
||||
emailSubject = String.format("New comment for you from %s", comment.getAuthorName());
|
||||
}
|
||||
return emailSender.sendMail(receiverEmail, emailSubject, emailTemplate, templateParams);
|
||||
}
|
||||
|
||||
private <E> Mono<Boolean> sendEmailForComment(String authorUserName, Application application, Organization organization,
|
||||
E commentDomain, String originHeader) {
|
||||
|
||||
List<Mono<Boolean>> emailMonos = new ArrayList<>();
|
||||
for (UserRole userRole : organization.getUserRoles()) {
|
||||
if(!authorUserName.equals(userRole.getUsername())) {
|
||||
if(commentDomain instanceof Comment) {
|
||||
Comment comment = (Comment)commentDomain;
|
||||
emailMonos.add(
|
||||
getEmailSenderMono(userRole, comment, originHeader, application, organization)
|
||||
);
|
||||
} else if(commentDomain instanceof CommentThread) {
|
||||
CommentThread commentThread = (CommentThread) commentDomain;
|
||||
emailMonos.add(
|
||||
getEmailSenderMono(userRole, commentThread, originHeader, application, organization)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Flux.concat(emailMonos).then(Mono.just(Boolean.TRUE));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!--<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<style type="text/css">
|
||||
body {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
body, p, div {
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
body a {
|
||||
color: #1188E6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.wrapper {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.column.of-2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.column.of-3 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.column.of-4 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.preheader .rightColumnContent,
|
||||
.footer .rightColumnContent {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent div,
|
||||
.preheader .rightColumnContent span,
|
||||
.footer .rightColumnContent div,
|
||||
.footer .rightColumnContent span {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent,
|
||||
.preheader .leftColumnContent {
|
||||
font-size: 80% !important;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
table.wrapper-mobile {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
a.bulletproof-button {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
font-size: 80%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.columns {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--user entered Head Start--><!--End Head user entered-->
|
||||
</head>
|
||||
<body>
|
||||
<center class="wrapper" data-link-color="#1188E6"
|
||||
data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
|
||||
<div class="webkit">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top" bgcolor="#FFFFFF" width="100%">
|
||||
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0"
|
||||
cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!--[if mso]>
|
||||
<center>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="600">
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0"
|
||||
style="width:100%; max-width:600px;" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="modules-container"
|
||||
style="padding:0px 0px 0px 0px; color:#000000; text-align:left;"
|
||||
bgcolor="#ffffff" width="100%" align="left">
|
||||
<table class="module preheader preheader-hide" role="module"
|
||||
data-type="preheader" border="0" cellpadding="0"
|
||||
cellspacing="0" width="100%"
|
||||
style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="module-content">
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="wrapper" role="module" data-type="image"
|
||||
border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;"
|
||||
valign="top" align="center">
|
||||
<a href="https://www.appsmith.com/"><img
|
||||
class="max-width" border="0"
|
||||
style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:25% !important; width:25%; height:auto !important;"
|
||||
width="150" alt=""
|
||||
data-proportionally-constrained="true"
|
||||
data-responsive="true"
|
||||
src="http://cdn.mcauto-images-production.sendgrid.net/4bbae2fffe647858/b21738f2-3a49-4774-aae9-c8e80ad9c26e/924x284.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="module" role="module" data-type="text" border="0"
|
||||
cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 18px 0px; line-height:22px; text-align:inherit; background-color:#ffffff;"
|
||||
height="100%" valign="top" bgcolor="#ffffff"
|
||||
role="module-content">
|
||||
<div>
|
||||
<div style="font-family: inherit; text-align: center">
|
||||
<br></div>
|
||||
<div style="margin-left: 0px">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Hi {{App_User_Name}},</span>
|
||||
</div>
|
||||
<div style="margin-left: 0px; margin-top: 10px">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">{{Commenter_Name}} just added a new comment in {{Application_Name}} in the {{Organization_Name}} organization.</span>
|
||||
</div>
|
||||
<div style="margin-left: 0px; margin-top: 10px;padding:10px;background-color:#EEE">
|
||||
{{#Comment_Body}}
|
||||
<div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">{{ . }}</div>
|
||||
{{/Comment_Body}}
|
||||
</div>
|
||||
<div style="margin-left: 0px; margin-top: 20px;">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Please follow this link to view and respond to the comment. </span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="table-layout:fixed;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="" class="outer-td"
|
||||
style="padding:0px 0px 0px 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
class="wrapper-mobile"
|
||||
style="text-align:center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#ff6d2d"
|
||||
class="inner-td"
|
||||
style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{inviteUrl}}"
|
||||
style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;"
|
||||
target="_blank">Go to Comment</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!--<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<style type="text/css">
|
||||
body {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
body, p, div {
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
body a {
|
||||
color: #1188E6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.wrapper {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.column.of-2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.column.of-3 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.column.of-4 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.preheader .rightColumnContent,
|
||||
.footer .rightColumnContent {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent div,
|
||||
.preheader .rightColumnContent span,
|
||||
.footer .rightColumnContent div,
|
||||
.footer .rightColumnContent span {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent,
|
||||
.preheader .leftColumnContent {
|
||||
font-size: 80% !important;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
table.wrapper-mobile {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
a.bulletproof-button {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
font-size: 80%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.columns {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--user entered Head Start--><!--End Head user entered-->
|
||||
</head>
|
||||
<body>
|
||||
<center class="wrapper" data-link-color="#1188E6"
|
||||
data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
|
||||
<div class="webkit">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top" bgcolor="#FFFFFF" width="100%">
|
||||
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0"
|
||||
cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!--[if mso]>
|
||||
<center>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="600">
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0"
|
||||
style="width:100%; max-width:600px;" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="modules-container"
|
||||
style="padding:0px 0px 0px 0px; color:#000000; text-align:left;"
|
||||
bgcolor="#ffffff" width="100%" align="left">
|
||||
<table class="module preheader preheader-hide" role="module"
|
||||
data-type="preheader" border="0" cellpadding="0"
|
||||
cellspacing="0" width="100%"
|
||||
style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="module-content">
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="wrapper" role="module" data-type="image"
|
||||
border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;"
|
||||
valign="top" align="center">
|
||||
<a href="https://www.appsmith.com/"><img
|
||||
class="max-width" border="0"
|
||||
style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:25% !important; width:25%; height:auto !important;"
|
||||
width="150" alt=""
|
||||
data-proportionally-constrained="true"
|
||||
data-responsive="true"
|
||||
src="http://cdn.mcauto-images-production.sendgrid.net/4bbae2fffe647858/b21738f2-3a49-4774-aae9-c8e80ad9c26e/924x284.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="module" role="module" data-type="text" border="0"
|
||||
cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 18px 0px; line-height:22px; text-align:inherit; background-color:#ffffff;"
|
||||
height="100%" valign="top" bgcolor="#ffffff"
|
||||
role="module-content">
|
||||
<div>
|
||||
<div style="font-family: inherit; text-align: center">
|
||||
<br></div>
|
||||
<div style="margin-left: 0px">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Hi {{App_User_Name}},</span>
|
||||
</div>
|
||||
<div style="margin-left: 0px; margin-top: 10px">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">{{Commenter_Name}} has resolved the comment in {{Application_Name}} in the {{Organization_Name}} organization.</span>
|
||||
</div>
|
||||
|
||||
<div style="margin-left: 0px; margin-top: 20px;">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Please follow this link to view and re-open the comment. </span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="table-layout:fixed;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="" class="outer-td"
|
||||
style="padding:0px 0px 0px 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
class="wrapper-mobile"
|
||||
style="text-align:center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#ff6d2d"
|
||||
class="inner-td"
|
||||
style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{inviteUrl}}"
|
||||
style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;"
|
||||
target="_blank">Go to Comment</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!--<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<style type="text/css">
|
||||
body {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
body, p, div {
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
body a {
|
||||
color: #1188E6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.wrapper {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.column.of-2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.column.of-3 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.column.of-4 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.preheader .rightColumnContent,
|
||||
.footer .rightColumnContent {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent div,
|
||||
.preheader .rightColumnContent span,
|
||||
.footer .rightColumnContent div,
|
||||
.footer .rightColumnContent span {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent,
|
||||
.preheader .leftColumnContent {
|
||||
font-size: 80% !important;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
table.wrapper-mobile {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
a.bulletproof-button {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
font-size: 80%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.columns {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--user entered Head Start--><!--End Head user entered-->
|
||||
</head>
|
||||
<body>
|
||||
<center class="wrapper" data-link-color="#1188E6"
|
||||
data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
|
||||
<div class="webkit">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top" bgcolor="#FFFFFF" width="100%">
|
||||
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0"
|
||||
cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!--[if mso]>
|
||||
<center>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="600">
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0"
|
||||
style="width:100%; max-width:600px;" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="modules-container"
|
||||
style="padding:0px 0px 0px 0px; color:#000000; text-align:left;"
|
||||
bgcolor="#ffffff" width="100%" align="left">
|
||||
<table class="module preheader preheader-hide" role="module"
|
||||
data-type="preheader" border="0" cellpadding="0"
|
||||
cellspacing="0" width="100%"
|
||||
style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="module-content">
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="wrapper" role="module" data-type="image"
|
||||
border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;"
|
||||
valign="top" align="center">
|
||||
<a href="https://www.appsmith.com/"><img
|
||||
class="max-width" border="0"
|
||||
style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:25% !important; width:25%; height:auto !important;"
|
||||
width="150" alt=""
|
||||
data-proportionally-constrained="true"
|
||||
data-responsive="true"
|
||||
src="http://cdn.mcauto-images-production.sendgrid.net/4bbae2fffe647858/b21738f2-3a49-4774-aae9-c8e80ad9c26e/924x284.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="module" role="module" data-type="text" border="0"
|
||||
cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 18px 0px; line-height:22px; text-align:inherit; background-color:#ffffff;"
|
||||
height="100%" valign="top" bgcolor="#ffffff"
|
||||
role="module-content">
|
||||
<div>
|
||||
<div style="font-family: inherit; text-align: center">
|
||||
<br></div>
|
||||
<div style="margin-left: 0px">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Hi {{App_User_Name}},</span>
|
||||
</div>
|
||||
<div style="margin-left: 0px; margin-top: 10px">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">{{Commenter_Name}} tagged you in a comment in {{Application_Name}} in the {{Organization_Name}} organization.</span>
|
||||
</div>
|
||||
<div style="margin-left: 0px; margin-top: 10px;padding:10px;background-color:#EEE">
|
||||
{{#Comment_Body}}
|
||||
<div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">{{ . }}</div>
|
||||
{{/Comment_Body}}
|
||||
</div>
|
||||
<div style="margin-left: 0px; margin-top: 20px;">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Please follow this link to view and respond to the comment. </span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="table-layout:fixed;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="" class="outer-td"
|
||||
style="padding:0px 0px 0px 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
class="wrapper-mobile"
|
||||
style="text-align:center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#ff6d2d"
|
||||
class="inner-td"
|
||||
style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{inviteUrl}}"
|
||||
style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;"
|
||||
target="_blank">Go to Comment</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.appsmith.server.helpers;
|
||||
|
||||
import com.appsmith.server.domains.Comment;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class CommentUtilsTest {
|
||||
@Test
|
||||
void getCommentBody_WhenBodyIsNull_ReturnsEmptyList() {
|
||||
Comment comment = new Comment();
|
||||
Assert.assertEquals(0, CommentUtils.getCommentBody(comment).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommentBody_WhenBodyHasMultipleBlocks_ReturnsValidBodies() {
|
||||
Comment.Block commentBlock1 = new Comment.Block();
|
||||
commentBlock1.setText("First line");
|
||||
Comment.Block commentBlock2 = new Comment.Block();
|
||||
commentBlock1.setText("Second line");
|
||||
|
||||
Comment.Body commentBody = new Comment.Body();
|
||||
commentBody.setBlocks(List.of(commentBlock1, commentBlock2));
|
||||
|
||||
Comment comment = new Comment();
|
||||
comment.setBody(commentBody);
|
||||
|
||||
Assert.assertEquals(2, CommentUtils.getCommentBody(comment).size());
|
||||
Assert.assertEquals(commentBlock1.getText(), CommentUtils.getCommentBody(comment).get(0));
|
||||
Assert.assertEquals(commentBlock2.getText(), CommentUtils.getCommentBody(comment).get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isUserMentioned_WhenBodyIsNull_ReturnsFalse() {
|
||||
Comment comment = new Comment();
|
||||
Assert.assertFalse(CommentUtils.isUserMentioned(comment, "user@abc.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isUserMentioned_WhenBodyHasNoMention_ReturnsFalse() {
|
||||
Comment.Body body = new Comment.Body();
|
||||
Comment comment = new Comment();
|
||||
comment.setBody(body);
|
||||
Assert.assertFalse(CommentUtils.isUserMentioned(comment, "user@abc.com"));
|
||||
|
||||
Comment.Entity entity = new Comment.Entity();
|
||||
Map<String, Comment.Entity> entityMap = new HashMap<>();
|
||||
entityMap.put("abc", entity);
|
||||
body.setEntityMap(entityMap);
|
||||
comment.setBody(body);
|
||||
Assert.assertFalse(CommentUtils.isUserMentioned(comment, "user@abc.com"));
|
||||
}
|
||||
|
||||
private Map<String, Comment.Entity> createEntityMapForUsers(List<String> mentionedUserNames) {
|
||||
Map<String, Comment.Entity> entityMap = new HashMap<>();
|
||||
for (String username: mentionedUserNames) {
|
||||
Comment.EntityData.EntityUser entityUser = new Comment.EntityData.EntityUser();
|
||||
entityUser.setUsername(username);
|
||||
Comment.EntityData.Mention mention = new Comment.EntityData.Mention();
|
||||
mention.setUser(entityUser);
|
||||
|
||||
Comment.EntityData entityData = new Comment.EntityData();
|
||||
entityData.setMention(mention);
|
||||
|
||||
Comment.Entity entity = new Comment.Entity();
|
||||
entity.setType("mention");
|
||||
entity.setData(entityData);
|
||||
entityMap.put(username, entity);
|
||||
}
|
||||
return entityMap;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isUserMentioned_WhenSomeoneIsMentioned_ReturnsCorrectValue() {
|
||||
Map<String, Comment.Entity> entityMap = createEntityMapForUsers(
|
||||
List.of("1", "2", "3")
|
||||
);
|
||||
Comment.Body body = new Comment.Body();
|
||||
body.setEntityMap(entityMap);
|
||||
Comment comment = new Comment();
|
||||
comment.setBody(body);
|
||||
|
||||
Assert.assertTrue(CommentUtils.isUserMentioned(comment, "1"));
|
||||
Assert.assertTrue(CommentUtils.isUserMentioned(comment, "2"));
|
||||
Assert.assertTrue(CommentUtils.isUserMentioned(comment, "3"));
|
||||
Assert.assertFalse(CommentUtils.isUserMentioned(comment, "4"));
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ public class CommentServiceTest {
|
|||
thread.setComments(List.of(
|
||||
makePlainTextComment("comment one")
|
||||
));
|
||||
return commentService.createThread(thread);
|
||||
return commentService.createThread(thread, "https://app.appsmith.com");
|
||||
})
|
||||
.zipWhen(thread -> commentService.getThreadsByApplicationId(thread.getApplicationId()));
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ public class CommentServiceTest {
|
|||
thread.setComments(List.of(
|
||||
makePlainTextComment("Test Comment")
|
||||
));
|
||||
return commentService.createThread(thread);
|
||||
return commentService.createThread(thread, "https://app.appsmith.com");
|
||||
})
|
||||
.flatMap(commentThread -> Mono.just(commentThread.getComments().get(0)))
|
||||
.cache();
|
||||
|
|
@ -163,7 +163,7 @@ public class CommentServiceTest {
|
|||
final CommentThread thread = new CommentThread();
|
||||
thread.setApplicationId(application.getId());
|
||||
thread.setComments(List.of(makePlainTextComment("Test Comment")));
|
||||
return commentService.createThread(thread);
|
||||
return commentService.createThread(thread, "https://app.appsmith.com");
|
||||
})
|
||||
.flatMap(commentThread -> Mono.just(commentThread.getComments().get(0)))
|
||||
.flatMap(comment -> {
|
||||
|
|
@ -202,7 +202,7 @@ public class CommentServiceTest {
|
|||
final CommentThread thread = new CommentThread();
|
||||
thread.setApplicationId(application.getId());
|
||||
thread.setComments(List.of(makePlainTextComment("Test Comment")));
|
||||
return commentService.createThread(thread);
|
||||
return commentService.createThread(thread, "https://app.appsmith.com");
|
||||
})
|
||||
.flatMap(commentThread -> Mono.just(commentThread.getComments().get(0)))
|
||||
.flatMap(comment -> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.appsmith.server.services;
|
|||
import com.appsmith.server.acl.RoleGraph;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.repositories.AssetRepository;
|
||||
|
|
@ -15,18 +14,15 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
|
||||
|
|
@ -56,7 +52,6 @@ public class OrganizationServiceUnitTest {
|
|||
organizationRepository, analyticsService, pluginRepository, sessionUserService, userOrganizationService,
|
||||
userRepository, roleGraph, assetRepository, assetService
|
||||
);
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Comment;
|
||||
import com.appsmith.server.domains.CommentThread;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.events.CommentAddedEvent;
|
||||
import com.appsmith.server.events.CommentThreadClosedEvent;
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class EmailEventHandlerTest {
|
||||
|
||||
private static final String COMMENT_ADDED_EMAIL_TEMPLATE = "email/commentAddedTemplate.html";
|
||||
private static final String USER_MENTIONED_EMAIL_TEMPLATE = "email/userTaggedInCommentTemplate.html";
|
||||
private static final String THREAD_RESOLVED_EMAIL_TEMPLATE = "email/commentResolvedTemplate.html";
|
||||
|
||||
@MockBean
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
@MockBean
|
||||
private EmailSender emailSender;
|
||||
@MockBean
|
||||
private OrganizationRepository organizationRepository;
|
||||
@MockBean
|
||||
private ApplicationRepository applicationRepository;
|
||||
|
||||
EmailEventHandler emailEventHandler;
|
||||
private Application application;
|
||||
private Organization organization;
|
||||
|
||||
String authorUserName = "abc";
|
||||
String originHeader = "efg";
|
||||
String applicationId = "application-id";
|
||||
String organizationId = "organization-id";
|
||||
String emailReceiverUsername = "email-receiver";
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
emailEventHandler = new EmailEventHandler(
|
||||
applicationEventPublisher, emailSender, organizationRepository, applicationRepository
|
||||
);
|
||||
application = new Application();
|
||||
application.setName("Test application for comment");
|
||||
application.setOrganizationId(organizationId);
|
||||
organization = new Organization();
|
||||
|
||||
// add a role with email receiver username
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setUsername(emailReceiverUsername);
|
||||
organization.setUserRoles(List.of(userRole));
|
||||
|
||||
Mockito.when(applicationRepository.findById(applicationId)).thenReturn(Mono.just(application));
|
||||
Mockito.when(organizationRepository.findById(organizationId)).thenReturn(Mono.just(organization));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publish_WhenValidCommentProvided_ReturnsTrue() {
|
||||
Comment comment = new Comment();
|
||||
CommentAddedEvent commentAddedEvent = new CommentAddedEvent(
|
||||
authorUserName, organization, application, originHeader, comment
|
||||
);
|
||||
|
||||
Mockito.doNothing().when(applicationEventPublisher).publishEvent(commentAddedEvent);
|
||||
|
||||
Mono<Boolean> booleanMono = emailEventHandler.publish(authorUserName, applicationId, comment, originHeader);
|
||||
StepVerifier.create(booleanMono).assertNext(aBoolean -> {
|
||||
Assert.assertEquals(Boolean.TRUE, aBoolean);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publish_WhenValidCommentThreadProvided_ReturnsTrue() {
|
||||
CommentThread commentThread = new CommentThread();
|
||||
CommentThreadClosedEvent commentThreadClosedEvent = new CommentThreadClosedEvent(
|
||||
authorUserName, organization, application, originHeader, commentThread
|
||||
);
|
||||
Mockito.doNothing().when(applicationEventPublisher).publishEvent(commentThreadClosedEvent);
|
||||
|
||||
Mono<Boolean> booleanMono = emailEventHandler.publish(authorUserName, applicationId, commentThread, originHeader);
|
||||
StepVerifier.create(booleanMono).assertNext(aBoolean -> {
|
||||
Assert.assertEquals(Boolean.TRUE, aBoolean);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handle_WhenValidCommentAddedEvent_ReturnsTrue() {
|
||||
Comment sampleComment = new Comment();
|
||||
sampleComment.setAuthorUsername(authorUserName);
|
||||
sampleComment.setAuthorName("Test Author");
|
||||
|
||||
// send the event
|
||||
CommentAddedEvent commentAddedEvent = new CommentAddedEvent(
|
||||
authorUserName, organization, application, originHeader, sampleComment
|
||||
);
|
||||
emailEventHandler.handle(commentAddedEvent);
|
||||
|
||||
String expectedEmailSubject = String.format(
|
||||
"New comment from %s in %s", sampleComment.getAuthorName(), application.getName()
|
||||
);
|
||||
// check email sender was called with expected template and subject
|
||||
Mockito.verify(emailSender, Mockito.times(1)).sendMail(
|
||||
eq(emailReceiverUsername), eq(expectedEmailSubject), eq(COMMENT_ADDED_EMAIL_TEMPLATE), Mockito.anyMap()
|
||||
);
|
||||
}
|
||||
|
||||
private Map<String, Comment.Entity> createEntityMapForUsers(List<String> mentionedUserNames) {
|
||||
Map<String, Comment.Entity> entityMap = new HashMap<>();
|
||||
for (String username: mentionedUserNames) {
|
||||
Comment.EntityData.EntityUser entityUser = new Comment.EntityData.EntityUser();
|
||||
entityUser.setUsername(username);
|
||||
Comment.EntityData.Mention mention = new Comment.EntityData.Mention();
|
||||
mention.setUser(entityUser);
|
||||
|
||||
Comment.EntityData entityData = new Comment.EntityData();
|
||||
entityData.setMention(mention);
|
||||
|
||||
Comment.Entity entity = new Comment.Entity();
|
||||
entity.setType("mention");
|
||||
entity.setData(entityData);
|
||||
entityMap.put(username, entity);
|
||||
}
|
||||
return entityMap;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handle_WhenUserMentionedEvent_ReturnsTrue() {
|
||||
Comment sampleComment = new Comment();
|
||||
sampleComment.setAuthorUsername(authorUserName);
|
||||
sampleComment.setAuthorName("Test Author");
|
||||
|
||||
// mention the emailReceiverUsername in the sample comment
|
||||
Map<String, Comment.Entity> entityMap = createEntityMapForUsers(List.of(emailReceiverUsername));
|
||||
Comment.Body body = new Comment.Body();
|
||||
body.setEntityMap(entityMap);
|
||||
sampleComment.setBody(body);
|
||||
|
||||
// send the event
|
||||
CommentAddedEvent commentAddedEvent = new CommentAddedEvent(
|
||||
authorUserName, organization, application, originHeader, sampleComment
|
||||
);
|
||||
emailEventHandler.handle(commentAddedEvent);
|
||||
|
||||
// check if expectation meets
|
||||
String expectedEmailSubject = String.format("New comment for you from %s", sampleComment.getAuthorName());
|
||||
|
||||
// check email sender was called with expected template and subject
|
||||
Mockito.verify(emailSender, Mockito.times(1)).sendMail(
|
||||
eq(emailReceiverUsername), eq(expectedEmailSubject), eq(USER_MENTIONED_EMAIL_TEMPLATE), Mockito.anyMap()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handle_WhenThreadClosed_ReturnsTrue() {
|
||||
// add comment thread with a resolved state where resolver is `authorUserName`
|
||||
String resolverName = "Test Author";
|
||||
CommentThread.CommentThreadState resolveState = new CommentThread.CommentThreadState();
|
||||
resolveState.setAuthorUsername(authorUserName);
|
||||
resolveState.setAuthorName(resolverName);
|
||||
resolveState.setActive(true);
|
||||
|
||||
CommentThread commentThread = new CommentThread();
|
||||
commentThread.setResolvedState(resolveState);
|
||||
|
||||
// send the event
|
||||
CommentThreadClosedEvent commentAddedEvent = new CommentThreadClosedEvent(
|
||||
authorUserName, organization, application, originHeader, commentThread
|
||||
);
|
||||
emailEventHandler.handle(commentAddedEvent);
|
||||
|
||||
// check if expectation meets
|
||||
String expectedEmailSubject = String.format(
|
||||
"%s has resolved comment in %s", resolveState.getAuthorName(), application.getName()
|
||||
);
|
||||
// check email sender was called with expected template and subject
|
||||
Mockito.verify(emailSender, Mockito.times(1)).sendMail(
|
||||
eq(emailReceiverUsername), eq(expectedEmailSubject), eq(THREAD_RESOLVED_EMAIL_TEMPLATE), Mockito.anyMap()
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user