feature/issues_v1 #25

Open
DmitrySheyko wants to merge 12 commits from feature/issues_v1 into develop
34 changed files with 1496 additions and 16 deletions
Showing only changes of commit 887f5a36f3 - Show all commits

View File

@ -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;
};
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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 + ")";
}
}
}

View File

@ -68,4 +68,11 @@ public class GitlabProperty {
private String discussionUrl;
/**
* Адрес, по которому можно получить ISSUE
*/
private String issueUrl;
private String openIssueUrl;
}

View File

@ -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;
};
}
}

View File

@ -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");
}
}

View File

@ -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())
);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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("Процесс обновления данных не был выполнен, так как пользователь не выполнил первичную настройку.");
}

View File

@ -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}"

View File

@ -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>

View File

@ -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")

TODO можно удалить, скоре всего это просто оставлено для обратной совместимости со старым апи

TODO можно удалить, скоре всего это просто оставлено для обратной совместимости со старым апи

Удалено

Удалено

View File

@ -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")

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}