Feat: завершен основной функционал issue, выгрузка, удаление, уведомления
This commit is contained in:
parent
a514fcb74f
commit
887f5a36f3
@ -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;
|
||||
|
||||
}
|
@ -22,6 +22,7 @@ import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@ -76,11 +77,10 @@ public class Issue {
|
||||
private Person closedBy;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "issue_labels", joinColumns = @JoinColumn(name = "label_id"))
|
||||
@Column(name = "labels")
|
||||
@CollectionTable(name = "issue_label", joinColumns = @JoinColumn(name = "issue_id"))
|
||||
@Column(name = "label")
|
||||
private Set<String> labels = new HashSet<>();
|
||||
|
||||
@Column(name = "assignees")
|
||||
@OneToMany(
|
||||
fetch = FetchType.LAZY,
|
||||
cascade = {CascadeType.PERSIST, CascadeType.MERGE}
|
||||
@ -113,7 +113,7 @@ public class Issue {
|
||||
private Integer downVotes;
|
||||
|
||||
@Column(name = "due_date")
|
||||
private LocalDateTime dueDate;
|
||||
private LocalDate dueDate;
|
||||
|
||||
@Column(name = "confidential")
|
||||
private Boolean confidential;
|
||||
@ -136,4 +136,10 @@ public class Issue {
|
||||
@Column(name = "has_tasks")
|
||||
private Boolean hasTasks;
|
||||
|
||||
@Column(name = "notification")
|
||||
private boolean notification;
|
||||
|
||||
@Column(name = "is_assignee")
|
||||
private boolean userAssignee;
|
||||
|
||||
}
|
@ -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("Старт обработаки старых Issue");
|
||||
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("Конец обработки старых Issue");
|
||||
}
|
||||
|
||||
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("Старт обработки новых Issue");
|
||||
|
||||
/**
|
||||
* получаем через репозиторий список 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("Из {} полученных MR не найдены в хранилище {}", 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("Конец обработки новых MR");
|
||||
}
|
||||
|
||||
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.haiti.utils.network.HttpParse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.RecursiveTask;
|
||||
|
||||
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
|
||||
import static dev.struchkov.haiti.utils.network.HttpParse.ACCEPT;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko [24.01.2023]
|
||||
*/
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class GetAllIssueForProjectTask extends RecursiveTask<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.haiti.utils.network.HttpParse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.RecursiveTask;
|
||||
|
||||
import static dev.struchkov.haiti.utils.network.HttpParse.ACCEPT;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class GetSingleIssueTask extends RecursiveTask<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,6 +34,9 @@ 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");
|
||||
@ -46,6 +51,9 @@ public class SchedulerService {
|
||||
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");
|
||||
@ -67,6 +75,9 @@ public class SchedulerService {
|
||||
mergeRequestsService.cleanOld();
|
||||
discussionService.cleanOld();
|
||||
pipelineService.cleanOld();
|
||||
issueParser.parsingNewIssue();
|
||||
issueParser.parsingOldIssue();
|
||||
issueService.cleanOld();
|
||||
} else {
|
||||
log.warn("Процесс обновления данных не был выполнен, так как пользователь не выполнил первичную настройку.");
|
||||
}
|
||||
|
@ -28,6 +28,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:}
|
||||
@ -76,4 +82,6 @@ gitlab-bot:
|
||||
scan:
|
||||
general: "0 */1 * * * *"
|
||||
new-project: "0 */1 * * * *"
|
||||
new-merge-request: "0 */1 * * * *"
|
||||
new-merge-request: "0 */1 * * * *"
|
||||
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}"
|
@ -77,6 +77,12 @@
|
||||
<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>
|
||||
|
||||
|
@ -3,11 +3,15 @@ package dev.struchkov.bot.gitlab.sdk.domain;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -47,7 +51,7 @@ public class IssueJson {
|
||||
|
||||
private Set<String> labels;
|
||||
private MilestoneJson milestone;
|
||||
private Set<PersonJson> assignees;
|
||||
private List<PersonJson> assignees;
|
||||
private PersonJson author;
|
||||
private IssueTypeJson type;
|
||||
private PersonJson assignee;
|
||||
@ -64,10 +68,10 @@ public class IssueJson {
|
||||
@JsonProperty("downvotes")
|
||||
private Integer downVotes;
|
||||
|
||||
@JsonSerialize(using = LocalDateTimeSerializer.class)
|
||||
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
|
||||
@JsonSerialize(using = LocalDateSerializer.class)
|
||||
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||
@JsonProperty("due_date")
|
||||
private LocalDateTime dueDate;
|
||||
private LocalDate dueDate;
|
||||
private Boolean confidential;
|
||||
|
||||
@JsonProperty("discussion_locked")
|
||||
|
@ -3,10 +3,13 @@ package dev.struchkov.bot.gitlab.sdk.domain;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@ -36,15 +39,15 @@ public class MilestoneJson {
|
||||
@JsonProperty("updated_at")
|
||||
private LocalDateTime updatedDate;
|
||||
|
||||
@JsonSerialize(using = LocalDateTimeSerializer.class)
|
||||
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
|
||||
@JsonSerialize(using = LocalDateSerializer.class)
|
||||
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||
@JsonProperty("start_date")
|
||||
private LocalDateTime startDate;
|
||||
private LocalDate startDate;
|
||||
|
||||
@JsonSerialize(using = LocalDateTimeSerializer.class)
|
||||
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
|
||||
@JsonSerialize(using = LocalDateSerializer.class)
|
||||
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||
@JsonProperty("due_date")
|
||||
private LocalDateTime dueDate;
|
||||
private LocalDate dueDate;
|
||||
private boolean expired;
|
||||
|
||||
@JsonProperty("web_url")
|
||||
|
@ -0,0 +1,37 @@
|
||||
package dev.struchkov.bot.gitlab.telegram.service.notify;
|
||||
|
||||
import dev.struchkov.bot.gitlab.context.domain.notify.issue.*;
|
||||
import dev.struchkov.bot.gitlab.context.utils.Icons;
|
||||
import dev.struchkov.godfather.main.domain.BoxAnswer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static dev.struchkov.bot.gitlab.context.utils.Icons.link;
|
||||
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 26.01.2021
|
||||
*/
|
||||
@Component
|
||||
public class DeleteFromAssigneesOfIssueNotifyGenerator implements NotifyBoxAnswerGenerator<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.main.domain.BoxAnswer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static dev.struchkov.bot.gitlab.context.utils.Icons.link;
|
||||
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 26.01.2021
|
||||
*/
|
||||
@Component
|
||||
public class DescriptionIssueNotifyGenerator implements NotifyBoxAnswerGenerator<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.main.domain.BoxAnswer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static dev.struchkov.bot.gitlab.context.utils.Icons.link;
|
||||
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 26.01.2021
|
||||
*/
|
||||
@Component
|
||||
public class DueDateIssueNotifyGenerator implements NotifyBoxAnswerGenerator<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.main.domain.BoxAnswer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static dev.struchkov.bot.gitlab.context.utils.Icons.link;
|
||||
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
|
||||
import static dev.struchkov.haiti.utils.Strings.escapeMarkdown;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 24.01.2023
|
||||
*/
|
||||
@Component
|
||||
public class NewIssueNotifyGenerator implements NotifyBoxAnswerGenerator<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.main.domain.BoxAnswer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static dev.struchkov.bot.gitlab.context.utils.Icons.link;
|
||||
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 26.01.2021
|
||||
*/
|
||||
@Component
|
||||
public class StatusIssueNotifyGenerator implements NotifyBoxAnswerGenerator<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.main.domain.BoxAnswer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static dev.struchkov.bot.gitlab.context.utils.Icons.link;
|
||||
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 26.01.2021
|
||||
*/
|
||||
@Component
|
||||
public class TitleIssueNotifyGenerator implements NotifyBoxAnswerGenerator<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.main.domain.BoxAnswer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static dev.struchkov.bot.gitlab.context.utils.Icons.link;
|
||||
import static dev.struchkov.godfather.main.domain.BoxAnswer.boxAnswer;
|
||||
|
||||
/**
|
||||
* @author Dmitry Sheyko 26.01.2021
|
||||
*/
|
||||
@Component
|
||||
public class TypeIssueNotifyGenerator implements NotifyBoxAnswerGenerator<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