From 7e1c6c1f4485b743a4f2e4ea27b7d5237135d403 Mon Sep 17 00:00:00 2001 From: DmitrySheyko Date: Fri, 3 Feb 2023 20:15:46 +0300 Subject: [PATCH] =?UTF-8?q?Feat:=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE=D0=B9?= =?UTF-8?q?=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB?= =?UTF-8?q?=20issue,=20=D0=B2=D1=8B=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B0,?= =?UTF-8?q?=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5,=20=D1=83?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../context/domain/AssigneesChanged.java | 50 +++ .../context/domain/IdAndStatusIssue.java | 20 ++ .../gitlab/context/domain/entity/Issue.java | 14 +- .../issue/DeleteFromAssigneesNotify.java | 33 ++ .../notify/issue/DescriptionIssueNotify.java | 33 ++ .../notify/issue/DueDateIssueNotify.java | 36 ++ .../domain/notify/issue/IssueNotify.java | 29 ++ .../domain/notify/issue/NewIssueNotify.java | 47 +++ .../notify/issue/StatusIssueNotify.java | 37 +++ .../domain/notify/issue/TitleIssueNotify.java | 33 ++ .../domain/notify/issue/TypeIssueNotify.java | 37 +++ .../context/repository/IssueRepository.java | 27 ++ .../gitlab/context/service/IssueService.java | 29 ++ .../bot/gitlab/context/utils/Icons.java | 3 +- .../config/properties/GitlabProperty.java | 7 + .../service/convert/IssueJsonConverter.java | 97 ++++++ .../core/service/impl/IssueServiceImpl.java | 313 ++++++++++++++++++ .../core/service/parser/IssueParser.java | 166 ++++++++++ .../forktask/GetAllIssueForProjectTask.java | 55 +++ .../parser/forktask/GetSingleIssueTask.java | 36 ++ .../gitlab/data/impl/IssueRepositoryImpl.java | 56 ++++ .../gitlab/data/jpa/IssueJpaRepository.java | 22 ++ .../gitlab/scheduler/SchedulerService.java | 8 + gitlab-app/src/main/resources/application.yml | 6 +- .../2023-01-19-create-tables-for-issue.xml | 6 + .../bot/gitlab/sdk/domain/IssueJson.java | 12 +- .../bot/gitlab/sdk/domain/MilestoneJson.java | 15 +- ...teFromAssigneesOfIssueNotifyGenerator.java | 37 +++ .../DescriptionIssueNotifyGenerator.java | 39 +++ .../notify/DueDateIssueNotifyGenerator.java | 38 +++ .../notify/NewIssueNotifyGenerator.java | 49 +++ .../notify/StatusIssueNotifyGenerator.java | 38 +++ .../notify/TitleIssueNotifyGenerator.java | 39 +++ .../notify/TypeIssueNotifyGenerator.java | 38 +++ 34 files changed, 1489 insertions(+), 16 deletions(-) create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/AssigneesChanged.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IdAndStatusIssue.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DeleteFromAssigneesNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DescriptionIssueNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DueDateIssueNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/IssueNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/NewIssueNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/StatusIssueNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TitleIssueNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TypeIssueNotify.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/repository/IssueRepository.java create mode 100644 bot-context/src/main/java/dev/struchkov/bot/gitlab/context/service/IssueService.java create mode 100644 bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/convert/IssueJsonConverter.java create mode 100644 bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/impl/IssueServiceImpl.java create mode 100644 bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/IssueParser.java create mode 100644 bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetAllIssueForProjectTask.java create mode 100644 bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetSingleIssueTask.java create mode 100644 bot-data/src/main/java/dev/struchkov/bot/gitlab/data/impl/IssueRepositoryImpl.java create mode 100644 bot-data/src/main/java/dev/struchkov/bot/gitlab/data/jpa/IssueJpaRepository.java create mode 100644 telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DeleteFromAssigneesOfIssueNotifyGenerator.java create mode 100644 telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DescriptionIssueNotifyGenerator.java create mode 100644 telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DueDateIssueNotifyGenerator.java create mode 100644 telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/NewIssueNotifyGenerator.java create mode 100644 telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/StatusIssueNotifyGenerator.java create mode 100644 telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TitleIssueNotifyGenerator.java create mode 100644 telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TypeIssueNotifyGenerator.java diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/AssigneesChanged.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/AssigneesChanged.java new file mode 100644 index 0000000..4e55368 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/AssigneesChanged.java @@ -0,0 +1,50 @@ +package dev.struchkov.bot.gitlab.context.domain; + +import dev.struchkov.bot.gitlab.context.domain.entity.Person; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Dmitry Sheyko [25.01.2023] + */ +@Getter +@RequiredArgsConstructor +public enum AssigneesChanged { + + BECOME(true), + DELETED(true), + NOT_AFFECT_USER(true), + NOT_CHANGED(false); + + private final boolean changed; + + public static AssigneesChanged valueOf(Long gitlabUserId, List oldAssignees, List newAssignees) { + final Map oldMap = oldAssignees.stream().collect(Collectors.toMap(Person::getId, p -> p)); + final Map newMap = newAssignees.stream().collect(Collectors.toMap(Person::getId, p -> p)); + + if (!oldMap.keySet().equals(newMap.keySet())) { + + if (oldMap.containsKey(gitlabUserId) && !newMap.containsKey(gitlabUserId)) { + return AssigneesChanged.DELETED; + } + if (!oldMap.containsKey(gitlabUserId) && newMap.containsKey(gitlabUserId)) { + return AssigneesChanged.BECOME; + } + return AssigneesChanged.NOT_AFFECT_USER; + } + return AssigneesChanged.NOT_CHANGED; + } + + public boolean getNewStatus(boolean oldStatus) { + return switch (this) { + case BECOME -> true; + case DELETED -> false; + case NOT_AFFECT_USER, NOT_CHANGED -> oldStatus; + }; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IdAndStatusIssue.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IdAndStatusIssue.java new file mode 100644 index 0000000..f257603 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IdAndStatusIssue.java @@ -0,0 +1,20 @@ +package dev.struchkov.bot.gitlab.context.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Dmotry Sheyko [25.01.2023] + */ +@Getter +@Setter +@AllArgsConstructor +public class IdAndStatusIssue { + + private long id; + private long twoId; + private long projectId; + private IssueState status; + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/entity/Issue.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/entity/Issue.java index 156f888..e3d57d2 100644 --- a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/entity/Issue.java +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/entity/Issue.java @@ -22,6 +22,7 @@ import javax.persistence.JoinTable; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashSet; @@ -76,11 +77,10 @@ public class Issue { private Person closedBy; @ElementCollection - @CollectionTable(name = "issue_labels", joinColumns = @JoinColumn(name = "label_id")) - @Column(name = "labels") + @CollectionTable(name = "issue_label", joinColumns = @JoinColumn(name = "issue_id")) + @Column(name = "label") private Set labels = new HashSet<>(); - @Column(name = "assignees") @OneToMany( fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE} @@ -113,7 +113,7 @@ public class Issue { private Integer downVotes; @Column(name = "due_date") - private LocalDateTime dueDate; + private LocalDate dueDate; @Column(name = "confidential") private Boolean confidential; @@ -136,4 +136,10 @@ public class Issue { @Column(name = "has_tasks") private Boolean hasTasks; + @Column(name = "notification") + private boolean notification; + + @Column(name = "is_assignee") + private boolean userAssignee; + } \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DeleteFromAssigneesNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DeleteFromAssigneesNotify.java new file mode 100644 index 0000000..3e64acb --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DeleteFromAssigneesNotify.java @@ -0,0 +1,33 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import lombok.Builder; +import lombok.Getter; + +/** + * @author Dmitry Sheyko 25.01.2021 + */ +@Getter +public class DeleteFromAssigneesNotify extends IssueNotify { + + public static final String TYPE = "DeleteFromAssigneesOfIssueNotify"; + + private final String updateDate; + + @Builder + public DeleteFromAssigneesNotify( + String projectName, + String title, + String url, + String issueType, + String updateDate + ) { + super(projectName, title, url, issueType); + this.updateDate = updateDate; + } + + @Override + public String getType() { + return TYPE; + } + +} diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DescriptionIssueNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DescriptionIssueNotify.java new file mode 100644 index 0000000..d61e54d --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DescriptionIssueNotify.java @@ -0,0 +1,33 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import lombok.Builder; +import lombok.Getter; + +/** + * @author Dmitry Sheyko 25.01.2021 + */ +@Getter +public class DescriptionIssueNotify extends IssueNotify { + + public static final String TYPE = "DescriptionIssueNotify"; + + private final String newDescription; + + @Builder + public DescriptionIssueNotify( + String projectName, + String title, + String url, + String issueType, + String newDescription + ) { + super(projectName, title, url, issueType); + this.newDescription = newDescription; + } + + @Override + public String getType() { + return TYPE; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DueDateIssueNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DueDateIssueNotify.java new file mode 100644 index 0000000..b00f295 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/DueDateIssueNotify.java @@ -0,0 +1,36 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import lombok.Builder; +import lombok.Getter; + +/** + * @author Dmitry Sheyko 25.01.2021 + */ +@Getter +public class DueDateIssueNotify extends IssueNotify { + + public static final String TYPE = "DueDateIssueNotify"; + + private final String oldDueDate; + private final String newDueDate; + + @Builder + public DueDateIssueNotify( + String projectName, + String title, + String url, + String issueType, + String oldDueDate, + String newDueDate + ) { + super(projectName, title, url, issueType); + this.oldDueDate = oldDueDate; + this.newDueDate = newDueDate; + } + + @Override + public String getType() { + return TYPE; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/IssueNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/IssueNotify.java new file mode 100644 index 0000000..9a109d6 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/IssueNotify.java @@ -0,0 +1,29 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import dev.struchkov.bot.gitlab.context.domain.notify.Notify; +import lombok.Getter; + +/** + * @author Dmitry Sheyko 23.01.2021 + */ +@Getter +public abstract class IssueNotify implements Notify { + + protected final String projectName; + protected final String title; + protected final String url; + protected final String issueType; + + public IssueNotify( + String projectName, + String title, + String url, + String issueType + ) { + this.projectName = projectName; + this.title = title; + this.url = url; + this.issueType = issueType; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/NewIssueNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/NewIssueNotify.java new file mode 100644 index 0000000..48a66f3 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/NewIssueNotify.java @@ -0,0 +1,47 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import lombok.Builder; +import lombok.Getter; + +import java.util.Set; + +/** + * @author Dmitry Sheyko 23.01.2021 + */ +@Getter +public class NewIssueNotify extends IssueNotify { + + public static final String TYPE = "NewIssueNotify"; + + private final String author; + private final String description; + private final String dueDate; + private final Set labels; + private final String confidential; + + @Builder + public NewIssueNotify( + String projectName, + String title, + String url, + String issueType, + String author, + String description, + String dueDate, + Set labels, + String confidential + ) { + super(projectName, title, url, issueType); + this.author = author; + this.description = description; + this.dueDate = dueDate; + this.labels = labels; + this.confidential = confidential; + } + + @Override + public String getType() { + return TYPE; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/StatusIssueNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/StatusIssueNotify.java new file mode 100644 index 0000000..6377a63 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/StatusIssueNotify.java @@ -0,0 +1,37 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import dev.struchkov.bot.gitlab.context.domain.IssueState; +import lombok.Builder; +import lombok.Getter; + +/** + * @author Dmitry Sheyko 23.01.2021 + */ +@Getter +public class StatusIssueNotify extends IssueNotify{ + + public static final String TYPE = "StatusIssueNotify"; + + private final IssueState oldStatus; + private final IssueState newStatus; + + @Builder + private StatusIssueNotify( + String name, + String url, + String projectName, + String issueType, + IssueState oldStatus, + IssueState newStatus + ) { + super(projectName, name, url, issueType); + this.oldStatus = oldStatus; + this.newStatus = newStatus; + } + + @Override + public String getType() { + return TYPE; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TitleIssueNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TitleIssueNotify.java new file mode 100644 index 0000000..ec46de7 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TitleIssueNotify.java @@ -0,0 +1,33 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import lombok.Builder; +import lombok.Getter; + +/** + * @author Dmitry Sheyko 25.01.2021 + */ +@Getter +public class TitleIssueNotify extends IssueNotify { + + public static final String TYPE = "TitleIssueNotify"; + + private final String newTitle; + + @Builder + public TitleIssueNotify( + String projectName, + String title, + String url, + String issueType, + String newTitle + ) { + super(projectName, title, url, issueType); + this.newTitle = newTitle; + } + + @Override + public String getType() { + return TYPE; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TypeIssueNotify.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TypeIssueNotify.java new file mode 100644 index 0000000..3c68833 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/notify/issue/TypeIssueNotify.java @@ -0,0 +1,37 @@ +package dev.struchkov.bot.gitlab.context.domain.notify.issue; + +import dev.struchkov.bot.gitlab.context.domain.IssueType; +import lombok.Builder; +import lombok.Getter; + +/** + * @author Dmitry Sheyko 25.01.2021 + */ +@Getter +public class TypeIssueNotify extends IssueNotify { + + public static final String TYPE = "TypeIssueNotify"; + + private final IssueType oldType; + private final IssueType newType; + + @Builder + public TypeIssueNotify( + String projectName, + String title, + String url, + String issueType, + IssueType oldType, + IssueType newType + ) { + super(projectName, title, url, issueType); + this.oldType = oldType; + this.newType = newType; + } + + @Override + public String getType() { + return TYPE; + } + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/repository/IssueRepository.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/repository/IssueRepository.java new file mode 100644 index 0000000..62780d4 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/repository/IssueRepository.java @@ -0,0 +1,27 @@ +package dev.struchkov.bot.gitlab.context.repository; + +import dev.struchkov.bot.gitlab.context.domain.IdAndStatusIssue; +import dev.struchkov.bot.gitlab.context.domain.IssueState; +import dev.struchkov.bot.gitlab.context.domain.entity.Issue; +import lombok.NonNull; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * @author Dmitry Sheyko [24.01.2023] + */ +public interface IssueRepository { + + Set findAllIdByStateIn(@NonNull Set states); + + Issue save(Issue issue); + + Optional findById(Long issueId); + + List findAllById(Set mergeRequestIds); + + void deleteByStates(Set states); + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/service/IssueService.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/service/IssueService.java new file mode 100644 index 0000000..e40e09a --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/service/IssueService.java @@ -0,0 +1,29 @@ +package dev.struchkov.bot.gitlab.context.service; + +import dev.struchkov.bot.gitlab.context.domain.*; +import dev.struchkov.bot.gitlab.context.domain.entity.Issue; +import lombok.NonNull; + +import java.util.List; +import java.util.Set; + +/** + * @author Dmitry Sheyko [24.01.2023] + */ +public interface IssueService { + + Issue create(@NonNull Issue issue); + + Issue update(@NonNull Issue issue); + + List updateAll(@NonNull List issues); + + ExistContainer existsById(@NonNull Set issueIds); + + List createAll(List issues); + + Set getAllId(Set statuses); + + void cleanOld(); + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/utils/Icons.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/utils/Icons.java index 49de493..8244fcd 100644 --- a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/utils/Icons.java +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/utils/Icons.java @@ -19,6 +19,7 @@ public class Icons { public static final String PEN = "✏️"; public static final String BUILD = "\uD83D\uDEE0"; public static final String LINK = "\uD83D\uDD17"; + public static final String BELL ="\uD83D\uDD14"; private Icons() { utilityClass(); @@ -28,4 +29,4 @@ public class Icons { return "[" + escapeMarkdown(title) + "](" + url + ")"; } -} +} \ No newline at end of file diff --git a/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/config/properties/GitlabProperty.java b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/config/properties/GitlabProperty.java index 2fb2a8e..e3ca4a1 100644 --- a/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/config/properties/GitlabProperty.java +++ b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/config/properties/GitlabProperty.java @@ -68,4 +68,11 @@ public class GitlabProperty { private String discussionUrl; + /** + * Адрес, по которому можно получить ISSUE + */ + private String issueUrl; + + private String openIssueUrl; + } diff --git a/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/convert/IssueJsonConverter.java b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/convert/IssueJsonConverter.java new file mode 100644 index 0000000..54d9315 --- /dev/null +++ b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/convert/IssueJsonConverter.java @@ -0,0 +1,97 @@ +package dev.struchkov.bot.gitlab.core.service.convert; + +import dev.struchkov.bot.gitlab.context.domain.IssueState; +import dev.struchkov.bot.gitlab.context.domain.IssueType; +import dev.struchkov.bot.gitlab.context.domain.entity.Issue; +import dev.struchkov.bot.gitlab.context.domain.entity.Person; +import dev.struchkov.bot.gitlab.sdk.domain.*; +import lombok.RequiredArgsConstructor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static dev.struchkov.haiti.utils.Checker.checkNotEmpty; +import static dev.struchkov.haiti.utils.Checker.checkNotNull; + +/** + * @author Dmitry Sheyko [22.01.2023] + */ +@Component +@RequiredArgsConstructor +public class IssueJsonConverter implements Converter { + + private final PersonJsonConverter convertPerson; + + @Override + public Issue convert(IssueJson source) { + final Issue issue = new Issue(); + issue.setId(source.getId()); + issue.setTwoId(source.getTwoId()); + issue.setProjectId(source.getProjectId()); + issue.setTitle(source.getTitle()); + issue.setDescription(source.getDescription()); + issue.setState(convertState(source.getState())); + issue.setCreatedDate(source.getCreatedDate()); + issue.setUpdatedDate(source.getUpdatedDate()); + issue.setCloseDate(source.getClosedDate()); + issue.setType(convertType(source.getType())); + issue.setUserNotesCount(source.getUserNotesCount()); + issue.setMergeRequestsCount(source.getMergeRequestsCount()); + issue.setUpVotes(source.getUpVotes()); + issue.setDownVotes(source.getDownVotes()); + issue.setDueDate(source.getDueDate()); + issue.setConfidential(source.getConfidential()); + issue.setDiscussionLocked(source.getDiscussionLocked()); + issue.setTaskCount(source.getTaskCompletionStatus().getCount()); + issue.setTaskCompletedCount(source.getTaskCompletionStatus().getCompletedCount()); + issue.setWebUrl(source.getWebUrl()); + issue.setBlockingIssuesCount(source.getBlockingIssuesCount()); + issue.setHasTasks(source.getHasTasks()); + + convertAssignees(issue, source.getAssignees()); + convertLabels(issue, source.getLabels()); + + if (checkNotNull(source.getClosedBy())) { + issue.setClosedBy(convertPerson.convert(source.getClosedBy())); + } + + issue.setAuthor(convertPerson.convert(source.getAuthor())); + return issue; + } + + private void convertAssignees(Issue issue, List jsonAssignees) { + if (checkNotEmpty(jsonAssignees)) { + final List assignees = jsonAssignees.stream() + .map(convertPerson::convert) + .toList(); + issue.setAssignees(assignees); + } + } + + private void convertLabels(Issue issue, Set source) { + if (checkNotEmpty(source)) { + final Set labels = source.stream() + .map(label -> label.replace("-", "_")) + .collect(Collectors.toSet()); + issue.setLabels(labels); + } + } + + private IssueState convertState(IssueStateJson state) { + return switch (state) { + case CLOSED -> IssueState.CLOSED; + case OPENED -> IssueState.OPENED; + }; + } + + private IssueType convertType(IssueTypeJson type) { + return switch (type) { + case ISSUE -> IssueType.ISSUE; + case INCIDENT -> IssueType.INCIDENT; + }; + } + +} \ No newline at end of file diff --git a/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/impl/IssueServiceImpl.java b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/impl/IssueServiceImpl.java new file mode 100644 index 0000000..bfa9e64 --- /dev/null +++ b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/impl/IssueServiceImpl.java @@ -0,0 +1,313 @@ +package dev.struchkov.bot.gitlab.core.service.impl; + +import dev.struchkov.bot.gitlab.context.domain.*; +import dev.struchkov.bot.gitlab.context.domain.entity.Issue; +import dev.struchkov.bot.gitlab.context.domain.entity.Person; +import dev.struchkov.bot.gitlab.context.domain.entity.Project; +import dev.struchkov.bot.gitlab.context.domain.notify.issue.*; +import dev.struchkov.bot.gitlab.context.repository.IssueRepository; +import dev.struchkov.bot.gitlab.context.service.IssueService; +import dev.struchkov.bot.gitlab.context.service.NotifyService; +import dev.struchkov.bot.gitlab.context.service.ProjectService; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static dev.struchkov.bot.gitlab.context.domain.IssueState.CLOSED; +import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException; +import static dev.struchkov.haiti.utils.Checker.checkNotEmpty; + +/** + * @author Dmitry Sheyko [25.01.2023] + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class IssueServiceImpl implements IssueService { + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy"); + public static final Set DELETE_STATES = Set.of(CLOSED); + + private final PersonInformation personInformation; + private final IssueRepository repository; + private final ProjectService projectService; + private final NotifyService notifyService; + + @Override + @Transactional + public Issue create(@NonNull Issue issue) { + final boolean botUserAssignee = isBotUserAssignee(issue); + final boolean botUserAssigneeAndNotAuthor = isBotUserAssigneeAndNotAuthor(issue); + issue.setUserAssignee(botUserAssignee); + issue.setNotification(botUserAssigneeAndNotAuthor); + + final Issue savedIssue = repository.save(issue); + + if (botUserAssigneeAndNotAuthor) { + final String projectName = projectService.getByIdOrThrow(savedIssue.getProjectId()).getName(); + sendNotifyAboutAssignee(issue, projectName); + } + return savedIssue; + } + + private boolean isBotUserAssignee(Issue savedIssue) { + final Long gitlabUserId = personInformation.getId(); + final List assignees = savedIssue.getAssignees(); + + if (checkNotEmpty(assignees)) { + for (Person assignee : assignees) { + if (gitlabUserId.equals(assignee.getId())) { + return true; + } + } + } + return false; + } + + private boolean isBotUserAssigneeAndNotAuthor(Issue savedIssue) { + final Long gitlabUserId = personInformation.getId(); + final boolean botUserAssignee = isBotUserAssignee(savedIssue); + + if (botUserAssignee) { + return !gitlabUserId.equals(savedIssue.getAuthor().getId()); + } + return false; + } + + private void sendNotifyAboutAssignee(Issue issue, String projectName) { + final Long gitlabUserId = personInformation.getId(); + if (!gitlabUserId.equals(issue.getAuthor().getId()) // создатель Issue не является пользователем бота + ) + notifyService.send( + NewIssueNotify.builder() + .projectName(projectName) + .title(issue.getTitle()) + .url(issue.getWebUrl()) + .issueType(issue.getType().name()) + .author(issue.getAuthor().getName()) + .description(issue.getDescription()) + .dueDate(issue.getDueDate().format(DATE_FORMAT)) + .labels(issue.getLabels()) + .confidential(issue.getConfidential().toString()) + .build() + ); + } + + private void sendNotifyAboutDeleteFromAssignees(Issue issue, String projectName) { + final Long gitlabUserId = personInformation.getId(); + if (!gitlabUserId.equals(issue.getAuthor().getId()) // создатель Issue не является пользователем бота + ) + notifyService.send( + DeleteFromAssigneesNotify.builder() + .projectName(projectName) + .title(issue.getTitle()) + .url(issue.getWebUrl()) + .issueType(issue.getType().name()) + .updateDate(issue.getUpdatedDate().format(DATE_FORMAT)) + .build() + ); + } + + @Override + @Transactional + public Issue update(@NonNull Issue issue) { + final Issue oldIssue = repository.findById(issue.getId()) + .orElseThrow(notFoundException("Issue не найдено")); + + issue.setNotification(oldIssue.isNotification()); + final Long gitlabUserId = personInformation.getId(); + + /** + * проверяем изменения списка Assignees: пользователь появился в списке или удален из него или без изменений. + */ + final AssigneesChanged assigneesChanged = AssigneesChanged.valueOf(gitlabUserId, oldIssue.getAssignees(), issue.getAssignees()); + issue.setUserAssignee(assigneesChanged.getNewStatus(oldIssue.isUserAssignee())); + final boolean isChangedIssue = !oldIssue.getUpdatedDate().equals(issue.getUpdatedDate()); + + /** + * Удаление пользователя из assignee не всегда обновляет UpdatedDate, поэтому добавляется + * второе условие assigneesChanged.isChanged() + */ + if (isChangedIssue || assigneesChanged.isChanged()) { + + if (assigneesChanged.equals(AssigneesChanged.BECOME) && !gitlabUserId.equals(issue.getAuthor().getId())) + issue.setNotification(true); + + if (issue.isNotification()) { + final Project project = projectService.getByIdOrThrow(issue.getProjectId()); + notifyAboutStatus(oldIssue, issue, project); + notifyAboutType(oldIssue, issue, project); + notifyAboutTitle(oldIssue, issue, project); + notifyAboutDescription(oldIssue, issue, project); + notifyAboutDueDate(oldIssue, issue, project); + notifyAboutChangeAssignees(assigneesChanged, issue, project); + } + return repository.save(issue); + } + return oldIssue; + } + + @Override + public ExistContainer existsById(@NonNull Set issueIds) { + final List existsEntity = repository.findAllById(issueIds); + final Set existsIds = existsEntity.stream().map(Issue::getId).collect(Collectors.toSet()); + if (existsIds.containsAll(issueIds)) { + return ExistContainer.allFind(existsEntity); + } else { + final Set noExistsId = issueIds.stream() + .filter(id -> !existsIds.contains(id)) + .collect(Collectors.toSet()); + return ExistContainer.notAllFind(existsEntity, noExistsId); + } + } + + @Override + public List createAll(List newIssues) { + return newIssues.stream() + .map(this::create) + .toList(); + } + + @Override + @Transactional + public List updateAll(@NonNull List issues) { + return issues.stream() + .map(this::update) + .collect(Collectors.toList()); + } + + @Override + public Set getAllId(Set statuses) { + return repository.findAllIdByStateIn(statuses); + } + + protected void notifyAboutChangeAssignees(AssigneesChanged assigneesChanged, Issue issue, Project project) { + switch (assigneesChanged) { + case BECOME -> sendNotifyAboutAssignee(issue, project.getName()); + case DELETED -> { + sendNotifyAboutDeleteFromAssignees(issue, project.getName()); + issue.setUserAssignee(false); + issue.setNotification(false); + } + } + } + + protected void notifyAboutTitle(Issue oldIssue, Issue newIssue, Project project) { + final String oldTitle = oldIssue.getTitle(); + final String newTitle = newIssue.getTitle(); + final Long gitlabUserId = personInformation.getId(); + if ( + !oldTitle.equals(newTitle) // заголовок изменился + && !gitlabUserId.equals(oldIssue.getAuthor().getId()) // создатель Issue не является пользователем бота + ) { + notifyService.send( + TitleIssueNotify.builder() + .projectName(project.getName()) + .title(oldIssue.getTitle()) + .url(oldIssue.getWebUrl()) + .issueType(oldIssue.getType().name()) + .newTitle(newTitle) + .build() + ); + } + } + + protected void notifyAboutDescription(Issue oldIssue, Issue newIssue, Project project) { + final String oldDescription = oldIssue.getDescription(); + final String newDescription = newIssue.getDescription(); + final Long gitlabUserId = personInformation.getId(); + if ( + !oldDescription.equals(newDescription) // описание изменилось + && !gitlabUserId.equals(oldIssue.getAuthor().getId()) // создатель Issue не является пользователем бота + ) { + notifyService.send( + DescriptionIssueNotify.builder() + .projectName(project.getName()) + .title(oldIssue.getTitle()) + .url(oldIssue.getWebUrl()) + .issueType(oldIssue.getType().name()) + .newDescription(newDescription) + .build() + ); + } + } + + protected void notifyAboutType(Issue oldIssue, Issue newIssue, Project project) { + final IssueType oldType = oldIssue.getType(); + final IssueType newType = newIssue.getType(); + final Long gitlabUserId = personInformation.getId(); + if ( + !oldType.equals(newType) // тип изменился + && !gitlabUserId.equals(oldIssue.getAuthor().getId()) // создатель Issue не является пользователем бота + ) { + notifyService.send( + TypeIssueNotify.builder() + .projectName(project.getName()) + .title(oldIssue.getTitle()) + .url(oldIssue.getWebUrl()) + .issueType(oldIssue.getType().name()) + .oldType(oldType) + .newType(newType) + .build() + ); + } + } + + protected void notifyAboutStatus(Issue oldIssue, Issue newIssue, Project project) { + final IssueState oldStatus = oldIssue.getState(); + final IssueState newStatus = newIssue.getState(); + final Long gitlabUserId = personInformation.getId(); + if ( + !oldStatus.equals(newStatus) // статус изменился + && gitlabUserId.equals(oldIssue.getAuthor().getId()) // создатель Issue является пользователем бота + ) { + notifyService.send( + StatusIssueNotify.builder() + .name(newIssue.getTitle()) + .url(oldIssue.getWebUrl()) + .issueType(oldIssue.getType().name()) + .projectName(project.getName()) + .newStatus(newStatus) + .oldStatus(oldStatus) + .build() + ); + } + } + + protected void notifyAboutDueDate(Issue oldIssue, Issue newIssue, Project project) { + final String oldDueDate = oldIssue.getDueDate().format(DATE_FORMAT); + final String newDueDate = newIssue.getDueDate().format(DATE_FORMAT); + final Long gitlabUserId = personInformation.getId(); + if ( + (!Objects.equals(oldDueDate, newDueDate)) // дата изменилась + && (!gitlabUserId.equals(oldIssue.getAuthor().getId())) // создатель Issue не является пользователем бота + ) { + notifyService.send( + DueDateIssueNotify.builder() + .projectName(project.getName()) + .title(oldIssue.getTitle()) + .url(oldIssue.getWebUrl()) + .issueType(oldIssue.getType().name()) + .oldDueDate(oldDueDate) + .newDueDate(newDueDate) + .build() + ); + } + } + + @Override + public void cleanOld() { + log.debug("Старт очистки старых Issue"); + repository.deleteByStates(DELETE_STATES); + log.debug("Конец очистки старых Issue"); + } + +} \ No newline at end of file diff --git a/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/IssueParser.java b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/IssueParser.java new file mode 100644 index 0000000..18ef891 --- /dev/null +++ b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/IssueParser.java @@ -0,0 +1,166 @@ +package dev.struchkov.bot.gitlab.core.service.parser; + +import dev.struchkov.bot.gitlab.context.domain.*; +import dev.struchkov.bot.gitlab.context.domain.entity.Issue; +import dev.struchkov.bot.gitlab.context.domain.entity.Person; +import dev.struchkov.bot.gitlab.context.service.IssueService; +import dev.struchkov.bot.gitlab.context.service.ProjectService; +import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty; +import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty; +import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetAllIssueForProjectTask; +import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetSingleIssueTask; +import dev.struchkov.bot.gitlab.sdk.domain.IssueJson; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.convert.ConversionService; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static dev.struchkov.haiti.utils.Checker.checkNotEmpty; +import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResult; +import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResults; + +/** + * @author Dmotry Sheyko [24.01.2023] + */ +@Slf4j +@Service +public class IssueParser { + + private static final Set OLD_STATUSES = Set.of(IssueState.OPENED, IssueState.CLOSED); + + private final GitlabProperty gitlabProperty; + private final IssueService issueService; + private final ProjectService projectService; + private final ConversionService conversionService; + private final PersonProperty personProperty; + + private final ForkJoinPool forkJoinPool; + + public IssueParser( + GitlabProperty gitlabProperty, + IssueService issueService, + ProjectService projectService, + ConversionService conversionService, + PersonProperty personProperty, + @Qualifier("parserPool") ForkJoinPool forkJoinPool + ) { + this.gitlabProperty = gitlabProperty; + this.issueService = issueService; + this.projectService = projectService; + this.conversionService = conversionService; + this.personProperty = personProperty; + this.forkJoinPool = forkJoinPool; + } + + public void parsingOldIssue(){ + log.debug("Старт обработаки старых Issue"); + final Set existIds = issueService.getAllId(OLD_STATUSES); + + final List newIssues = getOldIssues(existIds).stream() + .map(issueJson -> { + final Issue newIssue = conversionService.convert(issueJson, Issue.class); + return newIssue; + }) + .collect(Collectors.toList()); + + if (checkNotEmpty(newIssues)) { + personMapping(newIssues); + issueService.updateAll(newIssues); + } + log.debug("Конец обработки старых Issue"); + } + + private List getOldIssues(Set existIds) { + final List>> tasks = existIds.stream() + .map( + existId -> new GetSingleIssueTask( + gitlabProperty.getIssueUrl(), + existId.getProjectId(), + existId.getTwoId(), + personProperty.getToken() + ) + ).map(forkJoinPool::submit) + .collect(Collectors.toList()); + + return pullTaskResult(tasks).stream() + .flatMap(Optional::stream) + .collect(Collectors.toList()); + } + + + public void parsingNewIssue() { + log.debug("Старт обработки новых Issue"); + + /** + * получаем через репозиторий список id всех проектов хранящихся в БД + */ + final Set projectIds = projectService.getAllIds(); + + /** + * На основе id проекта, url для получения issues по id проекта и токена пользователя + * выгружаем из GitLab список всех IssueJson. Получаем в многопоточном режиме. + */ + final List issueJsons = getIssues(projectIds); + + /** + * Получаем id всех IssueJson загруженных из GitLab + */ + if (checkNotEmpty(issueJsons)) { + final Set jsonIds = issueJsons.stream() + .map(IssueJson::getId) + .collect(Collectors.toSet()); + + final ExistContainer existContainer = issueService.existsById(jsonIds); + log.trace("Из {} полученных MR не найдены в хранилище {}", jsonIds.size(), existContainer.getIdNoFound().size()); + if (!existContainer.isAllFound()) { + final List newIssues = issueJsons.stream() + .filter(json -> existContainer.getIdNoFound().contains(json.getId())) + .map(json -> { + final Issue issue = conversionService.convert(json, Issue.class); + return issue; + }) + .toList(); + log.trace("Пачка новых issues обработана и отправлена на сохранение. Количество: {} шт.", newIssues.size()); + issueService.createAll(newIssues); + } + } + log.debug("Конец обработки новых MR"); + } + + private List getIssues(Set projectIds) { + final List>> tasks = projectIds.stream() + .map(projectId -> new GetAllIssueForProjectTask(projectId, gitlabProperty.getOpenIssueUrl(), personProperty.getToken())) + .map(forkJoinPool::submit) + .collect(Collectors.toList()); + + return pullTaskResults(tasks); + } + + private static void personMapping(List newIssues) { + final Map personMap = Stream.concat( + newIssues.stream() + .map(Issue::getAuthor), + newIssues.stream() + .flatMap(issue -> issue.getAssignees().stream()) + ).distinct() + .filter(Objects::nonNull) + .collect(Collectors.toMap(Person::getId, p -> p)); + + for (Issue newIssue : newIssues) { + newIssue.setAuthor(personMap.get(newIssue.getAuthor().getId())); + + newIssue.setAssignees( + newIssue.getAssignees().stream() + .map(reviewer -> personMap.get(reviewer.getId())) + .collect(Collectors.toList()) + ); + } + } + +} \ No newline at end of file diff --git a/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetAllIssueForProjectTask.java b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetAllIssueForProjectTask.java new file mode 100644 index 0000000..e217843 --- /dev/null +++ b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetAllIssueForProjectTask.java @@ -0,0 +1,55 @@ +package dev.struchkov.bot.gitlab.core.service.parser.forktask; + +import dev.struchkov.bot.gitlab.core.utils.StringUtils; +import dev.struchkov.bot.gitlab.sdk.domain.IssueJson; +import dev.struchkov.haiti.utils.network.HttpParse; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.text.MessageFormat; +import java.util.List; +import java.util.concurrent.RecursiveTask; + +import static dev.struchkov.haiti.utils.Checker.checkNotEmpty; +import static dev.struchkov.haiti.utils.network.HttpParse.ACCEPT; + +/** + * @author Dmitry Sheyko [24.01.2023] + */ +@Slf4j +@AllArgsConstructor +@RequiredArgsConstructor +public class GetAllIssueForProjectTask extends RecursiveTask> { + + private static final int PAGE_COUNT = 100; + + private final long projectId; + private int pageNumber = 1; + private final String urlIssueOpen; + private final String gitlabToken; + + @Override + @SneakyThrows + protected List compute() { + Thread.sleep(200); + final List issueJson = getIssueJsons(); + if (checkNotEmpty(issueJson) && issueJson.size() == PAGE_COUNT) { + final GetAllIssueForProjectTask newTask = new GetAllIssueForProjectTask(projectId, pageNumber + 1, urlIssueOpen, gitlabToken); + newTask.fork(); + issueJson.addAll(newTask.join()); + } + return issueJson; + } + + private List getIssueJsons() { + final List jsons = HttpParse.request(MessageFormat.format(urlIssueOpen, projectId, pageNumber, PAGE_COUNT)) + .header(StringUtils.H_PRIVATE_TOKEN, gitlabToken) + .header(ACCEPT) + .executeList(IssueJson.class); + log.trace("Получено {} шт потенциально новых Issue для проекта id:'{}' ", jsons.size(), projectId); + return jsons; + } + +} \ No newline at end of file diff --git a/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetSingleIssueTask.java b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetSingleIssueTask.java new file mode 100644 index 0000000..f237c67 --- /dev/null +++ b/bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetSingleIssueTask.java @@ -0,0 +1,36 @@ +package dev.struchkov.bot.gitlab.core.service.parser.forktask; + +import dev.struchkov.bot.gitlab.core.utils.StringUtils; +import dev.struchkov.bot.gitlab.sdk.domain.IssueJson; +import dev.struchkov.haiti.utils.network.HttpParse; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.text.MessageFormat; +import java.util.Optional; +import java.util.concurrent.RecursiveTask; + +import static dev.struchkov.haiti.utils.network.HttpParse.ACCEPT; + +@Slf4j +@RequiredArgsConstructor +public class GetSingleIssueTask extends RecursiveTask> { + + private final String urlIssue; + private final long projectId; + private final long issueTwoId; + private final String gitlabToken; + + @Override + @SneakyThrows + protected Optional compute() { + Thread.sleep(200); + final String mrUrl = MessageFormat.format(urlIssue, projectId, issueTwoId); + return HttpParse.request(mrUrl) + .header(ACCEPT) + .header(StringUtils.H_PRIVATE_TOKEN, gitlabToken) + .execute(IssueJson.class); + } + +} \ No newline at end of file diff --git a/bot-data/src/main/java/dev/struchkov/bot/gitlab/data/impl/IssueRepositoryImpl.java b/bot-data/src/main/java/dev/struchkov/bot/gitlab/data/impl/IssueRepositoryImpl.java new file mode 100644 index 0000000..84a8ae8 --- /dev/null +++ b/bot-data/src/main/java/dev/struchkov/bot/gitlab/data/impl/IssueRepositoryImpl.java @@ -0,0 +1,56 @@ +package dev.struchkov.bot.gitlab.data.impl; + +import dev.struchkov.bot.gitlab.context.domain.IdAndStatusIssue; +import dev.struchkov.bot.gitlab.context.domain.IssueState; +import dev.struchkov.bot.gitlab.context.domain.entity.Issue; +import dev.struchkov.bot.gitlab.context.repository.IssueRepository; +import dev.struchkov.bot.gitlab.data.jpa.IssueJpaRepository; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * @author Dmitry Sheyko [24.01.2023] + */ +@Repository +@RequiredArgsConstructor +public class IssueRepositoryImpl implements IssueRepository { + + private final IssueJpaRepository jpaRepository; + + @Override + @Transactional(readOnly = true) + public Set findAllIdByStateIn(@NonNull Set statuses) { + return jpaRepository.findAllIdByStateIn(statuses); + } + + @Override + @Transactional + public Issue save(Issue issue) { + return jpaRepository.save(issue); + } + + @Override + @Transactional(readOnly = true) + public Optional findById(Long issueId) { + return jpaRepository.findById(issueId); + } + + @Override + @Transactional(readOnly = true) + public List findAllById(Set issueIds) { + return jpaRepository.findAllById(issueIds); + } + + @Override + @Transactional + public void deleteByStates(Set states) { + jpaRepository.deleteAllByStateIn(states); + } + +} \ No newline at end of file diff --git a/bot-data/src/main/java/dev/struchkov/bot/gitlab/data/jpa/IssueJpaRepository.java b/bot-data/src/main/java/dev/struchkov/bot/gitlab/data/jpa/IssueJpaRepository.java new file mode 100644 index 0000000..e6941ec --- /dev/null +++ b/bot-data/src/main/java/dev/struchkov/bot/gitlab/data/jpa/IssueJpaRepository.java @@ -0,0 +1,22 @@ +package dev.struchkov.bot.gitlab.data.jpa; + +import dev.struchkov.bot.gitlab.context.domain.IdAndStatusIssue; +import dev.struchkov.bot.gitlab.context.domain.IssueState; +import dev.struchkov.bot.gitlab.context.domain.entity.Issue; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Set; + +/** + * @author Dmitry Sheyko [24.01.2023] + */ +public interface IssueJpaRepository extends JpaRepository { + + @Query("SELECT new dev.struchkov.bot.gitlab.context.domain.IdAndStatusIssue(i.id, i.twoId, i.projectId, i.state) FROM Issue i WHERE i.state IN :states") + Set findAllIdByStateIn(@Param("states") Set states); + + void deleteAllByStateIn(Set states); + +} \ No newline at end of file diff --git a/gitlab-app/src/main/java/dev/struchkov/bot/gitlab/scheduler/SchedulerService.java b/gitlab-app/src/main/java/dev/struchkov/bot/gitlab/scheduler/SchedulerService.java index f02c3da..14b3ae2 100644 --- a/gitlab-app/src/main/java/dev/struchkov/bot/gitlab/scheduler/SchedulerService.java +++ b/gitlab-app/src/main/java/dev/struchkov/bot/gitlab/scheduler/SchedulerService.java @@ -1,9 +1,11 @@ package dev.struchkov.bot.gitlab.scheduler; import dev.struchkov.bot.gitlab.context.service.AppSettingService; +import dev.struchkov.bot.gitlab.context.service.IssueService; import dev.struchkov.bot.gitlab.context.service.MergeRequestsService; import dev.struchkov.bot.gitlab.context.service.PipelineService; import dev.struchkov.bot.gitlab.core.service.parser.DiscussionParser; +import dev.struchkov.bot.gitlab.core.service.parser.IssueParser; import dev.struchkov.bot.gitlab.core.service.parser.MergeRequestParser; import dev.struchkov.bot.gitlab.core.service.parser.PipelineParser; import lombok.RequiredArgsConstructor; @@ -28,6 +30,9 @@ public class SchedulerService { private final PipelineService pipelineService; private final MergeRequestsService mergeRequestsService; + private final IssueParser issueParser; + private final IssueService issueService; + @Scheduled(cron = "0 */1 * * * *") public void newMergeRequest() { log.debug("Запуск процесса обновления данных"); @@ -40,6 +45,9 @@ public class SchedulerService { discussionParser.scanNewDiscussion(); mergeRequestsService.cleanOld(); pipelineService.cleanOld(); + issueParser.parsingNewIssue(); + issueParser.parsingOldIssue(); + issueService.cleanOld(); } else { log.warn("Процесс обновления данных не был выполнен, так как пользователь не выполнил первичную настройку."); } diff --git a/gitlab-app/src/main/resources/application.yml b/gitlab-app/src/main/resources/application.yml index f0a2367..91ce8ea 100644 --- a/gitlab-app/src/main/resources/application.yml +++ b/gitlab-app/src/main/resources/application.yml @@ -16,6 +16,8 @@ spring: jdbc: lob: non_contextual_creation: true + event: + merge.entity_copy_observer: allow telegram-config: bot-username: ${TELEGRAM_BOT_USERNAME} bot-token: ${TELEGRAM_BOT_TOKEN} @@ -50,4 +52,6 @@ gitlab-bot: last-commit-of-merge-request-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/commits?&page=1&per_page=1" new-note-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/discussions/{2}/notes?body={3}" discussions-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/discussions?&page={2,number,#}&per_page={3,number,#}" - discussion-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/discussions/{2}" \ No newline at end of file + discussion-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/discussions/{2}" + issue-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/issues/{1,number,#}" + open-issue-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/issues?state=opened&page={1, number, integer}&per_page={2, number, integer}" \ No newline at end of file diff --git a/gitlab-app/src/main/resources/liquibase/v.2.0.0/2023-01-19-create-tables-for-issue.xml b/gitlab-app/src/main/resources/liquibase/v.2.0.0/2023-01-19-create-tables-for-issue.xml index 7c2c0d8..d1dc525 100644 --- a/gitlab-app/src/main/resources/liquibase/v.2.0.0/2023-01-19-create-tables-for-issue.xml +++ b/gitlab-app/src/main/resources/liquibase/v.2.0.0/2023-01-19-create-tables-for-issue.xml @@ -77,6 +77,12 @@ + + + + + + diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueJson.java index 6d7863d..ee104c4 100644 --- a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueJson.java +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueJson.java @@ -3,11 +3,15 @@ package dev.struchkov.bot.gitlab.sdk.domain; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import lombok.Data; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import java.util.Set; /** @@ -47,7 +51,7 @@ public class IssueJson { private Set labels; private MilestoneJson milestone; - private Set assignees; + private List assignees; private PersonJson author; private IssueTypeJson type; private PersonJson assignee; @@ -64,10 +68,10 @@ public class IssueJson { @JsonProperty("downvotes") private Integer downVotes; - @JsonSerialize(using = LocalDateTimeSerializer.class) - @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) @JsonProperty("due_date") - private LocalDateTime dueDate; + private LocalDate dueDate; private Boolean confidential; @JsonProperty("discussion_locked") diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneJson.java index 33200ec..a46539b 100644 --- a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneJson.java +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneJson.java @@ -3,10 +3,13 @@ package dev.struchkov.bot.gitlab.sdk.domain; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import lombok.Data; +import java.time.LocalDate; import java.time.LocalDateTime; /** @@ -36,15 +39,15 @@ public class MilestoneJson { @JsonProperty("updated_at") private LocalDateTime updatedDate; - @JsonSerialize(using = LocalDateTimeSerializer.class) - @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) @JsonProperty("start_date") - private LocalDateTime startDate; + private LocalDate startDate; - @JsonSerialize(using = LocalDateTimeSerializer.class) - @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) @JsonProperty("due_date") - private LocalDateTime dueDate; + private LocalDate dueDate; private boolean expired; @JsonProperty("web_url") diff --git a/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DeleteFromAssigneesOfIssueNotifyGenerator.java b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DeleteFromAssigneesOfIssueNotifyGenerator.java new file mode 100644 index 0000000..92966c7 --- /dev/null +++ b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DeleteFromAssigneesOfIssueNotifyGenerator.java @@ -0,0 +1,37 @@ +package dev.struchkov.bot.gitlab.telegram.service.notify; + +import dev.struchkov.bot.gitlab.context.domain.notify.issue.*; +import dev.struchkov.bot.gitlab.context.utils.Icons; +import dev.struchkov.godfather.main.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; + +/** + * @author Dmitry Sheyko 26.01.2021 + */ +@Component +public class DeleteFromAssigneesOfIssueNotifyGenerator implements NotifyBoxAnswerGenerator { + + @Override + public BoxAnswer generate(DeleteFromAssigneesNotify notify) { + + final StringBuilder builder = new StringBuilder(Icons.PEN) + .append(String.format(" *You excluded from %s assignees | ", notify.getIssueType())) + .append(notify.getProjectName()).append("*") + .append(Icons.HR) + .append(link(notify.getType(), notify.getUrl())) + .append(Icons.HR) + .append(notify.getUpdateDate()); + final String notifyMessage = builder.toString(); + + return boxAnswer(notifyMessage); + } + + @Override + public String getNotifyType() { + return DeleteFromAssigneesNotify.TYPE; + } + +} \ No newline at end of file diff --git a/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DescriptionIssueNotifyGenerator.java b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DescriptionIssueNotifyGenerator.java new file mode 100644 index 0000000..67f612e --- /dev/null +++ b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DescriptionIssueNotifyGenerator.java @@ -0,0 +1,39 @@ +package dev.struchkov.bot.gitlab.telegram.service.notify; + +import dev.struchkov.bot.gitlab.context.domain.notify.issue.DescriptionIssueNotify; +import dev.struchkov.bot.gitlab.context.utils.Icons; +import dev.struchkov.godfather.main.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; + +/** + * @author Dmitry Sheyko 26.01.2021 + */ +@Component +public class DescriptionIssueNotifyGenerator implements NotifyBoxAnswerGenerator { + + @Override + public BoxAnswer generate(DescriptionIssueNotify notify) { + + final StringBuilder builder = new StringBuilder(Icons.PEN) + .append(String.format(" *Description of %s changed | ", notify.getIssueType())) + .append(notify.getProjectName()).append("*") + .append(Icons.HR) + .append(link(notify.getType(), notify.getUrl())) + .append(Icons.HR) + .append("new description: ") + .append(notify.getNewDescription()); + + final String notifyMessage = builder.toString(); + + return boxAnswer(notifyMessage); + } + + @Override + public String getNotifyType() { + return DescriptionIssueNotify.TYPE; + } + +} \ No newline at end of file diff --git a/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DueDateIssueNotifyGenerator.java b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DueDateIssueNotifyGenerator.java new file mode 100644 index 0000000..bda0e0e --- /dev/null +++ b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DueDateIssueNotifyGenerator.java @@ -0,0 +1,38 @@ +package dev.struchkov.bot.gitlab.telegram.service.notify; + +import dev.struchkov.bot.gitlab.context.domain.notify.issue.DueDateIssueNotify; +import dev.struchkov.bot.gitlab.context.utils.Icons; +import dev.struchkov.godfather.main.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; + +/** + * @author Dmitry Sheyko 26.01.2021 + */ +@Component +public class DueDateIssueNotifyGenerator implements NotifyBoxAnswerGenerator { + + @Override + public BoxAnswer generate(DueDateIssueNotify notify) { + + final StringBuilder builder = new StringBuilder(Icons.PEN) + .append(String.format(" *Due date of %s changed | ", notify.getIssueType())) + .append(notify.getProjectName()).append("*") + .append(Icons.HR) + .append(link(notify.getType(), notify.getUrl())) + .append(Icons.HR) + .append(notify.getOldDueDate()).append(Icons.ARROW).append(notify.getNewDueDate()); + + final String notifyMessage = builder.toString(); + + return boxAnswer(notifyMessage); + } + + @Override + public String getNotifyType() { + return DueDateIssueNotify.TYPE; + } + +} \ No newline at end of file diff --git a/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/NewIssueNotifyGenerator.java b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/NewIssueNotifyGenerator.java new file mode 100644 index 0000000..bef4468 --- /dev/null +++ b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/NewIssueNotifyGenerator.java @@ -0,0 +1,49 @@ +package dev.struchkov.bot.gitlab.telegram.service.notify; + +import dev.struchkov.bot.gitlab.context.domain.notify.issue.NewIssueNotify; +import dev.struchkov.bot.gitlab.context.utils.Icons; +import dev.struchkov.godfather.main.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import java.util.stream.Collectors; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; +import static dev.struchkov.haiti.utils.Strings.escapeMarkdown; + +/** + * @author Dmitry Sheyko 24.01.2023 + */ +@Component +public class NewIssueNotifyGenerator implements NotifyBoxAnswerGenerator { + + @Override + public BoxAnswer generate(NewIssueNotify notify) { + final String labelText = notify.getLabels().stream() + .map(label -> "#" + label) + .collect(Collectors.joining(" ")); + + final StringBuilder builder = new StringBuilder(Icons.FUN) + .append(String.format(" *New %s assigned to you | ", notify.getIssueType())) + .append(escapeMarkdown(notify.getProjectName())).append("*") + .append(Icons.HR) + .append(link(notify.getType(), notify.getUrl())); + + if (!labelText.isEmpty()) { + builder.append("\n\n").append(labelText); + } + + builder.append(Icons.HR) + .append(Icons.BELL).append(": ").append(notify.getTitle()).append("\n") + .append(Icons.AUTHOR).append(": ").append(notify.getAuthor()); + + final String notifyMessage = builder.toString(); + return boxAnswer(notifyMessage); + } + + @Override + public String getNotifyType() { + return NewIssueNotify.TYPE; + } + +} \ No newline at end of file diff --git a/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/StatusIssueNotifyGenerator.java b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/StatusIssueNotifyGenerator.java new file mode 100644 index 0000000..2af1027 --- /dev/null +++ b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/StatusIssueNotifyGenerator.java @@ -0,0 +1,38 @@ +package dev.struchkov.bot.gitlab.telegram.service.notify; + +import dev.struchkov.bot.gitlab.context.domain.notify.issue.StatusIssueNotify; +import dev.struchkov.bot.gitlab.context.utils.Icons; +import dev.struchkov.godfather.main.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; + +/** + * @author Dmitry Sheyko 26.01.2021 + */ +@Component +public class StatusIssueNotifyGenerator implements NotifyBoxAnswerGenerator { + + @Override + public BoxAnswer generate(StatusIssueNotify notify) { + final StringBuilder builder = new StringBuilder(Icons.PEN) + .append(String.format(" *Status of %s changed | ", notify.getIssueType())) + .append(notify.getProjectName()).append("*") + .append(Icons.HR) + .append(link(notify.getType(), notify.getUrl())) + .append(Icons.HR) + .append(notify.getOldStatus().name()).append(Icons.ARROW).append(notify.getNewStatus().name()); + + + final String notifyMessage = builder.toString(); + + return boxAnswer(notifyMessage); + } + + @Override + public String getNotifyType() { + return StatusIssueNotify.TYPE; + } + +} \ No newline at end of file diff --git a/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TitleIssueNotifyGenerator.java b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TitleIssueNotifyGenerator.java new file mode 100644 index 0000000..f41c257 --- /dev/null +++ b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TitleIssueNotifyGenerator.java @@ -0,0 +1,39 @@ +package dev.struchkov.bot.gitlab.telegram.service.notify; + +import dev.struchkov.bot.gitlab.context.domain.notify.issue.TitleIssueNotify; +import dev.struchkov.bot.gitlab.context.utils.Icons; +import dev.struchkov.godfather.main.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; + +/** + * @author Dmitry Sheyko 26.01.2021 + */ +@Component +public class TitleIssueNotifyGenerator implements NotifyBoxAnswerGenerator { + + @Override + public BoxAnswer generate(TitleIssueNotify notify) { + + final StringBuilder builder = new StringBuilder(Icons.PEN) + .append(String.format(" *Title of %s changed | ", notify.getIssueType())) + .append(notify.getProjectName()).append("*") + .append(Icons.HR) + .append(link(notify.getType(), notify.getUrl())) + .append(Icons.HR) + .append("new title: ") + .append(notify.getNewTitle()); + + final String notifyMessage = builder.toString(); + + return boxAnswer(notifyMessage); + } + + @Override + public String getNotifyType() { + return TitleIssueNotify.TYPE; + } + +} \ No newline at end of file diff --git a/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TypeIssueNotifyGenerator.java b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TypeIssueNotifyGenerator.java new file mode 100644 index 0000000..f9f0f5a --- /dev/null +++ b/telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TypeIssueNotifyGenerator.java @@ -0,0 +1,38 @@ +package dev.struchkov.bot.gitlab.telegram.service.notify; + +import dev.struchkov.bot.gitlab.context.domain.notify.issue.TypeIssueNotify; +import dev.struchkov.bot.gitlab.context.utils.Icons; +import dev.struchkov.godfather.main.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer; + +/** + * @author Dmitry Sheyko 26.01.2021 + */ +@Component +public class TypeIssueNotifyGenerator implements NotifyBoxAnswerGenerator { + + @Override + public BoxAnswer generate(TypeIssueNotify notify) { + + final StringBuilder builder = new StringBuilder(Icons.PEN) + .append(String.format(" *Type of %s changed | ", notify.getIssueType())) + .append(notify.getProjectName()).append("*") + .append(Icons.HR) + .append(link(notify.getType(), notify.getUrl())) + .append(Icons.HR) + .append(notify.getOldType().name()).append(Icons.ARROW).append(notify.getNewType().name()); + + final String notifyMessage = builder.toString(); + + return boxAnswer(notifyMessage); + } + + @Override + public String getNotifyType() { + return TypeIssueNotify.TYPE; + } + +} \ No newline at end of file