Skip to content
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

Display Review Score on User Profiles #167

Merged
merged 11 commits into from
Nov 21, 2024
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 -> {
GODrums marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading