feature/issues_v1 #25
@ -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<Person> oldAssignees, List<Person> newAssignees) {
|
||||
final Map<Long, Person> oldMap = oldAssignees.stream().collect(Collectors.toMap(Person::getId, p -> p));
|
||||
final Map<Long, Person> 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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package dev.struchkov.bot.gitlab.context.domain;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 21.01.2021
|
||||
*/
|
||||
public enum IssueState {
|
||||
|
||||
OPENED, CLOSED
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package dev.struchkov.bot.gitlab.context.domain;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 21.01.2021
|
||||
|
||||
*/
|
||||
public enum IssueType {
|
||||
|
||||
ISSUE, INCIDENT
|
||||
|
||||
}
|
@ -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
|
||||
upagge
commented
Лишний перенос строки, и комментарий непонятно что означает, почему это стоит учитывать и почему этот комментарий тут? Пагинация передается и настраивается. Комментарий стоит удалить. Лишний перенос строки, и комментарий непонятно что означает, почему это стоит учитывать и почему этот комментарий тут? Пагинация передается и настраивается. Комментарий стоит удалить.
DmitrySheyko
commented
Правки внес. Правки внес.
Комментарий думал оставить временно, чтобы не забыть осоенность которую нашел в документации. Привычка везде оставлять напоминания.
|
||||
@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<String> 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<Person> 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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<String> labels;
|
||||
private final String confidential;
|
||||
|
||||
@Builder
|
||||
public NewIssueNotify(
|
||||
String projectName,
|
||||
String title,
|
||||
String url,
|
||||
String issueType,
|
||||
String author,
|
||||
String description,
|
||||
String dueDate,
|
||||
Set<String> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<IdAndStatusIssue> findAllIdByStateIn(@NonNull Set<IssueState> states);
|
||||
|
||||
Issue save(Issue issue);
|
||||
|
||||
Optional<Issue> findById(Long issueId);
|
||||
|
||||
List<Issue> findAllById(Set<Long> mergeRequestIds);
|
||||
|
||||
void deleteByStates(Set<IssueState> states);
|
||||
|
||||
}
|
@ -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<Issue> updateAll(@NonNull List<Issue> issues);
|
||||
|
||||
ExistContainer<Issue, Long> existsById(@NonNull Set<Long> issueIds);
|
||||
|
||||
List<Issue> createAll(List<Issue> issues);
|
||||
|
||||
Set<IdAndStatusIssue> getAllId(Set<IssueState> statuses);
|
||||
|
||||
void cleanOld();
|
||||
|
||||
}
|
@ -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 + ")";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -68,4 +68,11 @@ public class GitlabProperty {
|
||||
private String discussionUrl;
|
||||
|
||||
|
||||
/**
|
||||
* Адрес, по которому можно получить ISSUE
|
||||
*/
|
||||
private String issueUrl;
|
||||
|
||||
private String openIssueUrl;
|
||||
|
||||
}
|
||||
|
@ -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<IssueJson, Issue> {
|
||||
|
||||
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<PersonJson> jsonAssignees) {
|
||||
if (checkNotEmpty(jsonAssignees)) {
|
||||
final List<Person> assignees = jsonAssignees.stream()
|
||||
.map(convertPerson::convert)
|
||||
.toList();
|
||||
issue.setAssignees(assignees);
|
||||
}
|
||||
}
|
||||
|
||||
private void convertLabels(Issue issue, Set<String> source) {
|
||||
if (checkNotEmpty(source)) {
|
||||
final Set<String> 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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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<IssueState> 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<Person> 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<Issue, Long> existsById(@NonNull Set<Long> issueIds) {
|
||||
final List<Issue> existsEntity = repository.findAllById(issueIds);
|
||||
final Set<Long> existsIds = existsEntity.stream().map(Issue::getId).collect(Collectors.toSet());
|
||||
if (existsIds.containsAll(issueIds)) {
|
||||
return ExistContainer.allFind(existsEntity);
|
||||
} else {
|
||||
final Set<Long> noExistsId = issueIds.stream()
|
||||
.filter(id -> !existsIds.contains(id))
|
||||
.collect(Collectors.toSet());
|
||||
return ExistContainer.notAllFind(existsEntity, noExistsId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Issue> createAll(List<Issue> newIssues) {
|
||||
return newIssues.stream()
|
||||
.map(this::create)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<Issue> updateAll(@NonNull List<Issue> issues) {
|
||||
return issues.stream()
|
||||
.map(this::update)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<IdAndStatusIssue> getAllId(Set<IssueState> 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");
|
||||
}
|
||||
|
||||
}
|
@ -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<IssueState> 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<IdAndStatusIssue> existIds = issueService.getAllId(OLD_STATUSES);
|
||||
|
||||
final List<Issue> 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<IssueJson> getOldIssues(Set<IdAndStatusIssue> existIds) {
|
||||
final List<ForkJoinTask<Optional<IssueJson>>> 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<Long> projectIds = projectService.getAllIds();
|
||||
|
||||
/*
|
||||
* На основе id проекта, url для получения issues по id проекта и токена пользователя
|
||||
* выгружаем из GitLab список всех IssueJson. Получаем в многопоточном режиме.
|
||||
*/
|
||||
final List<IssueJson> issueJsons = getIssues(projectIds);
|
||||
|
||||
/*
|
||||
* Получаем id всех IssueJson загруженных из GitLab
|
||||
*/
|
||||
if (checkNotEmpty(issueJsons)) {
|
||||
final Set<Long> jsonIds = issueJsons.stream()
|
||||
.map(IssueJson::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final ExistContainer<Issue, Long> existContainer = issueService.existsById(jsonIds);
|
||||
log.trace("Из {} полученных Issues не найдены в хранилище {}", jsonIds.size(), existContainer.getIdNoFound().size());
|
||||
if (!existContainer.isAllFound()) {
|
||||
final List<Issue> 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<IssueJson> getIssues(Set<Long> projectIds) {
|
||||
final List<ForkJoinTask<List<IssueJson>>> 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<Issue> newIssues) {
|
||||
final Map<Long, Person> 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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<List<IssueJson>> {
|
||||
|
||||
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<IssueJson> compute() {
|
||||
Thread.sleep(200);
|
||||
final List<IssueJson> 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<IssueJson> getIssueJsons() {
|
||||
final List<IssueJson> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<Optional<IssueJson>> {
|
||||
|
||||
private final String urlIssue;
|
||||
private final long projectId;
|
||||
private final long issueTwoId;
|
||||
private final String gitlabToken;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected Optional<IssueJson> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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<IdAndStatusIssue> findAllIdByStateIn(@NonNull Set<IssueState> statuses) {
|
||||
return jpaRepository.findAllIdByStateIn(statuses);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Issue save(Issue issue) {
|
||||
return jpaRepository.save(issue);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<Issue> findById(Long issueId) {
|
||||
return jpaRepository.findById(issueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<Issue> findAllById(Set<Long> issueIds) {
|
||||
return jpaRepository.findAllById(issueIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteByStates(Set<IssueState> states) {
|
||||
jpaRepository.deleteAllByStateIn(states);
|
||||
}
|
||||
|
||||
}
|
@ -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<Issue, Long> {
|
||||
|
||||
@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<IdAndStatusIssue> findAllIdByStateIn(@Param("states") Set<IssueState> states);
|
||||
|
||||
void deleteAllByStateIn(Set<IssueState> states);
|
||||
|
||||
}
|
@ -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("Процесс обновления данных не был выполнен, так как пользователь не выполнил первичную настройку.");
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -0,0 +1,114 @@
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.17.xsd">
|
||||
|
||||
<changeSet id="2023-01-19-create-table-issue" author="Dmitry Sheyko">
|
||||
<createTable tableName="issue">
|
||||
<column name="id" type="int">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="two_id" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="project_id" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="title" type="varchar(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="description" type="varchar(2000)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="state" type="varchar(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_date" type="datetime">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="updated_date" type="datetime">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="closed_at" type="datetime">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="closed_by_id" type="int">
|
||||
<constraints nullable="true" foreignKeyName="fk_issue_closed_by_id_person_id" references="person(id)"/>
|
||||
</column>
|
||||
<column name="author_id" type="int">
|
||||
<constraints nullable="false" foreignKeyName="fk_issue_author_id_person_id" references="person(id)"/>
|
||||
</column>
|
||||
<column name="type" type="varchar(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="user_notes_count" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="merge_requests_count" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="up_votes" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="down_votes" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="due_date" type="datetime">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="confidential" type="boolean">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="discussion_locked" type="int">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="task_count" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="task_completed_count" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="web_url" type="varchar(300)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="blocking_issues_count" type="int">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="has_tasks" type="boolean">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="notification" type="boolean">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_assignee" type="boolean">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="2023-01-19-create-table-issue_label" author="Dmitry Sheyko">
|
||||
<createTable tableName="issue_label">
|
||||
<column name="issue_id" type="int">
|
||||
<constraints nullable="false" foreignKeyName="fk_issue_label_issue_id"
|
||||
references="issue(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="label" type="varchar(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="2023-01-19-create-table-issue_assignees" author="Dmitry Sheyko">
|
||||
<createTable tableName="issue_assignees">
|
||||
<column name="issue_id" type="int">
|
||||
<constraints nullable="false" foreignKeyName="fk_issue_assignees_issue_id" references="issue(id)"/>
|
||||
</column>
|
||||
<column name="person_id" type="int">
|
||||
<constraints nullable="false" foreignKeyName="fk_issue_assignees_person_id" references="person(id)"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey tableName="issue_assignees" columnNames="issue_id, person_id"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
@ -9,5 +9,6 @@
|
||||
|
||||
<include file="2022-12-03-create-tables.xml" relativeToChangelogFile="true"/>
|
||||
<include file="2022-12-03-insert.xml" relativeToChangelogFile="true"/>
|
||||
<include file="2023-01-19-create-tables-for-issue.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
</databaseChangeLog>
|
@ -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]
|
||||
upagge
commented
Вот этот класс не обязательно упращать. Можно и даже нужно оставить его в таком виде. Вот этот класс не обязательно упращать. Можно и даже нужно оставить его в таком виде.
DmitrySheyko
commented
Понятно Понятно
|
||||
*/
|
||||
@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<String> labels;
|
||||
private MilestoneJson milestone;
|
||||
private List<PersonJson> 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")
|
||||
upagge
commented
TODO можно удалить, скоре всего это просто оставлено для обратной совместимости со старым апи TODO можно удалить, скоре всего это просто оставлено для обратной совместимости со старым апи
DmitrySheyko
commented
Удалено Удалено
|
||||
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;
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package dev.struchkov.bot.gitlab.sdk.domain;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 21.01.2021
|
||||
*/
|
||||
public enum IssueTypeJson {
|
||||
|
||||
ISSUE, INCIDENT
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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 {
|
||||
upagge
commented
Лишние переносы нужно удалить во всех классах Лишние переносы нужно удалить во всех классах
DmitrySheyko
commented
Переносы удалил Переносы удалил
|
||||
|
||||
@JsonProperty("active")
|
||||
ACTIVE,
|
||||
|
||||
@JsonProperty("closed")
|
||||
CLOSED
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
upagge
commented
Код стайл в данном проекте это перенос строки в начале класса и в конце.
Код стайл в данном проекте это перенос строки в начале класса и в конце.
Пример
```
@Data
public class ReferencesJson {
@JsonProperty("full")
private String fullReference;
}
```
DmitrySheyko
commented
Стиль исправил Стиль исправил
|
||||
@JsonProperty("short")
|
||||
private String shortReference;
|
||||
|
||||
@JsonProperty("relative")
|
||||
private String relativeReference;
|
||||
|
||||
@JsonProperty("full")
|
||||
private String fullReference;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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"
|
||||
}
|
@ -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<DeleteFromAssigneesNotify> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -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<DescriptionIssueNotify> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -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<DueDateIssueNotify> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -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<NewIssueNotify> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -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<StatusIssueNotify> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -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<TitleIssueNotify> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -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<TypeIssueNotify> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user
Нужно писать в одном стиле. Сейчас даже в рамках одного ПР стиль отличается. За образец предлагаю взять MergeRequestState
Правки внес.