Skip to content

Commit

Permalink
Display Review Score on User Profiles (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
GODrums authored Nov 21, 2024
1 parent 80cd9ee commit f95852c
Show file tree
Hide file tree
Showing 18 changed files with 256 additions and 165 deletions.
4 changes: 4 additions & 0 deletions server/application-server/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ components:
- htmlUrl
- id
- isDismissed
- score
- state
type: object
properties:
Expand All @@ -243,6 +244,9 @@ components:
$ref: "#/components/schemas/PullRequestBaseInfo"
htmlUrl:
type: string
score:
type: integer
format: int32
submittedAt:
type: string
format: date-time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,15 @@ SELECT MIN(p.createdAt)
List<PullRequest> findAssignedByLoginAndStates(
@Param("assigneeLogin") String assigneeLogin,
@Param("states") Set<PullRequest.State> states);

@Query("""
SELECT p
FROM PullRequest p
LEFT JOIN FETCH p.labels
JOIN FETCH p.author
LEFT JOIN FETCH p.assignees
LEFT JOIN FETCH p.repository
WHERE p.repository = :repository AND p.number = :number
""")
Optional<PullRequest> findByRepositoryAndNumber(@Param("repository") de.tum.in.www1.hephaestus.gitprovider.repository.Repository repository, @Param("number") int number);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package de.tum.in.www1.hephaestus.gitprovider.pullrequestreview;

import java.time.OffsetDateTime;

import org.springframework.lang.NonNull;
import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.in.www1.hephaestus.gitprovider.issuecomment.IssueComment;
import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequestBaseInfoDTO;
import de.tum.in.www1.hephaestus.gitprovider.user.UserInfoDTO;

Expand All @@ -18,31 +16,7 @@ public record PullRequestReviewInfoDTO(
UserInfoDTO author,
PullRequestBaseInfoDTO pullRequest,
@NonNull String htmlUrl,
@NonNull int score,
OffsetDateTime submittedAt) {
// We do not have createdAt and updatedAt for reviews

public static PullRequestReviewInfoDTO fromPullRequestReview(PullRequestReview pullRequestReview) {
return new PullRequestReviewInfoDTO(
pullRequestReview.getId(),
pullRequestReview.isDismissed(),
pullRequestReview.getState(),
pullRequestReview.getComments().size(),
UserInfoDTO.fromUser(pullRequestReview.getAuthor()),
PullRequestBaseInfoDTO.fromPullRequest(pullRequestReview.getPullRequest()),
pullRequestReview.getHtmlUrl(),
pullRequestReview.getSubmittedAt());
}

public static PullRequestReviewInfoDTO fromIssueComment(IssueComment issueComment) {
return new PullRequestReviewInfoDTO(
issueComment.getId(),
false,
PullRequestReview.State.COMMENTED,
0,
UserInfoDTO.fromUser(issueComment.getAuthor()),
PullRequestBaseInfoDTO.fromIssue(issueComment.getIssue()),
issueComment.getHtmlUrl(),
issueComment.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package de.tum.in.www1.hephaestus.gitprovider.pullrequestreview;

import de.tum.in.www1.hephaestus.gitprovider.issuecomment.IssueComment;
import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequestBaseInfoDTO;
import de.tum.in.www1.hephaestus.gitprovider.user.UserInfoDTO;
import de.tum.in.www1.hephaestus.leaderboard.ScoringService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

@Component
public class PullRequestReviewInfoDTOConverter implements Converter<PullRequestReview, PullRequestReviewInfoDTO> {

@Autowired
private ScoringService scoringService;

@Override
public PullRequestReviewInfoDTO convert(@NonNull PullRequestReview source) {
return new PullRequestReviewInfoDTO(
source.getId(),
source.isDismissed(),
source.getState(),
source.getComments().size(),
UserInfoDTO.fromUser(source.getAuthor()),
PullRequestBaseInfoDTO.fromPullRequest(source.getPullRequest()),
source.getHtmlUrl(),
(int) scoringService.calculateReviewScore(List.of(source)),
source.getSubmittedAt()
);
}

public PullRequestReviewInfoDTO convert(@NonNull IssueComment source) {
return new PullRequestReviewInfoDTO(
source.getId(),
false,
PullRequestReview.State.COMMENTED,
0,
UserInfoDTO.fromUser(source.getAuthor()),
PullRequestBaseInfoDTO.fromIssue(source.getIssue()),
source.getHtmlUrl(),
(int) scoringService.calculateReviewScore(source),
source.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import de.tum.in.www1.hephaestus.gitprovider.issue.Issue;
import de.tum.in.www1.hephaestus.gitprovider.issuecomment.IssueComment;
import de.tum.in.www1.hephaestus.gitprovider.issuecomment.IssueCommentRepository;
import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequestInfoDTO;
import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequestRepository;
import de.tum.in.www1.hephaestus.gitprovider.pullrequestreview.PullRequestReviewInfoDTO;
import de.tum.in.www1.hephaestus.gitprovider.pullrequestreview.PullRequestReviewInfoDTOConverter;
import de.tum.in.www1.hephaestus.gitprovider.pullrequestreview.PullRequestReviewRepository;
import de.tum.in.www1.hephaestus.gitprovider.repository.RepositoryInfoDTO;
import de.tum.in.www1.hephaestus.gitprovider.repository.RepositoryRepository;
Expand All @@ -27,24 +28,18 @@
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);

private final UserRepository userRepository;
private final RepositoryRepository repositoryRepository;
private final PullRequestRepository pullRequestRepository;
private final PullRequestReviewRepository pullRequestReviewRepository;
private final IssueCommentRepository issueCommentRepository;

public UserService(
UserRepository userRepository,
RepositoryRepository repositoryRepository,
PullRequestRepository pullRequestRepository,
PullRequestReviewRepository pullRequestReviewRepository,
IssueCommentRepository issueCommentRepository) {
this.userRepository = userRepository;
this.repositoryRepository = repositoryRepository;
this.pullRequestRepository = pullRequestRepository;
this.pullRequestReviewRepository = pullRequestReviewRepository;
this.issueCommentRepository = issueCommentRepository;
}
@Autowired
private UserRepository userRepository;
@Autowired
private RepositoryRepository repositoryRepository;
@Autowired
private PullRequestRepository pullRequestRepository;
@Autowired
private PullRequestReviewRepository pullRequestReviewRepository;
@Autowired
private IssueCommentRepository issueCommentRepository;
@Autowired
private PullRequestReviewInfoDTOConverter pullRequestReviewInfoDTOConverter;

@Transactional
public Optional<UserProfileDTO> getUserProfile(String login) {
Expand Down Expand Up @@ -72,13 +67,13 @@ public Optional<UserProfileDTO> getUserProfile(String login) {
List<PullRequestReviewInfoDTO> reviewActivity = pullRequestReviewRepository
.findAllByAuthorLoginSince(login, OffsetDateTime.now().minusDays(7))
.stream()
.map(PullRequestReviewInfoDTO::fromPullRequestReview)
.map(pullRequestReviewInfoDTOConverter::convert)
.collect(Collectors.toCollection(ArrayList::new));
reviewActivity.addAll(
issueCommentRepository
.findAllByAuthorLoginSince(login, OffsetDateTime.now().minusDays(7), true)
.stream()
.map(PullRequestReviewInfoDTO::fromIssueComment)
.map(pullRequestReviewInfoDTOConverter::convert)
.toList()
);
reviewActivity.sort(Comparator.comparing(PullRequestReviewInfoDTO::submittedAt).reversed());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.stream.IntStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

Expand All @@ -26,20 +27,16 @@ public class LeaderboardService {

private static final Logger logger = LoggerFactory.getLogger(LeaderboardService.class);

private final PullRequestReviewRepository pullRequestReviewRepository;
private final IssueCommentRepository issueCommentRepository;
@Autowired
private PullRequestReviewRepository pullRequestReviewRepository;
@Autowired
private IssueCommentRepository issueCommentRepository;
@Autowired
private ScoringService scoringService;

@Value("${monitoring.timeframe}")
private int timeframe;

public LeaderboardService(
PullRequestReviewRepository pullRequestReviewRepository,
IssueCommentRepository issueCommentRepository
) {
this.pullRequestReviewRepository = pullRequestReviewRepository;
this.issueCommentRepository = issueCommentRepository;
}

@Transactional
public List<LeaderboardEntryDTO> createLeaderboard(
OffsetDateTime after,
Expand Down Expand Up @@ -166,90 +163,8 @@ private int calculateTotalScore(List<PullRequestReview> reviews, int numberOfIss
double totalScore = reviewsByPullRequestId
.values()
.stream()
.map(pullRequestReviews -> {
// All reviews are for the same pull request
int complexityScore = calculateComplexityScore(pullRequestReviews.get(0).getPullRequest());

double approvalWeight = 2.0;
double approvalScore = pullRequestReviews
.stream()
.filter(review -> review.getState() == PullRequestReview.State.APPROVED)
.filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId())
.map(review -> approvalWeight * calculateCodeReviewBonus(review.getComments().size(), complexityScore))
.reduce(0.0, Double::sum);

double changesRequestedWeight = 2.5;
double changesRequestedScore = pullRequestReviews
.stream()
.filter(review -> review.getState() == PullRequestReview.State.CHANGES_REQUESTED)
.filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId())
.map(review -> changesRequestedWeight * calculateCodeReviewBonus(review.getComments().size(), complexityScore))
.reduce(0.0, Double::sum);

double commentWeight = 1.5;
double commentScore = pullRequestReviews
.stream()
.filter(review -> review.getState() == PullRequestReview.State.COMMENTED || review.getState() == PullRequestReview.State.UNKNOWN)
.filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId())
.map(review -> commentWeight * calculateCodeReviewBonus(review.getComments().size(), complexityScore))
.reduce(0.0, Double::sum);

double issueCommentScore = commentWeight * numberOfIssueComments;

double interactionScore = approvalScore + changesRequestedScore + commentScore + issueCommentScore;
return 10 * interactionScore * complexityScore / (interactionScore + complexityScore);
})
.map(pullRequestReviews -> scoringService.calculateReviewScore(pullRequestReviews, numberOfIssueComments))
.reduce(0.0, Double::sum);
return (int) Math.ceil(totalScore);
}

/**
* Calculates the complexity score for a given pull request.
* Possible values: 1, 3, 7, 17, 33.
* Taken from the original leaderboard implementation script.
*
* @param pullRequest
* @return score
*/
private int calculateComplexityScore(PullRequest pullRequest) {
Double complexityScore =
((pullRequest.getChangedFiles() * 3) +
(pullRequest.getCommits() * 0.5) +
pullRequest.getAdditions() +
pullRequest.getDeletions()) /
10;
if (complexityScore < 10) {
return 1; // Simple
} else if (complexityScore < 50) {
return 3; // Medium
} else if (complexityScore < 100) {
return 7; // Large
} else if (complexityScore < 500) {
return 17; // Huge
}
return 33; // Overly complex
}

/**
* Calculates the code review bonus for a given number of code comments and complexity score.
* The bonus is a value between 0 and 2.
* Taken from the original leaderboard implementation script.
*
* @param codeComments
* @param complexityScore
* @return bonus
*/
private double calculateCodeReviewBonus(int codeComments, int complexityScore) {
double maxBonus = 2;

double codeReviewBonus = 1;
if (codeComments < complexityScore) {
// Function goes from 0 at codeComments = 0 to 1 at codeComments = complexityScore
codeReviewBonus += 2 * Math.sqrt(complexityScore) * Math.sqrt(codeComments) / (codeComments + complexityScore);
} else {
// Saturate at 1
codeReviewBonus += 1;
}
return codeReviewBonus / 2 * maxBonus;
}
}
Loading

0 comments on commit f95852c

Please sign in to comment.