PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/jira-project/jira-components/jira-core/src/main/java/com/atlassian/jira/mention/SortedMentionableUserSearcher.java

https://bitbucket.org/ahmed_bilal_360factors/jira7-core
Java | 221 lines | 184 code | 22 blank | 15 comment | 13 complexity | 2f33c3715cfc8c50653c8cbd1f3aab91 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.atlassian.jira.mention;
  2. import com.atlassian.crowd.embedded.api.User;
  3. import com.atlassian.jira.bc.ServiceOutcome;
  4. import com.atlassian.jira.bc.issue.comment.CommentService;
  5. import com.atlassian.jira.bc.issue.vote.VoteService;
  6. import com.atlassian.jira.bc.issue.watcher.WatcherService;
  7. import com.atlassian.jira.bc.user.search.UserMatcherPredicate;
  8. import com.atlassian.jira.bc.user.search.UserSearchParams;
  9. import com.atlassian.jira.bc.user.search.UserSearchService;
  10. import com.atlassian.jira.issue.Issue;
  11. import com.atlassian.jira.issue.comments.Comment;
  12. import com.atlassian.jira.permission.ProjectPermissions;
  13. import com.atlassian.jira.security.PermissionManager;
  14. import com.atlassian.jira.security.Permissions;
  15. import com.atlassian.jira.user.ApplicationUser;
  16. import com.atlassian.jira.user.ApplicationUsers;
  17. import com.atlassian.jira.user.ImmutableUserIssueRelevance;
  18. import com.atlassian.jira.user.IssueInvolvement;
  19. import com.atlassian.jira.user.UserIssueRelevance;
  20. import com.atlassian.jira.util.I18nHelper;
  21. import com.atlassian.jira.util.lang.Pair;
  22. import com.google.common.collect.ImmutableList;
  23. import org.apache.commons.lang.StringUtils;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.Comparator;
  27. import java.util.Date;
  28. import java.util.HashSet;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Optional;
  32. import java.util.Set;
  33. import java.util.function.BinaryOperator;
  34. import java.util.function.Predicate;
  35. import java.util.stream.Collectors;
  36. import static com.atlassian.jira.util.dbc.Assertions.notNull;
  37. public class SortedMentionableUserSearcher implements MentionableUserSearcher {
  38. private static final Date EPOCH = new Date(0);
  39. private final PermissionManager permissionManager;
  40. private final I18nHelper i18n;
  41. private final CommentService commentService;
  42. private final WatcherService watcherService;
  43. private final VoteService voteService;
  44. private final UserSearchService userSearchService;
  45. public SortedMentionableUserSearcher(
  46. final I18nHelper i18n,
  47. final PermissionManager permissionManager,
  48. final CommentService commentService,
  49. final WatcherService watcherService,
  50. final VoteService voteService,
  51. final UserSearchService userSearchService) {
  52. this.permissionManager = permissionManager;
  53. this.commentService = commentService;
  54. this.watcherService = watcherService;
  55. this.voteService = voteService;
  56. this.userSearchService = userSearchService;
  57. this.i18n = notNull(i18n);
  58. }
  59. @Override
  60. public List<UserIssueRelevance> findRelatedUsersToMention(
  61. final String query,
  62. final Issue issue,
  63. final ApplicationUser requestingUser,
  64. final int maxResults) {
  65. if (issue == null || !permissionManager.hasPermission(Permissions.USER_PICKER, requestingUser)) {
  66. return ImmutableList.of();
  67. }
  68. final String lowercaseQuery = StringUtils.isEmpty(query) ? "" : query.toLowerCase(i18n.getLocale());
  69. final List<Comment> visibleComments = commentService.getCommentsForUser(requestingUser, issue).stream()
  70. .filter(comment -> comment.getAuthorApplicationUser() != null)
  71. .collect(Collectors.toList());
  72. final Optional<ApplicationUser> assignee = Optional.ofNullable(issue.getAssignee());
  73. final Optional<ApplicationUser> reporter = Optional.ofNullable(issue.getReporter());
  74. final List<ApplicationUser> usersFromRoleMention = getUsersFromRoleMention(lowercaseQuery, assignee, reporter);
  75. final Map<ApplicationUser, Date> userLatestCommentDates = visibleComments.stream()
  76. .collect(Collectors.groupingBy(
  77. Comment::getAuthorApplicationUser,
  78. Collectors.reducing(EPOCH, Comment::getCreated, BinaryOperator.maxBy(Comparator.naturalOrder()))
  79. ));
  80. final List<ApplicationUser> watchers = getWatchers(requestingUser, issue);
  81. final List<ApplicationUser> voters = getVoters(requestingUser, issue);
  82. final Set<ApplicationUser> involvedUsers = new HashSet<>();
  83. assignee.ifPresent(involvedUsers::add);
  84. reporter.ifPresent(involvedUsers::add);
  85. involvedUsers.addAll(userLatestCommentDates.keySet());
  86. involvedUsers.addAll(watchers);
  87. involvedUsers.addAll(voters);
  88. final List<ApplicationUser> filteredInvolvedUsers = involvedUsers.stream()
  89. .filter(new UserMatcherPredicate(lowercaseQuery, false)::apply)
  90. .collect(Collectors.toList());
  91. final Set<ApplicationUser> matchingUsers = new HashSet<>();
  92. matchingUsers.addAll(usersFromRoleMention);
  93. matchingUsers.addAll(filteredInvolvedUsers);
  94. // If we haven't found enough users involved with the issue, search for
  95. // users who match the query
  96. if (matchingUsers.size() < maxResults) {
  97. // We ask for maxResults (and not maxResults - matchingUsers.size())
  98. // as there may be overlap with already added users
  99. final List<ApplicationUser> usersFromSearch = searchForUsers(lowercaseQuery, issue, maxResults);
  100. matchingUsers.addAll(usersFromSearch);
  101. }
  102. return getUserIssueRelevances(matchingUsers, issue, assignee, reporter, userLatestCommentDates, voters, watchers, maxResults);
  103. }
  104. private List<UserIssueRelevance> getUserIssueRelevances(
  105. final Set<ApplicationUser> matchingUsers,
  106. final Issue issue,
  107. final Optional<ApplicationUser> assignee,
  108. final Optional<ApplicationUser> reporter,
  109. final Map<ApplicationUser, Date> userLatestCommentDates,
  110. final List<ApplicationUser> voters,
  111. final List<ApplicationUser> watchers,
  112. final int maxResults) {
  113. final Set<ApplicationUser> watcherSet = new HashSet<>(watchers);
  114. final Set<ApplicationUser> voterSet = new HashSet<>(voters);
  115. return matchingUsers.stream()
  116. .map(user -> buildUserIssuerRelevance(user, issue, assignee, reporter, userLatestCommentDates, watcherSet, voterSet))
  117. .sorted()
  118. .limit(maxResults)
  119. .collect(Collectors.toList());
  120. }
  121. /**
  122. * Returns a list of users matching the role mentioned in the query
  123. * (i.e. returns the assignee if the query matches "assignee")
  124. *
  125. * @param lowercaseQuery The query to compare against roles
  126. * @param assignee The user to add to the list of matching users if the
  127. * query matches "assignee"
  128. * @param reporter The user to add to the list of matching users if the
  129. * query matches "reporter"
  130. * @return A list of users matching the role mentioned in the query
  131. */
  132. private List<ApplicationUser> getUsersFromRoleMention(
  133. final String lowercaseQuery,
  134. final Optional<ApplicationUser> assignee,
  135. final Optional<ApplicationUser> reporter) {
  136. final List<ApplicationUser> users = new ArrayList<>(2);
  137. if (queryMatchesInvolvement(lowercaseQuery, IssueInvolvement.REPORTER)) {
  138. reporter.ifPresent(users::add);
  139. }
  140. if (queryMatchesInvolvement(lowercaseQuery, IssueInvolvement.ASSIGNEE)) {
  141. assignee.ifPresent(users::add);
  142. }
  143. return users;
  144. }
  145. private boolean queryMatchesInvolvement(final String lowercaseQuery, final IssueInvolvement involvement) {
  146. return i18n.getText(involvement.getI18nKey())
  147. .toLowerCase(i18n.getLocale())
  148. .startsWith(lowercaseQuery);
  149. }
  150. private List<ApplicationUser> getVoters(final ApplicationUser requestingUser, final Issue issue) {
  151. final ServiceOutcome<Collection<ApplicationUser>> voters = voteService.viewVoters(issue, requestingUser);
  152. return voters.isValid() ? ImmutableList.copyOf(voters.getReturnedValue()) : ImmutableList.of();
  153. }
  154. private List<ApplicationUser> getWatchers(final ApplicationUser requestingUser, final Issue issue) {
  155. if (watcherService.isWatchingEnabled()) {
  156. final ServiceOutcome<Pair<Integer, List<ApplicationUser>>> watchers = watcherService.getWatchers(issue, requestingUser);
  157. if (watchers.isValid()) {
  158. return watchers.getReturnedValue().second();
  159. }
  160. }
  161. return ImmutableList.of();
  162. }
  163. private UserIssueRelevance buildUserIssuerRelevance(
  164. final ApplicationUser user,
  165. final Issue issue,
  166. final Optional<ApplicationUser> assignee,
  167. final Optional<ApplicationUser> reporter,
  168. final Map<ApplicationUser, Date> userLatestCommentDates,
  169. final Set<ApplicationUser> watchers,
  170. final Set<ApplicationUser> voters) {
  171. final ImmutableUserIssueRelevance.IssueUserBuilder issueUserBuilder = new ImmutableUserIssueRelevance.IssueUserBuilder()
  172. .withIssue(issue)
  173. .withUser(user);
  174. if (assignee.map(user::equals).orElse(false)) {
  175. issueUserBuilder.withAssigneeInvolvement();
  176. }
  177. if (reporter.map(user::equals).orElse(false)) {
  178. issueUserBuilder.withReporterInvolvement();
  179. }
  180. Optional.ofNullable(userLatestCommentDates.get(user))
  181. .ifPresent(issueUserBuilder::withCommenterInvolvement);
  182. if (watchers.contains(user)) {
  183. issueUserBuilder.withWatcherInvolvement();
  184. }
  185. if (voters.contains(user)) {
  186. issueUserBuilder.withVoterInvolvement();
  187. }
  188. return issueUserBuilder.build();
  189. }
  190. private List<ApplicationUser> searchForUsers(final String lowercaseQuery, final Issue issue, final int maxResults) {
  191. final Predicate<User> hasIssueBrowsePermission = user -> permissionManager.hasPermission(ProjectPermissions.BROWSE_PROJECTS, issue, ApplicationUsers.from(user));
  192. final UserSearchParams anyActiveUser = UserSearchParams.builder()
  193. .allowEmptyQuery(true)
  194. .filter(hasIssueBrowsePermission::test)
  195. .maxResults(maxResults)
  196. .build();
  197. return userSearchService.findUsers(lowercaseQuery, anyActiveUser);
  198. }
  199. }