diff --git a/server/application-server/openapi.yaml b/server/application-server/openapi.yaml index d01f4540..6fbf65bc 100644 --- a/server/application-server/openapi.yaml +++ b/server/application-server/openapi.yaml @@ -219,6 +219,7 @@ components: - htmlUrl - id - isDismissed + - score - state type: object properties: @@ -243,6 +244,9 @@ components: $ref: "#/components/schemas/PullRequestBaseInfo" htmlUrl: type: string + score: + type: integer + format: int32 submittedAt: type: string format: date-time diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequest/PullRequestRepository.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequest/PullRequestRepository.java index 324d1e13..7dc6f946 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequest/PullRequestRepository.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequest/PullRequestRepository.java @@ -33,4 +33,15 @@ SELECT MIN(p.createdAt) List findAssignedByLoginAndStates( @Param("assigneeLogin") String assigneeLogin, @Param("states") Set 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 findByRepositoryAndNumber(@Param("repository") de.tum.in.www1.hephaestus.gitprovider.repository.Repository repository, @Param("number") int number); } \ No newline at end of file diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequestreview/PullRequestReviewInfoDTO.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequestreview/PullRequestReviewInfoDTO.java index fd5049fa..1f494042 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequestreview/PullRequestReviewInfoDTO.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequestreview/PullRequestReviewInfoDTO.java @@ -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; @@ -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() - ); - } } diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequestreview/PullRequestReviewInfoDTOConverter.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequestreview/PullRequestReviewInfoDTOConverter.java new file mode 100644 index 00000000..c15ffc4c --- /dev/null +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/pullrequestreview/PullRequestReviewInfoDTOConverter.java @@ -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 { + + @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() + ); + } +} diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/UserService.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/UserService.java index 75159b16..504256db 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/UserService.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/UserService.java @@ -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; @@ -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 getUserProfile(String login) { @@ -72,13 +67,13 @@ public Optional getUserProfile(String login) { List 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()); diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardService.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardService.java index 2ada550b..448fcac2 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardService.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardService.java @@ -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; @@ -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 createLeaderboard( OffsetDateTime after, @@ -166,90 +163,8 @@ private int calculateTotalScore(List 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; - } } diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/ScoringService.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/ScoringService.java new file mode 100644 index 00000000..9bfa7aaf --- /dev/null +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/ScoringService.java @@ -0,0 +1,129 @@ +package de.tum.in.www1.hephaestus.leaderboard; + +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.pullrequest.PullRequest; +import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequestRepository; +import de.tum.in.www1.hephaestus.gitprovider.pullrequestreview.PullRequestReview; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class ScoringService { + + private final Logger logger = LoggerFactory.getLogger(ScoringService.class); + + @Autowired + private PullRequestRepository pullRequestRepository; + + public double WEIGHT_APPROVAL = 2.0; + public double WEIGHT_CHANGESREQUESTED = 2.5; + public double WEIGHT_COMMENT = 1.5; + + public double calculateReviewScore(List pullRequestReviews) { + return calculateReviewScore(pullRequestReviews, 0); + } + + public double calculateReviewScore(List pullRequestReviews, int numberOfIssueComments) { + // All reviews are for the same pull request + int complexityScore = calculateComplexityScore(pullRequestReviews.get(0).getPullRequest()); + + double approvalScore = pullRequestReviews + .stream() + .filter(review -> review.getState() == PullRequestReview.State.APPROVED) + .filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId()) + .map(review -> WEIGHT_APPROVAL * calculateCodeReviewBonus(review.getComments().size(), complexityScore)) + .reduce(0.0, Double::sum); + + double changesRequestedScore = pullRequestReviews + .stream() + .filter(review -> review.getState() == PullRequestReview.State.CHANGES_REQUESTED) + .filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId()) + .map(review -> WEIGHT_CHANGESREQUESTED * calculateCodeReviewBonus(review.getComments().size(), complexityScore)) + .reduce(0.0, Double::sum); + + 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 -> WEIGHT_COMMENT * calculateCodeReviewBonus(review.getComments().size(), complexityScore)) + .reduce(0.0, Double::sum); + + double issueCommentScore = WEIGHT_COMMENT * numberOfIssueComments; + + double interactionScore = approvalScore + changesRequestedScore + commentScore + issueCommentScore; + return 10 * interactionScore * complexityScore / (interactionScore + complexityScore); + } + + public double calculateReviewScore(IssueComment issueComment) { + Issue issue = issueComment.getIssue(); + PullRequest pullRequest; + if (issue.isPullRequest()) { + pullRequest = (PullRequest) issue; + } else { + var optionalPR = pullRequestRepository.findByRepositoryAndNumber(issue.getRepository(), issue.getNumber()); + if (optionalPR.isEmpty()) { + logger.error("Issue comment is not associated with a pull request."); + return 0; + } + pullRequest = optionalPR.get(); + } + + int complexityScore = calculateComplexityScore(pullRequest); + + return 10 * WEIGHT_COMMENT * complexityScore / (WEIGHT_COMMENT + complexityScore); + } + + /** + * 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; + } + + /** + * 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 + } +} diff --git a/webapp/src/app/core/modules/openapi/model/pull-request-review-info.ts b/webapp/src/app/core/modules/openapi/model/pull-request-review-info.ts index d9f4d53b..dba6c5f1 100644 --- a/webapp/src/app/core/modules/openapi/model/pull-request-review-info.ts +++ b/webapp/src/app/core/modules/openapi/model/pull-request-review-info.ts @@ -21,6 +21,7 @@ export interface PullRequestReviewInfo { author?: UserInfo; pullRequest?: PullRequestBaseInfo; htmlUrl: string; + score: number; submittedAt?: string; } export namespace PullRequestReviewInfo { diff --git a/webapp/src/app/home/leaderboard/leaderboard.component.html b/webapp/src/app/home/leaderboard/leaderboard.component.html index 05e98f37..c56bdadb 100644 --- a/webapp/src/app/home/leaderboard/leaderboard.component.html +++ b/webapp/src/app/home/leaderboard/leaderboard.component.html @@ -3,7 +3,10 @@ Rank Contributor - Score + + + Score + Activity diff --git a/webapp/src/app/home/leaderboard/leaderboard.component.ts b/webapp/src/app/home/leaderboard/leaderboard.component.ts index ef046e95..ae43cd88 100644 --- a/webapp/src/app/home/leaderboard/leaderboard.component.ts +++ b/webapp/src/app/home/leaderboard/leaderboard.component.ts @@ -14,6 +14,8 @@ import { TableHeaderDirective } from 'app/ui/table/table-header.directive'; import { TableRowDirective } from 'app/ui/table/table-row.directive'; import { TableComponent } from 'app/ui/table/table.component'; import { ReviewsPopoverComponent } from './reviews-popover/reviews-popover.component'; +import { HlmIconComponent, provideIcons } from '@spartan-ng/ui-icon-helm'; +import { lucideAward } from '@ng-icons/lucide'; @Component({ selector: 'app-leaderboard', @@ -29,8 +31,10 @@ import { ReviewsPopoverComponent } from './reviews-popover/reviews-popover.compo TableRowDirective, ReviewsPopoverComponent, NgIconComponent, + HlmIconComponent, RouterLink ], + providers: [provideIcons({ lucideAward })], templateUrl: './leaderboard.component.html' }) export class LeaderboardComponent { diff --git a/webapp/src/app/home/leaderboard/legend/legend.component.html b/webapp/src/app/home/leaderboard/legend/legend.component.html index 5f8c8eea..05667b27 100644 --- a/webapp/src/app/home/leaderboard/legend/legend.component.html +++ b/webapp/src/app/home/leaderboard/legend/legend.component.html @@ -36,14 +36,6 @@

Icons

more complex pull requests contribute more. The final score balances your interactions with the complexity of the work reviewed, highlighting both your engagement and the difficulty of the tasks you've undertaken. This score reflects your impact but does not directly measure time invested or work quality. - - [source] -

diff --git a/webapp/src/app/home/leaderboard/reviews-popover/reviews-popover.component.html b/webapp/src/app/home/leaderboard/reviews-popover/reviews-popover.component.html index 39cbad51..6e21b717 100644 --- a/webapp/src/app/home/leaderboard/reviews-popover/reviews-popover.component.html +++ b/webapp/src/app/home/leaderboard/reviews-popover/reviews-popover.component.html @@ -25,7 +25,7 @@

Reviewed PRs

} - +
@for (pullRequest of sortedReviewedPRs(); track pullRequest.id) { diff --git a/webapp/src/app/user/review-activity-card/review-activity-card.component.html b/webapp/src/app/user/review-activity-card/review-activity-card.component.html index b4c09763..f89d89b9 100644 --- a/webapp/src/app/user/review-activity-card/review-activity-card.component.html +++ b/webapp/src/app/user/review-activity-card/review-activity-card.component.html @@ -11,10 +11,23 @@ } @else {
-
- {{ relativeActivityTime() }} - in - {{ this.repositoryName() }} #{{ this.pullRequest()?.number }} +
+ + {{ relativeActivityTime() }} + in + {{ this.repositoryName() }} #{{ this.pullRequest()?.number }} + +
diff --git a/webapp/src/app/user/review-activity-card/review-activity-card.component.ts b/webapp/src/app/user/review-activity-card/review-activity-card.component.ts index 436cfcb9..241e5c90 100644 --- a/webapp/src/app/user/review-activity-card/review-activity-card.component.ts +++ b/webapp/src/app/user/review-activity-card/review-activity-card.component.ts @@ -1,11 +1,15 @@ import { Component, computed, input } from '@angular/core'; import { PullRequestBaseInfo, PullRequestReviewInfo } from '@app/core/modules/openapi'; -import { NgIcon } from '@ng-icons/core'; +import { NgIcon, provideIcons } from '@ng-icons/core'; import { octCheck, octComment, octFileDiff, octGitPullRequest, octGitPullRequestClosed } from '@ng-icons/octicons'; import { HlmCardModule } from '@spartan-ng/ui-card-helm'; import { HlmSkeletonComponent } from '@spartan-ng/ui-skeleton-helm'; +import { HlmIconComponent } from '@spartan-ng/ui-icon-helm'; +import { HlmTooltipTriggerDirective } from '@spartan-ng/ui-tooltip-helm'; +import { HlmButtonModule } from '@spartan-ng/ui-button-helm'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; +import { lucideAward } from '@ng-icons/lucide'; dayjs.extend(relativeTime); @@ -20,7 +24,8 @@ type ReviewStateCases = { @Component({ selector: 'app-review-activity-card', templateUrl: './review-activity-card.component.html', - imports: [NgIcon, HlmCardModule, HlmSkeletonComponent], + imports: [NgIcon, HlmCardModule, HlmSkeletonComponent, HlmIconComponent, HlmTooltipTriggerDirective, HlmButtonModule], + providers: [provideIcons({ lucideAward })], standalone: true }) export class ReviewActivityCardComponent { @@ -37,6 +42,7 @@ export class ReviewActivityCardComponent { htmlUrl = input(); pullRequest = input(); repositoryName = input(); + score = input(); relativeActivityTime = computed(() => dayjs(this.submittedAt()).fromNow()); displayPullRequestTitle = computed(() => (this.pullRequest()?.title ?? '').replace(/`([^`]+)`/g, '$1')); diff --git a/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts b/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts index d6839212..93884e71 100644 --- a/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts +++ b/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts @@ -6,6 +6,7 @@ type FlatArgs = { isLoading: boolean; reviewActivityCreatedAt: string; reviewActivityState: string; + reviewActivityScore: number; pullRequestNumber: number; pullRequestState: string; pullRequestUrl: string; @@ -18,6 +19,7 @@ function flatArgsToProps(args: FlatArgs) { isLoading: args.isLoading, createdAt: dayjs(args.reviewActivityCreatedAt), state: args.reviewActivityState, + score: args.reviewActivityScore, pullRequest: { number: args.pullRequestNumber, title: args.pullRequestTitle, @@ -34,6 +36,7 @@ const meta: Meta = { isLoading: false, reviewActivityCreatedAt: dayjs().subtract(4, 'days').toISOString(), reviewActivityState: 'CHANGES_REQUESTED', + reviewActivityScore: 3, pullRequestNumber: 100, pullRequestTitle: '`Leaderboard`: Custom Sliding Time Window', pullRequestUrl: 'https://github.com/ls1intum/Hephaestus/pull/100', diff --git a/webapp/src/app/user/user-profile.component.html b/webapp/src/app/user/user-profile.component.html index 59f21ac3..74e7c1ee 100644 --- a/webapp/src/app/user/user-profile.component.html +++ b/webapp/src/app/user/user-profile.component.html @@ -29,6 +29,7 @@

Latest Review Activity

[htmlUrl]="activity?.htmlUrl" [pullRequest]="activity?.pullRequest" [repositoryName]="activity?.pullRequest?.repository?.name" + [score]="activity?.score" /> } @if (!showSkeleton && (!query.data()?.reviewActivity || query.data()?.reviewActivity?.length === 0)) { diff --git a/webapp/src/app/user/user-profile.component.ts b/webapp/src/app/user/user-profile.component.ts index c039a090..658c28a9 100644 --- a/webapp/src/app/user/user-profile.component.ts +++ b/webapp/src/app/user/user-profile.component.ts @@ -1,13 +1,10 @@ import { Component, inject } from '@angular/core'; -import { NgIconComponent } from '@ng-icons/core'; import { PullRequestInfo, PullRequestReviewInfo, UserService } from 'app/core/modules/openapi'; import { HlmAvatarModule } from '@spartan-ng/ui-avatar-helm'; import { HlmSkeletonModule } from '@spartan-ng/ui-skeleton-helm'; import { ActivatedRoute } from '@angular/router'; import { injectQuery } from '@tanstack/angular-query-experimental'; import { HlmIconModule } from 'libs/ui/ui-icon-helm/src/index'; -import { BrnTooltipContentDirective } from '@spartan-ng/ui-tooltip-brain'; -import { HlmTooltipComponent, HlmTooltipTriggerDirective } from '@spartan-ng/ui-tooltip-helm'; import { HlmButtonModule } from '@spartan-ng/ui-button-helm'; import { HlmScrollAreaComponent } from '@spartan-ng/ui-scrollarea-helm'; import { HlmAlertModule } from '@spartan-ng/ui-alert-helm'; @@ -22,15 +19,11 @@ import { UserHeaderComponent } from './header/header.component'; standalone: true, imports: [ LucideAngularModule, - NgIconComponent, ReviewActivityCardComponent, IssueCardComponent, HlmAvatarModule, HlmSkeletonModule, HlmIconModule, - HlmTooltipComponent, - HlmTooltipTriggerDirective, - BrnTooltipContentDirective, HlmButtonModule, HlmScrollAreaComponent, UserHeaderComponent, diff --git a/webapp/src/libs/ui/ui-card-helm/src/lib/hlm-card-content.directive.ts b/webapp/src/libs/ui/ui-card-helm/src/lib/hlm-card-content.directive.ts index 8d8a2342..07a2d84e 100644 --- a/webapp/src/libs/ui/ui-card-helm/src/lib/hlm-card-content.directive.ts +++ b/webapp/src/libs/ui/ui-card-helm/src/lib/hlm-card-content.directive.ts @@ -7,7 +7,7 @@ export const cardContentVariants = cva('pt-0', { variants: { variant: { default: 'p-6', - profile: 'flex flex-col gap-2' + profile: 'flex flex-col gap-1' } }, defaultVariants: {}