diff --git a/server/application-server/openapi.yaml b/server/application-server/openapi.yaml index a193b0d2..f4b6f8c9 100644 --- a/server/application-server/openapi.yaml +++ b/server/application-server/openapi.yaml @@ -289,6 +289,18 @@ components: $ref: "#/components/schemas/PullRequestReview" repository: $ref: "#/components/schemas/Repository" + pullRequestLabels: + uniqueItems: true + type: array + items: + $ref: "#/components/schemas/PullRequestLabel" + PullRequestLabel: + type: object + properties: + name: + type: string + color: + type: string PullRequestReview: required: - state 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 46c89016..4e0f4dde 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 @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.Set; +import jakarta.persistence.*; import org.springframework.lang.NonNull; import de.tum.in.www1.hephaestus.codereview.base.BaseGitServiceEntity; @@ -11,13 +12,6 @@ import de.tum.in.www1.hephaestus.codereview.pullrequest.review.PullRequestReview; import de.tum.in.www1.hephaestus.codereview.repository.Repository; import de.tum.in.www1.hephaestus.codereview.user.User; -import jakarta.persistence.Table; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -74,6 +68,9 @@ public class PullRequest extends BaseGitServiceEntity { @ToString.Exclude private Repository repository; + @ElementCollection + private Set pullRequestLabels = new HashSet<>(); + public void addComment(IssueComment comment) { comments.add(comment); } 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 ce054c74..855152d1 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 @@ -1,7 +1,11 @@ package de.tum.in.www1.hephaestus.codereview.pullrequest; import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import org.kohsuke.github.GHLabel; import org.kohsuke.github.GHPullRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +28,7 @@ public PullRequest convert(@NonNull GHPullRequest source) { pullRequest.setTitle(source.getTitle()); pullRequest.setUrl(source.getHtmlUrl().toString()); pullRequest.setState(state); + pullRequest.setPullRequestLabels(convertLabels(source.getLabels())); try { pullRequest.setAdditions(source.getAdditions()); } catch (IOException e) { @@ -65,4 +70,14 @@ private IssueState convertState(org.kohsuke.github.GHIssueState state) { } } + private Set convertLabels(Collection labels) { + Set pullRequestLabels = new HashSet<>(); + for (GHLabel label : labels) { + PullRequestLabel pullRequestLabel = new PullRequestLabel(); + pullRequestLabel.setName(label.getName()); + pullRequestLabel.setColor(label.getColor()); + pullRequestLabels.add(pullRequestLabel); + } + return pullRequestLabels; + } } diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequestLabel.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequestLabel.java new file mode 100644 index 00000000..28c666e2 --- /dev/null +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/pullrequest/PullRequestLabel.java @@ -0,0 +1,19 @@ +package de.tum.in.www1.hephaestus.codereview.pullrequest; + +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +public class PullRequestLabel { + @NonNull + private String name; + + @NonNull + private String color; +} diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 42280e4f..3a1e52f2 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -24,6 +24,7 @@ "autoprefixer": "10.4.20", "class-variance-authority": "0.7.0", "clsx": "2.1.1", + "dayjs": "^1.11.13", "lucide-angular": "0.429.0", "postcss": "8.4.41", "rxjs": "7.8.1", @@ -9136,6 +9137,12 @@ "node": ">=4.0" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", diff --git a/webapp/package.json b/webapp/package.json index cb31cc2e..db4e66e3 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -36,6 +36,7 @@ "autoprefixer": "10.4.20", "class-variance-authority": "0.7.0", "clsx": "2.1.1", + "dayjs": "^1.11.13", "lucide-angular": "0.429.0", "postcss": "8.4.41", "rxjs": "7.8.1", diff --git a/webapp/src/app/core/IssueCard/issue-card.component.html b/webapp/src/app/core/IssueCard/issue-card.component.html new file mode 100644 index 00000000..04e370da --- /dev/null +++ b/webapp/src/app/core/IssueCard/issue-card.component.html @@ -0,0 +1,37 @@ +
+
+ + @if (state() === 'OPEN') { + + } @else { + + } + + {{ repositoryName() }} #{{ number() }} on {{ createdAt().format('MMM D') }} + + + +{{ additions() }} + -{{ deletions() }} + +
+ +
+ {{ title() }} + @if (getMostRecentReview(); as review) { + @if (review.state === 'APPROVED') { + + } @else if (review.state === 'DISMISSED') { + + } @else if (review.state === 'COMMENTED') { + + } @else { + + } + } +
+
+ @for (label of pullRequestLabels(); track label.name) { + {{ label.name }} + } +
+
diff --git a/webapp/src/app/core/IssueCard/issue-card.component.ts b/webapp/src/app/core/IssueCard/issue-card.component.ts new file mode 100644 index 00000000..75bcb571 --- /dev/null +++ b/webapp/src/app/core/IssueCard/issue-card.component.ts @@ -0,0 +1,41 @@ +import { Component, input } from '@angular/core'; +import { PullRequest, PullRequestLabel, PullRequestReview } from '@app/core/modules/openapi'; +import { NgIcon } from '@ng-icons/core'; +import { octCheck, octComment, octFileDiff, octGitPullRequest, octGitPullRequestClosed, octX } from '@ng-icons/octicons'; +import { Dayjs } from 'dayjs'; +import { NgStyle } from '@angular/common'; + +@Component({ + selector: 'app-issue-card', + templateUrl: './issue-card.component.html', + imports: [NgIcon, NgStyle], + standalone: true +}) +export class IssueCardComponent { + title = input.required(); + number = input.required(); + additions = input.required(); + deletions = input.required(); + url = input.required(); + repositoryName = input.required(); + reviews = input.required>(); + createdAt = input.required(); + state = input.required(); + pullRequestLabels = input.required>(); + protected readonly octCheck = octCheck; + protected readonly octX = octX; + protected readonly octComment = octComment; + protected readonly octGitPullRequest = octGitPullRequest; + protected readonly octFileDiff = octFileDiff; + protected readonly octGitPullRequestClosed = octGitPullRequestClosed; + + getMostRecentReview() { + return this.reviews().reduce((latest, review) => { + return new Date(review.updatedAt || 0) > new Date(latest.updatedAt || 0) ? review : latest; + }); + } + + openIssue() { + window.open(this.url()); + } +} diff --git a/webapp/src/app/core/IssueCard/issue-card.stories.ts b/webapp/src/app/core/IssueCard/issue-card.stories.ts new file mode 100644 index 00000000..fe54d662 --- /dev/null +++ b/webapp/src/app/core/IssueCard/issue-card.stories.ts @@ -0,0 +1,40 @@ +import { Meta, StoryObj } from '@storybook/angular'; +import { IssueCardComponent } from './issue-card.component'; +import dayjs from 'dayjs'; + +const meta: Meta = { + title: 'Components/Core/IssueCard', + component: IssueCardComponent, + tags: ['autodocs'] // Auto-generate docs if enabled +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'Add feature X', + number: 12, + additions: 10, + deletions: 5, + url: 'http://example.com', + state: 'OPEN', + repositoryName: 'Artemis', + createdAt: dayjs('Jan 1'), + pullRequestLabels: [ + { name: 'bug', color: 'red' }, + { name: 'enhancement', color: 'green' } + ], + reviews: [ + { + state: 'APPROVED', + updatedAt: 'Jan 2' + }, + { + state: 'CHANGES_REQUESTED', + updatedAt: 'Jan 4' + } + ] + } +}; diff --git a/webapp/src/app/core/modules/openapi/.openapi-generator/FILES b/webapp/src/app/core/modules/openapi/.openapi-generator/FILES index e40c7a6a..f148186c 100644 --- a/webapp/src/app/core/modules/openapi/.openapi-generator/FILES +++ b/webapp/src/app/core/modules/openapi/.openapi-generator/FILES @@ -20,6 +20,7 @@ model/issue-comment.ts model/leaderboard-entry.ts model/models.ts model/pull-request-dto.ts +model/pull-request-label.ts model/pull-request-review-comment.ts model/pull-request-review-dto.ts model/pull-request-review.ts diff --git a/webapp/src/app/core/modules/openapi/model/models.ts b/webapp/src/app/core/modules/openapi/model/models.ts index 967af558..ae9140b7 100644 --- a/webapp/src/app/core/modules/openapi/model/models.ts +++ b/webapp/src/app/core/modules/openapi/model/models.ts @@ -3,6 +3,7 @@ export * from './issue-comment-dto'; export * from './leaderboard-entry'; export * from './pull-request'; export * from './pull-request-dto'; +export * from './pull-request-label'; export * from './pull-request-review'; export * from './pull-request-review-comment'; export * from './pull-request-review-dto'; diff --git a/webapp/src/app/core/modules/openapi/model/pull-request-label.ts b/webapp/src/app/core/modules/openapi/model/pull-request-label.ts new file mode 100644 index 00000000..9e0750c2 --- /dev/null +++ b/webapp/src/app/core/modules/openapi/model/pull-request-label.ts @@ -0,0 +1,18 @@ +/** + * 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 PullRequestLabel { + name?: string; + color?: string; +} + 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 f1fa06ad..98c19061 100644 --- a/webapp/src/app/core/modules/openapi/model/pull-request.ts +++ b/webapp/src/app/core/modules/openapi/model/pull-request.ts @@ -10,6 +10,7 @@ * Do not edit the class manually. */ import { Repository } from './repository'; +import { PullRequestLabel } from './pull-request-label'; import { PullRequestReview } from './pull-request-review'; import { User } from './user'; import { IssueComment } from './issue-comment'; @@ -35,6 +36,7 @@ export interface PullRequest { comments?: Set; reviews?: Set; repository?: Repository; + pullRequestLabels?: Set; } export namespace PullRequest { export type StateEnum = 'CLOSED' | 'OPEN';