diff --git a/server/application-server/openapi.yaml b/server/application-server/openapi.yaml index 53a612d0..2b04452c 100644 --- a/server/application-server/openapi.yaml +++ b/server/application-server/openapi.yaml @@ -232,6 +232,9 @@ components: updatedAt: type: string format: date-time + number: + type: integer + format: int32 title: type: string url: @@ -450,11 +453,36 @@ components: type: integer format: int32 changesRequested: - type: integer - format: int32 + type: array + items: + $ref: "#/components/schemas/PullRequestReviewDTO" approvals: - type: integer - format: int32 + type: array + items: + $ref: "#/components/schemas/PullRequestReviewDTO" comments: + type: array + items: + $ref: "#/components/schemas/PullRequestReviewDTO" + PullRequestReviewDTO: + type: object + properties: + id: type: integer - format: int32 + format: int64 + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + submittedAt: + type: string + format: date-time + state: + type: string + enum: + - COMMENTED + - APPROVED + - CHANGES_REQUESTED + - DISMISSED diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequest.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequest.java index 7822ba3a..46c89016 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequest.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequest.java @@ -30,6 +30,9 @@ @NoArgsConstructor @ToString(callSuper = true) public class PullRequest extends BaseGitServiceEntity { + + private int number; + @NonNull private String title; diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequestConverter.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequestConverter.java index ba10d1d1..ce054c74 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequestConverter.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequestConverter.java @@ -20,6 +20,7 @@ public PullRequest convert(@NonNull GHPullRequest source) { IssueState state = convertState(source.getState()); PullRequest pullRequest = new PullRequest(); convertBaseFields(source, pullRequest); + pullRequest.setNumber(source.getNumber()); pullRequest.setTitle(source.getTitle()); pullRequest.setUrl(source.getHtmlUrl().toString()); pullRequest.setState(state); diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/review/PullRequestReviewDTO.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/review/PullRequestReviewDTO.java new file mode 100644 index 00000000..92e2564b --- /dev/null +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/review/PullRequestReviewDTO.java @@ -0,0 +1,10 @@ +package de.tum.in.www1.hephaestus.codereview.pullrequest.review; + +import java.time.OffsetDateTime; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record PullRequestReviewDTO(Long id, OffsetDateTime createdAt, OffsetDateTime updatedAt, + OffsetDateTime submittedAt, PullRequestReviewState state) { +} diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardEntry.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardEntry.java index 0faba666..28115cf1 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardEntry.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/LeaderboardEntry.java @@ -1,6 +1,8 @@ package de.tum.in.www1.hephaestus.leaderboard; import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.hephaestus.codereview.pullrequest.review.PullRequestReviewDTO; import de.tum.in.www1.hephaestus.codereview.user.UserType; import lombok.AllArgsConstructor; import lombok.Getter; @@ -19,7 +21,7 @@ public class LeaderboardEntry { private UserType type; private int score; private int rank; - private int changesRequested; - private int approvals; - private int comments; + private PullRequestReviewDTO[] changesRequested; + private PullRequestReviewDTO[] approvals; + private PullRequestReviewDTO[] comments; } 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 57de7fc1..952ec62f 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 @@ -1,17 +1,24 @@ package de.tum.in.www1.hephaestus.leaderboard; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import de.tum.in.www1.hephaestus.codereview.pullrequest.PullRequest; +import de.tum.in.www1.hephaestus.codereview.pullrequest.review.PullRequestReviewDTO; import de.tum.in.www1.hephaestus.codereview.user.User; import de.tum.in.www1.hephaestus.codereview.user.UserService; import de.tum.in.www1.hephaestus.codereview.user.UserType; @@ -22,6 +29,9 @@ public class LeaderboardService { private final UserService userService; + @Value("${monitoring.timeframe}") + private int timeframe; + public LeaderboardService(UserService userService) { this.userService = userService; } @@ -32,32 +42,47 @@ public List createLeaderboard() { List users = userService.getAllUsers(); logger.info("Found " + users.size() + " users"); + OffsetDateTime cutOffTime = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24 * timeframe) + .toInstant().atOffset(ZoneOffset.UTC); + List leaderboard = users.stream().map(user -> { if (user.getType() != UserType.USER) { return null; } - int comments = user.getIssueComments().size(); - AtomicInteger changesRequested = new AtomicInteger(0); - AtomicInteger changesApproved = new AtomicInteger(0); + logger.info("User: " + user.getLogin()); AtomicInteger score = new AtomicInteger(0); - user.getReviews().stream().forEach(review -> { - switch (review.getState()) { - case CHANGES_REQUESTED: - changesRequested.incrementAndGet(); - break; - case APPROVED: - changesApproved.incrementAndGet(); - break; - default: - break; - } - score.addAndGet(calculateScore(review.getPullRequest())); - }); + Set changesRequestedSet = new HashSet<>(); + Set approvedSet = new HashSet<>(); + Set commentSet = new HashSet<>(); + + user.getReviews().stream() + .filter(review -> (review.getCreatedAt() != null && review.getCreatedAt().isAfter(cutOffTime)) + || (review.getUpdatedAt() != null && review.getUpdatedAt().isAfter(cutOffTime))) + .forEach(review -> { + if (review.getPullRequest().getAuthor().getLogin().equals(user.getLogin())) { + return; + } + PullRequestReviewDTO reviewDTO = new PullRequestReviewDTO(review.getId(), review.getCreatedAt(), + review.getUpdatedAt(), review.getSubmittedAt(), review.getState()); + switch (review.getState()) { + case CHANGES_REQUESTED: + changesRequestedSet.add(reviewDTO); + break; + case APPROVED: + approvedSet.add(reviewDTO); + break; + default: + commentSet.add(reviewDTO); + break; + } + score.addAndGet(calculateScore(review.getPullRequest())); + }); return new LeaderboardEntry(user.getLogin(), user.getAvatarUrl(), user.getName(), user.getType(), score.get(), 0, // preliminary rank - changesRequested.get(), - changesApproved.get(), comments); + changesRequestedSet.toArray(new PullRequestReviewDTO[changesRequestedSet.size()]), + approvedSet.toArray(new PullRequestReviewDTO[approvedSet.size()]), + commentSet.toArray(new PullRequestReviewDTO[commentSet.size()])); }).filter(Objects::nonNull).collect(Collectors.toCollection(ArrayList::new)); // update ranks by score diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/scheduler/GitHubDataSyncService.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/scheduler/GitHubDataSyncService.java index 6cc99c55..934ba522 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/scheduler/GitHubDataSyncService.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/scheduler/GitHubDataSyncService.java @@ -4,21 +4,27 @@ import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import org.kohsuke.github.GHDirection; +import org.kohsuke.github.GHIssueState; import org.kohsuke.github.GHObject; import org.kohsuke.github.GHPullRequest; import org.kohsuke.github.GHPullRequestReviewComment; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.PagedIterator; +import org.kohsuke.github.GHPullRequestQueryBuilder.Sort; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import de.tum.in.www1.hephaestus.codereview.comment.IssueComment; import de.tum.in.www1.hephaestus.codereview.comment.IssueCommentConverter; @@ -51,6 +57,7 @@ public class GitHubDataSyncService { @Value("${monitoring.timeframe}") private int timeframe; + private Date cutOffTime; private GitHub github; @@ -68,6 +75,9 @@ public class GitHubDataSyncService { private final PullRequestReviewCommentConverter reviewCommentConverter; private final UserConverter userConverter; + private Set users = new HashSet<>(); + private Set reviews = new HashSet<>(); + public GitHubDataSyncService(RepositoryRepository repositoryRepository, PullRequestRepository pullRequestRepository, PullRequestReviewRepository prReviewRepository, IssueCommentRepository commentRepository, PullRequestReviewCommentRepository reviewCommentRepository, @@ -94,6 +104,8 @@ public GitHubDataSyncService(RepositoryRepository repositoryRepository, PullRequ public void syncData() { int successfullySyncedRepositories = 0; + this.cutOffTime = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24 * timeframe); + logger.info("Cut-off time for the data sync: " + cutOffTime); for (String repositoryName : repositoriesToMonitor) { try { syncRepository(repositoryName); @@ -149,94 +161,113 @@ public Repository fetchRepository(String nameWithOwner) throws IOException { repository = repositoryRepository.save(repository); Set prs = getPullRequestsFromGHRepository(ghRepo, repository); - logger.info("Found " + prs.size() + " PRs"); + logger.info("Found total of " + prs.size() + " PRs"); repository.setPullRequests(prs); - pullRequestRepository.saveAll(prs); + pullRequestRepository.saveAll(prs); + userRepository.saveAll(users); repositoryRepository.save(repository); + prReviewRepository.saveAll(reviews); return repository; } private Set getPullRequestsFromGHRepository(GHRepository ghRepo, Repository repository) throws IOException { - // Retrieve PRs in pages of 10 - return ghRepo.queryPullRequests().list().withPageSize(20).toList().stream() - .takeWhile(pr -> isResourceRecent(pr)).map(pr -> { - PullRequest pullRequest = pullRequestRepository.save(pullRequestConverter.convert(pr)); - pullRequest.setRepository(repository); - try { - Collection comments = getCommentsFromGHPullRequest(pr, pullRequest); - comments = commentRepository.saveAll(comments); - for (IssueComment c : comments) { - pullRequest.addComment(c); - } - } catch (IOException e) { - logger.error("Error while fetching PR comments!"); - pullRequest.setComments(new HashSet<>()); - } - try { - User prAuthor = getUserFromGHUser(pr.getUser()); - prAuthor.addPullRequest(pullRequest); - pullRequest.setAuthor(prAuthor); - } catch (IOException e) { - logger.error("Error while fetching PR author!"); - pullRequest.setAuthor(null); - } + // Iterator allows us to handle pullrequests without knowing the next ones + PagedIterator pullRequests = ghRepo.queryPullRequests() + .state(GHIssueState.ALL) + .sort(Sort.UPDATED) + .direction(GHDirection.DESC) + .list().withPageSize(100).iterator(); + Set prs = new HashSet<>(); - try { - Collection reviews = pr.listReviews().toList().stream() - .takeWhile(prr -> isResourceRecent(prr)).map(review -> { - PullRequestReview prReview = prReviewRepository - .save(reviewConverter.convert(review)); - try { - User reviewAuthor = getUserFromGHUser(review.getUser()); - reviewAuthor.addReview(prReview); - prReview.setAuthor(reviewAuthor); - } catch (IOException e) { - logger.error("Error while fetching review owner!"); - } - prReview.setPullRequest(pullRequest); - return prReview; - }).collect(Collectors.toSet()); - reviews = prReviewRepository.saveAll(reviews); - for (PullRequestReview prReview : reviews) { - pullRequest.addReview(prReview); + // Only fetch next page if all PRs are still within the timeframe + AtomicBoolean fetchStillInTimeframe = new AtomicBoolean(true); + + while (fetchStillInTimeframe.get() && pullRequests.hasNext()) { + List nextPage = pullRequests.nextPage(); + logger.info("Fetched " + nextPage.size() + " PRs from Github"); + prs.addAll(nextPage.stream().map(pr -> { + if (!isResourceRecent(pr)) { + fetchStillInTimeframe.set(false); + return null; + } + PullRequest pullRequest = pullRequestRepository.save(pullRequestConverter.convert(pr)); + pullRequest.setRepository(repository); + try { + User prAuthor = getUserFromGHUser(pr.getUser()); + prAuthor.addPullRequest(pullRequest); + pullRequest.setAuthor(prAuthor); + } catch (IOException e) { + // Dont mind this error as it occurs only for bots + pullRequest.setAuthor(null); + } + + try { + Set newReviews = pr.listReviews().toList().stream().map(review -> { + PullRequestReview prReview = prReviewRepository + .save(reviewConverter.convert(review)); + try { + User reviewAuthor = getUserFromGHUser(review.getUser()); + reviewAuthor.addReview(prReview); + prReview.setAuthor(reviewAuthor); + } catch (IOException e) { + // Dont mind this error as it occurs only for bots } - } catch (IOException e) { - logger.error("Error while fetching PR reviews!"); - pullRequest.setReviews(new HashSet<>()); + prReview.setPullRequest(pullRequest); + return prReview; + }).collect(Collectors.toSet()); + for (PullRequestReview prReview : newReviews) { + pullRequest.addReview(prReview); + reviews.add(prReview); } + logger.info("Found " + newReviews.size() + " reviews for PR #" + pullRequest.getNumber()); + } catch (IOException e) { + logger.error("Error while fetching PR reviews!"); + } - try { - pr.listReviewComments().withPageSize(20).toList().stream().takeWhile(rc -> isResourceRecent(rc)) - .forEach(c -> handleSinglePullRequestReviewComment(c)); - } catch (IOException e) { - logger.error("Error while fetching PR review comments!"); + try { + List prrComments = pr.listReviewComments().withPageSize(100).toList() + .stream() + .takeWhile(rc -> isResourceRecent(rc)) + .map(c -> handleSinglePullRequestReviewComment(c)).filter(Objects::nonNull).toList(); + reviewCommentRepository.saveAll(prrComments); + } catch (IOException e) { + logger.error("Error while fetching PR review comments!"); + } + + try { + Collection comments = getCommentsFromGHPullRequest(pr, pullRequest); + // comments = commentRepository.saveAll(comments); + for (IssueComment c : comments) { + pullRequest.addComment(c); } + } catch (IOException e) { + logger.error("Error while fetching PR comments!"); + } - return pullRequest; - }).collect(Collectors.toSet()); + return pullRequest; + }).filter(Objects::nonNull).collect(Collectors.toSet())); + } + return prs; } - private void handleSinglePullRequestReviewComment(GHPullRequestReviewComment comment) { - PullRequestReviewComment c = reviewCommentRepository.save(reviewCommentConverter.convert(comment)); + private PullRequestReviewComment handleSinglePullRequestReviewComment(GHPullRequestReviewComment comment) { + PullRequestReviewComment prrc = reviewCommentRepository.save(reviewCommentConverter.convert(comment)); - Optional review = prReviewRepository - .findByIdWithEagerComments(comment.getPullRequestReviewId()); - if (review.isPresent()) { - PullRequestReview prReview = review.get(); - c.setReview(prReview); - User commentAuthor; - try { - commentAuthor = getUserFromGHUser(comment.getUser()); - commentAuthor.addReviewComment(c); - } catch (IOException e) { - logger.error("Error while fetching author!"); - commentAuthor = null; - } - c.setAuthor(commentAuthor); - prReview.addComment(c); + PullRequestReview prReview = getPRRFromReviewId(comment.getPullRequestReviewId()); + prrc.setReview(prReview); + User commentAuthor; + try { + commentAuthor = getUserFromGHUser(comment.getUser()); + commentAuthor.addReviewComment(prrc); + } catch (IOException e) { + // Dont mind this error as it occurs only for bots + commentAuthor = null; } + prrc.setAuthor(commentAuthor); + prReview.addComment(prrc); + return prrc; } /** @@ -245,12 +276,11 @@ private void handleSinglePullRequestReviewComment(GHPullRequestReviewComment com * @param pr The GH pull request. * @param pullRequest Stored PR to which the comments belong. * @return The comments of the given pull request. - * @throws IOException + * @throws IOException when fetching the comments fails */ - @Transactional private Set getCommentsFromGHPullRequest(GHPullRequest pr, PullRequest pullRequest) throws IOException { - return pr.queryComments().list().withPageSize(20).toList().stream() + return pr.queryComments().list().withPageSize(100).toList().stream() .map(comment -> { IssueComment c = commentRepository.save(commentConverter.convert(comment)); c.setPullRequest(pullRequest); @@ -259,7 +289,7 @@ private Set getCommentsFromGHPullRequest(GHPullRequest pr, PullReq commentAuthor = getUserFromGHUser(comment.getUser()); commentAuthor.addIssueComment(c); } catch (IOException e) { - logger.error("Error while fetching author!"); + // Dont mind this error as it occurs only for bots commentAuthor = null; } c.setAuthor(commentAuthor); @@ -267,23 +297,60 @@ private Set getCommentsFromGHPullRequest(GHPullRequest pr, PullReq }).collect(Collectors.toSet()); } + /** + * Gets the corresponding User entity instance: cache -> database -> create new + * user + * + * @param user GHUser instance + * @return entity instance + */ private User getUserFromGHUser(org.kohsuke.github.GHUser user) { - User ghUser = userRepository.findUserEagerly(user.getLogin()).orElse(null); - if (ghUser == null) { - ghUser = userRepository.save(userConverter.convert(user)); + // Try to find user in cache + Optional ghUser = users.stream().filter(u -> u.getLogin().equals(user.getLogin())).findFirst(); + if (ghUser.isPresent()) { + return ghUser.get(); + } + // Try to find user in database + ghUser = userRepository.findUserEagerly(user.getLogin()); + if (ghUser.isPresent()) { + User u = ghUser.get(); + if (!users.contains(u)) { + users.add(u); + } + return u; + } + // Otherwise create new user + User u = userRepository.save(userConverter.convert(user)); + users.add(u); + return u; + } + + /** + * Gets the corresponding PullRequestReview from the cache. + * + * @implNote Assumes that it's executed after the review has been fetched from + * Github already + * @param reviewId + */ + private PullRequestReview getPRRFromReviewId(Long reviewId) { + Optional prReview = reviews.stream().filter(prr -> prr.getId().equals(reviewId)).findFirst(); + if (prReview.isPresent()) { + return prReview.get(); } - return ghUser; + logger.error("Cannot find PRR with ID " + reviewId); + return null; } /** - * Checks if the resource has been created within the last week. + * Checks if the resource has been created within the timeframe. * * @param obj * @return */ private boolean isResourceRecent(GHObject obj) { try { - return obj.getCreatedAt().after(new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24 * timeframe)); + return obj.getUpdatedAt() != null + && obj.getUpdatedAt().after(cutOffTime); } catch (IOException e) { logger.error("Error while fetching createdAt! Resource ID: " + obj.getId()); return false; diff --git a/webapp/src/app/core/modules/openapi/.openapi-generator/FILES b/webapp/src/app/core/modules/openapi/.openapi-generator/FILES index 1840a28c..e40c7a6a 100644 --- a/webapp/src/app/core/modules/openapi/.openapi-generator/FILES +++ b/webapp/src/app/core/modules/openapi/.openapi-generator/FILES @@ -21,6 +21,7 @@ model/leaderboard-entry.ts model/models.ts model/pull-request-dto.ts model/pull-request-review-comment.ts +model/pull-request-review-dto.ts model/pull-request-review.ts model/pull-request.ts model/repository-dto.ts diff --git a/webapp/src/app/core/modules/openapi/model/leaderboard-entry.ts b/webapp/src/app/core/modules/openapi/model/leaderboard-entry.ts index 91663d60..eeb83ebf 100644 --- a/webapp/src/app/core/modules/openapi/model/leaderboard-entry.ts +++ b/webapp/src/app/core/modules/openapi/model/leaderboard-entry.ts @@ -9,6 +9,7 @@ * https://openapi-generator.tech * Do not edit the class manually. */ +import { PullRequestReviewDTO } from './pull-request-review-dto'; export interface LeaderboardEntry { @@ -18,9 +19,9 @@ export interface LeaderboardEntry { type?: LeaderboardEntry.TypeEnum; score?: number; rank?: number; - changesRequested?: number; - approvals?: number; - comments?: number; + changesRequested?: Array; + approvals?: Array; + comments?: Array; } export namespace LeaderboardEntry { export type TypeEnum = 'USER' | 'BOT'; diff --git a/webapp/src/app/core/modules/openapi/model/models.ts b/webapp/src/app/core/modules/openapi/model/models.ts index 81d76b51..967af558 100644 --- a/webapp/src/app/core/modules/openapi/model/models.ts +++ b/webapp/src/app/core/modules/openapi/model/models.ts @@ -5,6 +5,7 @@ export * from './pull-request'; export * from './pull-request-dto'; export * from './pull-request-review'; export * from './pull-request-review-comment'; +export * from './pull-request-review-dto'; export * from './repository'; export * from './repository-dto'; export * from './user'; diff --git a/webapp/src/app/core/modules/openapi/model/pull-request-review-dto.ts b/webapp/src/app/core/modules/openapi/model/pull-request-review-dto.ts new file mode 100644 index 00000000..b4947f88 --- /dev/null +++ b/webapp/src/app/core/modules/openapi/model/pull-request-review-dto.ts @@ -0,0 +1,31 @@ +/** + * Hephaestus API + * API documentation for the Hephaestus application server. + * + * The version of the OpenAPI document: 0.0.1 + * Contact: felixtj.dietrich@tum.de + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface PullRequestReviewDTO { + id?: number; + createdAt?: string; + updatedAt?: string; + submittedAt?: string; + state?: PullRequestReviewDTO.StateEnum; +} +export namespace PullRequestReviewDTO { + export type StateEnum = 'COMMENTED' | 'APPROVED' | 'CHANGES_REQUESTED' | 'DISMISSED'; + export const StateEnum = { + Commented: 'COMMENTED' as StateEnum, + Approved: 'APPROVED' as StateEnum, + ChangesRequested: 'CHANGES_REQUESTED' as StateEnum, + Dismissed: 'DISMISSED' as StateEnum + }; +} + + diff --git a/webapp/src/app/core/modules/openapi/model/pull-request.ts b/webapp/src/app/core/modules/openapi/model/pull-request.ts index 0224aa04..f1fa06ad 100644 --- a/webapp/src/app/core/modules/openapi/model/pull-request.ts +++ b/webapp/src/app/core/modules/openapi/model/pull-request.ts @@ -19,6 +19,7 @@ export interface PullRequest { id?: number; createdAt?: string; updatedAt?: string; + number?: number; title: string; url: string; /** diff --git a/webapp/src/app/home/leaderboard/leaderboard.component.html b/webapp/src/app/home/leaderboard/leaderboard.component.html index af9a5c8d..73b269bd 100644 --- a/webapp/src/app/home/leaderboard/leaderboard.component.html +++ b/webapp/src/app/home/leaderboard/leaderboard.component.html @@ -24,22 +24,22 @@ {{ entry.score }} - @if (entry.changesRequested && entry.changesRequested > 0) { + @if (entry.changesRequested && entry.changesRequested.length > 0) {
- {{ entry.changesRequested }} + {{ entry.changesRequested.length }}
} - @if (entry.approvals && entry.approvals > 0) { + @if (entry.approvals && entry.approvals.length > 0) {
- {{ entry.approvals }} + {{ entry.approvals.length }}
} - @if (entry.comments && entry.comments > 0) { + @if (entry.comments && entry.comments.length > 0) {
- {{ entry.comments }} + {{ entry.comments.length }}
} diff --git a/webapp/src/app/home/leaderboard/leaderboard.stories.ts b/webapp/src/app/home/leaderboard/leaderboard.stories.ts index c3047a25..9432bb67 100644 --- a/webapp/src/app/home/leaderboard/leaderboard.stories.ts +++ b/webapp/src/app/home/leaderboard/leaderboard.stories.ts @@ -1,6 +1,19 @@ import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular'; import { LeaderboardComponent } from './leaderboard.component'; import { LeaderboardEntry } from 'app/core/modules/openapi/model/leaderboard-entry'; +import { PullRequestReviewDTO } from 'app/core/modules/openapi/model/pull-request-review-dto'; + +let reviewIdCounter = 1; + +const generateReviews = (count: number, state: PullRequestReviewDTO.StateEnum): PullRequestReviewDTO[] => { + return Array.from({ length: count }, () => ({ + id: reviewIdCounter++, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + submittedAt: new Date().toISOString(), + state + })); +}; const leaderboardEntries: LeaderboardEntry[] = [ { @@ -9,9 +22,9 @@ const leaderboardEntries: LeaderboardEntry[] = [ type: LeaderboardEntry.TypeEnum.User, name: 'Armin Stanitzok', score: 100, - changesRequested: 3, - approvals: 5, - comments: 1, + changesRequested: generateReviews(3, PullRequestReviewDTO.StateEnum.ChangesRequested), + approvals: generateReviews(5, PullRequestReviewDTO.StateEnum.Approved), + comments: generateReviews(1, PullRequestReviewDTO.StateEnum.Commented), rank: 1 }, { @@ -20,9 +33,9 @@ const leaderboardEntries: LeaderboardEntry[] = [ type: LeaderboardEntry.TypeEnum.User, name: 'Felix T.J. Dietrich', score: 90, - changesRequested: 1, - approvals: 1, - comments: 14, + changesRequested: generateReviews(1, PullRequestReviewDTO.StateEnum.ChangesRequested), + approvals: generateReviews(1, PullRequestReviewDTO.StateEnum.Approved), + comments: generateReviews(14, PullRequestReviewDTO.StateEnum.Commented), rank: 2 }, { @@ -31,9 +44,9 @@ const leaderboardEntries: LeaderboardEntry[] = [ type: LeaderboardEntry.TypeEnum.User, name: 'Stephan Krusche', score: 50, - changesRequested: 0, - approvals: 3, - comments: 1, + changesRequested: [], + approvals: generateReviews(3, PullRequestReviewDTO.StateEnum.Approved), + comments: generateReviews(1, PullRequestReviewDTO.StateEnum.Commented), rank: 3 }, { @@ -42,9 +55,9 @@ const leaderboardEntries: LeaderboardEntry[] = [ type: LeaderboardEntry.TypeEnum.User, name: 'shadcn', score: 20, - changesRequested: 0, - approvals: 0, - comments: 1, + changesRequested: [], + approvals: [], + comments: generateReviews(1, PullRequestReviewDTO.StateEnum.Commented), rank: 4 } ];