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/IssueState.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IssueState.java new file mode 100644 index 0000000..498cd36 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IssueState.java @@ -0,0 +1,10 @@ +package dev.struchkov.bot.gitlab.context.domain; + +/** + * @author Dmitry Sheyko 21.01.2021 + */ +public enum IssueState { + + OPENED, CLOSED + +} \ No newline at end of file diff --git a/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IssueType.java b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IssueType.java new file mode 100644 index 0000000..4a4e477 --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/IssueType.java @@ -0,0 +1,10 @@ +package dev.struchkov.bot.gitlab.context.domain; + +/** + * @author Dmitry Sheyko 21.01.2021 + */ +public enum IssueType { + + ISSUE, INCIDENT + +} 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 new file mode 100644 index 0000000..486bb0d --- /dev/null +++ b/bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/entity/Issue.java @@ -0,0 +1,145 @@ +package dev.struchkov.bot.gitlab.context.domain.entity; + +import dev.struchkov.bot.gitlab.context.domain.IssueState; +import dev.struchkov.bot.gitlab.context.domain.IssueType; +import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames; +import dev.struchkov.haiti.utils.fieldconstants.domain.Mode; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Сущность Issue. + * + * @author Dmitry Sheyko [17.01.2023] + */ +@Getter +@Setter +@Entity +@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE}) +@Table(name = "issue") +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class Issue { + + @Id + @Column(name = "id") + @EqualsAndHashCode.Include + private Long id; + + @Column(name = "two_id") + private Long twoId; + + @Column(name = "project_id") + private Long projectId; + + @Column(name = "title") + private String title; + + @Column(name = "description") + private String description; + + @Enumerated(value = EnumType.STRING) + @Column(name = "state") + private IssueState state; + + @Column(name = "created_date") + private LocalDateTime createdDate; + + @Column(name = "updated_date") + private LocalDateTime updatedDate; + + @Column(name = "closed_at") + private LocalDateTime closeDate; + + @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @JoinColumn(name = "closed_by_id") + private Person closedBy; + + @ElementCollection + @CollectionTable(name = "issue_label", joinColumns = @JoinColumn(name = "issue_id")) + @Column(name = "label") + private Set labels = new HashSet<>(); + + @OneToMany( + fetch = FetchType.LAZY, + cascade = {CascadeType.PERSIST, CascadeType.MERGE} + ) + @JoinTable( + name = "issue_assignees", + joinColumns = @JoinColumn(name = "issue_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id") + ) + private List assignees = new ArrayList<>(); + + @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @JoinColumn(name = "author_id") + private Person author; + + @Enumerated(value = EnumType.STRING) + @Column(name = "type") + private IssueType type; + + @Column(name = "user_notes_count") + private Integer userNotesCount; + + @Column(name = "merge_requests_count") + private Integer mergeRequestsCount; + + @Column(name = "up_votes") + private Integer upVotes; + + @Column(name = "down_votes") + private Integer downVotes; + + @Column(name = "due_date") + private LocalDate dueDate; + + @Column(name = "confidential") + private Boolean confidential; + + @Column(name = "discussion_locked") + private Integer discussionLocked; + + @Column(name = "task_count") + private Integer taskCount; + + @Column(name = "task_completed_count") + private Integer taskCompletedCount; + + @Column(name = "web_url") + private String webUrl; + + @Column(name = "blocking_issues_count") + private Integer blockingIssuesCount; + + @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 cdbd037..3f5f7a9 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 @@ -28,6 +28,7 @@ public class Icons { public static final String NO = "❌"; public static final String NOTIFY = "\uD83D\uDD14"; public static final String GOOD = "\uD83D\uDC4D"; + public static final String BELL ="\uD83D\uDD14"; private Icons() { utilityClass(); @@ -37,4 +38,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..8014bce --- /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("Старт обработаки старых Issues"); + 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("Конец обработки старых Issues"); + } + + 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("Старт обработки новых Issues"); + + /* + * получаем через репозиторий список 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("Из {} полученных Issues не найдены в хранилище {}", 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("Конец обработки новых Issues"); + } + + 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..99bf373 --- /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.bot.gitlab.core.utils.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.bot.gitlab.core.utils.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..0c54efc --- /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.bot.gitlab.core.utils.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.bot.gitlab.core.utils.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 4e9f74d..b29b3ba 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,10 +1,12 @@ 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.DiscussionService; 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 dev.struchkov.bot.gitlab.core.service.parser.ProjectParser; @@ -32,9 +34,12 @@ public class SchedulerService { private final MergeRequestsService mergeRequestsService; private final DiscussionService discussionService; + private final IssueParser issueParser; + private final IssueService issueService; + @Scheduled(cron = "${gitlab-bot.cron.scan.new-project}") public void newProjects() { - log.info("Запуск процесса получение новых репозиториев c GitLab"); + log.info("Запуск процесса получения новых репозиториев c GitLab"); if (!settingService.isFirstStart()) { if (settingService.isOwnerProjectScan()) { projectParser.parseAllProjectOwner(); @@ -43,16 +48,29 @@ public class SchedulerService { projectParser.parseAllPrivateProject(); } } - log.info("Конец процесса получение новых репозиториев c GitLab"); + log.info("Конец процесса получения новых репозиториев c GitLab"); } + + + @Scheduled(cron = "0 */1 * * * *") @Scheduled(cron = "${gitlab-bot.cron.scan.new-merge-request}") public void newMergeRequests() { - log.info("Запуск процесса получение новых MR c GitLab"); + log.info("Запуск процесса получения новых MR c GitLab"); if (!settingService.isFirstStart()) { mergeRequestParser.parsingNewMergeRequest(); } - log.info("Конец процесса получение новых MR c GitLab"); + log.info("Конец процесса получения новых MR c GitLab"); + } + + @Scheduled(cron = "0 */1 * * * *") + @Scheduled(cron = "${gitlab-bot.cron.scan.new-merge-request}") + public void newIssues() { + log.info("Запуск процесса получения новых Issues c GitLab"); + if (!settingService.isFirstStart()) { + issueParser.parsingNewIssue(); + } + log.info("Конец процесса получения новых Issues c GitLab"); } @Scheduled(cron = "${gitlab-bot.cron.scan.general}") @@ -67,6 +85,8 @@ public class SchedulerService { mergeRequestsService.cleanOld(); discussionService.cleanOld(); pipelineService.cleanOld(); + 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 1638eca..f88181e 100644 --- a/gitlab-app/src/main/resources/application.yml +++ b/gitlab-app/src/main/resources/application.yml @@ -16,6 +16,13 @@ spring: jdbc: lob: non_contextual_creation: true +# без данной настройк ипостоянно выбрасывается исключение: +# org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the +# same entity [dev.struchkov.bot.gitlab.context.domain.entity.Person#13445232] are being merged. +# Detached: [dev.struchkov.bot.gitlab.context.domain.entity.Person@cd28ab]; +# Detached: [dev.struchkov.bot.gitlab.context.domain.entity.Person@cd28ab] + event: + merge.entity_copy_observer: allow logging: level: @@ -28,6 +35,12 @@ telegram: autoresponder: threads: ${AUTORESPONDER_THREADS:8} proxy: + event: + merge.entity_copy_observer: allow +telegram-config: + bot-username: ${TELEGRAM_BOT_USERNAME} + bot-token: ${TELEGRAM_BOT_TOKEN} + proxy-config: enable: ${PROXY_ENABLE:false} host: ${PROXY_HOST:} port: ${PROXY_PORT:} @@ -64,6 +77,8 @@ gitlab-bot: 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}" + 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}" --- spring: diff --git a/gitlab-app/src/main/resources/liquibase/v.1.0.0/2023-01-19-create-tables-for-issue.xml b/gitlab-app/src/main/resources/liquibase/v.1.0.0/2023-01-19-create-tables-for-issue.xml new file mode 100644 index 0000000..d1dc525 --- /dev/null +++ b/gitlab-app/src/main/resources/liquibase/v.1.0.0/2023-01-19-create-tables-for-issue.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gitlab-app/src/main/resources/liquibase/v.1.0.0/changelog.xml b/gitlab-app/src/main/resources/liquibase/v.1.0.0/changelog.xml index 31d0925..a392bdb 100644 --- a/gitlab-app/src/main/resources/liquibase/v.1.0.0/changelog.xml +++ b/gitlab-app/src/main/resources/liquibase/v.1.0.0/changelog.xml @@ -9,5 +9,6 @@ + \ No newline at end of file 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 new file mode 100644 index 0000000..ee104c4 --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueJson.java @@ -0,0 +1,113 @@ +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; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ +@Data +public class IssueJson { + + private Long id; + + @JsonProperty("iid") + private Long twoId; + + @JsonProperty("project_id") + private Long projectId; + private String title; + private String description; + private IssueStateJson state; + + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonProperty("created_at") + private LocalDateTime createdDate; + + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonProperty("updated_at") + private LocalDateTime updatedDate; + + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonProperty("closed_at") + private LocalDateTime closedDate; + + @JsonProperty("closed_by") + private PersonJson closedBy; + + private Set labels; + private MilestoneJson milestone; + private List assignees; + private PersonJson author; + private IssueTypeJson type; + private PersonJson assignee; + + @JsonProperty("user_notes_count") + private Integer userNotesCount; + + @JsonProperty("merge_requests_count") + private Integer mergeRequestsCount; + + @JsonProperty("upvotes") + private Integer upVotes; + + @JsonProperty("downvotes") + private Integer downVotes; + + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonProperty("due_date") + private LocalDate dueDate; + private Boolean confidential; + + @JsonProperty("discussion_locked") + private Integer discussionLocked; + + @JsonProperty("issue_type") + private String issueType; + + @JsonProperty("web_url") + private String webUrl; + + @JsonProperty("time_stats") + private TimeStatsJson timeStats; + + @JsonProperty("task_completion_status") + private TaskCompletionStatusJson taskCompletionStatus; + + @JsonProperty("blocking_issues_count") + private Integer blockingIssuesCount; + + @JsonProperty("has_tasks") + private Boolean hasTasks; + + @JsonProperty("_links") + private LinksJson links; + + private ReferencesJson references; + private String severity; + + @JsonProperty("moved_to_id") + private Long movedToId; + + @JsonProperty("service_desk_reply_to") + private Long serviceDescReplyTo; + + @JsonProperty("epic_issue_id") + private Long epicId; + +} \ No newline at end of file diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueStateJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueStateJson.java new file mode 100644 index 0000000..b37d121 --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueStateJson.java @@ -0,0 +1,16 @@ +package dev.struchkov.bot.gitlab.sdk.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ +public enum IssueStateJson { + + @JsonProperty("opened") + OPENED, + + @JsonProperty("closed") + CLOSED + +} \ No newline at end of file diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueTypeJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueTypeJson.java new file mode 100644 index 0000000..8eba66c --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/IssueTypeJson.java @@ -0,0 +1,10 @@ +package dev.struchkov.bot.gitlab.sdk.domain; + +/** + * @author Dmitry Sheyko 21.01.2021 + */ +public enum IssueTypeJson { + + ISSUE, INCIDENT + +} diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/LinksJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/LinksJson.java new file mode 100644 index 0000000..af13add --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/LinksJson.java @@ -0,0 +1,21 @@ +package dev.struchkov.bot.gitlab.sdk.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ + +@Data +public class LinksJson { + private String self; + private String notes; + + @JsonProperty("award_emoji") + private String awardEmoji; + private String project; + + @JsonProperty("closed_as_duplicate_of") + private String closedAsDuplicateOf; +} \ No newline at end of file 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 new file mode 100644 index 0000000..a46539b --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneJson.java @@ -0,0 +1,56 @@ +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; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ +@Data +public class MilestoneJson { + + private Long id; + + @JsonProperty("iid") + private Long twoId; + + @JsonProperty("project_id") + private Long projectId; + private String title; + private String description; + private MilestoneStateJson state; + + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonProperty("created_at") + private LocalDateTime createdDate; + + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonProperty("updated_at") + private LocalDateTime updatedDate; + + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonProperty("start_date") + private LocalDate startDate; + + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonProperty("due_date") + private LocalDate dueDate; + private boolean expired; + + @JsonProperty("web_url") + private String webUrl; + +} \ No newline at end of file diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneStateJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneStateJson.java new file mode 100644 index 0000000..fca77ad --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/MilestoneStateJson.java @@ -0,0 +1,16 @@ +package dev.struchkov.bot.gitlab.sdk.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ +public enum MilestoneStateJson { + + @JsonProperty("active") + ACTIVE, + + @JsonProperty("closed") + CLOSED + +} \ No newline at end of file diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/ReferencesJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/ReferencesJson.java new file mode 100644 index 0000000..134422f --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/ReferencesJson.java @@ -0,0 +1,21 @@ +package dev.struchkov.bot.gitlab.sdk.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ +@Data +public class ReferencesJson { + + @JsonProperty("short") + private String shortReference; + + @JsonProperty("relative") + private String relativeReference; + + @JsonProperty("full") + private String fullReference; + +} diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/TaskCompletionStatusJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/TaskCompletionStatusJson.java new file mode 100644 index 0000000..6da0be3 --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/TaskCompletionStatusJson.java @@ -0,0 +1,17 @@ +package dev.struchkov.bot.gitlab.sdk.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ + +@Data +public class TaskCompletionStatusJson { + @JsonProperty("count") + private Integer count; + + @JsonProperty("completed_count") + private Integer completedCount; +} \ No newline at end of file diff --git a/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/TimeStatsJson.java b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/TimeStatsJson.java new file mode 100644 index 0000000..4eeef90 --- /dev/null +++ b/gitlab-sdk/src/main/java/dev/struchkov/bot/gitlab/sdk/domain/TimeStatsJson.java @@ -0,0 +1,24 @@ +package dev.struchkov.bot.gitlab.sdk.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author Dmitry Sheyko [17.01.2023] + */ + +@Data +public class TimeStatsJson { + + @JsonProperty("time_estimate") + private Integer timeEstimate; + + @JsonProperty("total_time_spent") + private Integer totalTimeSpent; // количество секунд затраченых на работы, пример 37800" + + @JsonProperty("human_time_estimate") + private String humanTimeEstimate; + + @JsonProperty("human_total_time_spent") + private String humanTotalTimeSpent; // Время строкой, пример "10h 30m" +} \ No newline at end of file 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..c26f1d7 --- /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.simple.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.simple.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..95bd725 --- /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.simple.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.simple.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..70e709e --- /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.simple.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.simple.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..8a340e1 --- /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.simple.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.simple.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..ae74ff5 --- /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.simple.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.simple.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..d7dc6b1 --- /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.simple.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.simple.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..ed64024 --- /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.simple.domain.BoxAnswer; +import org.springframework.stereotype.Component; + +import static dev.struchkov.bot.gitlab.context.utils.Icons.link; +import static dev.struchkov.godfather.simple.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