Групповые уведомления о пайплайнах

This commit is contained in:
Struchkov Mark 2024-09-03 23:21:17 +03:00
parent 936490de23
commit e5727f2b30
No known key found for this signature in database
GPG Key ID: A3F0AC3F0FA52F3C
12 changed files with 197 additions and 46 deletions

View File

@ -21,7 +21,7 @@ public enum PipelineStatus {
SKIPPED("\uD83D\uDD18"), SKIPPED("\uD83D\uDD18"),
MANUAL("\uD83D\uDD79"), MANUAL("\uD83D\uDD79"),
SCHEDULED("\uD83D\uDD52"), SCHEDULED("\uD83D\uDD52"),
NULL("\uD83C\uDD95"); NEW("\uD83C\uDD95");
private final String icon; private final String icon;

View File

@ -8,11 +8,13 @@ import lombok.Getter;
import java.util.Map; import java.util.Map;
import static dev.struchkov.bot.gitlab.context.domain.notify.group.mr.NewMergeRequestGroupPersonalNotifyFields.CLASS_NAME;
@Getter @Getter
@FieldNames @FieldNames
public class NewMergeRequestGroupPersonalNotify implements GroupNotify { public class NewMergeRequestGroupPersonalNotify implements GroupNotify {
public static final String TYPE = "NewMergeRequestGroupNotify"; public static final String TYPE = CLASS_NAME;
protected final Long mrId; protected final Long mrId;
protected final String projectName; protected final String projectName;

View File

@ -0,0 +1,37 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.pipeline;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
@Getter
@FieldNames
public class PipelineGroupNotify implements GroupNotify {
public static final String TYPE = "PipelineGroupNotify";
private final String projectName;
private final String refName;
private final PipelineStatus oldStatus;
private final PipelineStatus newStatus;
private final String webUrl;
private final String ownerTelegramUsername;
@Builder
public PipelineGroupNotify(String projectName, String refName, PipelineStatus oldStatus, PipelineStatus newStatus, String webUrl, String ownerTelegramUsername) {
this.projectName = projectName;
this.refName = refName;
this.oldStatus = oldStatus;
this.newStatus = newStatus;
this.webUrl = webUrl;
this.ownerTelegramUsername = ownerTelegramUsername;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -18,8 +18,7 @@ public final class PipelinePersonalNotify implements PersonalNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
private final Long projectId; private final String projectName;
private final Long pipelineId;
private final String refName; private final String refName;
private final PipelineStatus oldStatus; private final PipelineStatus oldStatus;
private final PipelineStatus newStatus; private final PipelineStatus newStatus;
@ -27,15 +26,13 @@ public final class PipelinePersonalNotify implements PersonalNotify {
@Builder @Builder
public PipelinePersonalNotify( public PipelinePersonalNotify(
Long projectId, String projectName,
Long pipelineId,
String refName, String refName,
PipelineStatus oldStatus, PipelineStatus oldStatus,
PipelineStatus newStatus, PipelineStatus newStatus,
String webUrl String webUrl
) { ) {
this.projectId = projectId; this.projectName = projectName;
this.pipelineId = pipelineId;
this.refName = refName; this.refName = refName;
this.oldStatus = oldStatus; this.oldStatus = oldStatus;
this.newStatus = newStatus; this.newStatus = newStatus;

View File

@ -18,6 +18,8 @@ public interface PersonRepository {
List<Person> findAllById(Set<Long> personIds); List<Person> findAllById(Set<Long> personIds);
List<PersonTelegram> getAllTelegramInfoByIds(Set<Long> personIds); Optional<PersonTelegram> findTelegramInfoById(Long personId);
List<PersonTelegram> findAllTelegramInfoByIds(Set<Long> personIds);
} }

View File

@ -6,6 +6,7 @@ import lombok.NonNull;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
/** /**
@ -21,6 +22,8 @@ public interface PersonService {
List<Person> createAll(List<Person> newPersons); List<Person> createAll(List<Person> newPersons);
Optional<String> getTelegramUsernamesByPersonIds(Long personId);
Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds); Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds);
} }

View File

@ -6,8 +6,12 @@ import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline; import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.context.domain.event.NewPipelineEvent; import dev.struchkov.bot.gitlab.context.domain.event.NewPipelineEvent;
import dev.struchkov.bot.gitlab.context.domain.event.UpdatePipelineEvent; import dev.struchkov.bot.gitlab.context.domain.event.UpdatePipelineEvent;
import dev.struchkov.bot.gitlab.context.domain.notify.group.pipeline.PipelineGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePersonalNotify;
import dev.struchkov.bot.gitlab.context.service.NotifyService; import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.haiti.utils.Strings;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
@ -15,6 +19,7 @@ import org.springframework.stereotype.Component;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CANCELED; import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CANCELED;
@ -30,24 +35,48 @@ public class PipelineHandler {
// Статусы пайплайнов, о которых нужно уведомить // Статусы пайплайнов, о которых нужно уведомить
private static final Set<PipelineStatus> notificationStatus = Set.of(FAILED, SUCCESS, CANCELED, SKIPPED); private static final Set<PipelineStatus> notificationStatus = Set.of(FAILED, SUCCESS, CANCELED, SKIPPED);
private final PersonService personService;
private final PersonInformation personInformation; private final PersonInformation personInformation;
private final NotifyService notifyService; private final NotifyService notifyService;
private final ProjectService projectService;
@EventListener @EventListener
public void newPipelineHandle(NewPipelineEvent event) { public void newPipelineHandle(NewPipelineEvent event) {
final Pipeline pipeline = event.getPipeline(); final Pipeline pipeline = event.getPipeline();
if (isNeedNotifyNewPipeline(pipeline)) {
notifyService.send( final Optional<String> optProjectName = projectService.getProjectNameById(pipeline.getProjectId())
PipelinePersonalNotify.builder() .map(Strings::escapeMarkdown);
.projectId(pipeline.getProjectId())
.newStatus(pipeline.getStatus()) if (isNeedPersonalNotifyPipeline(pipeline)) {
.pipelineId(pipeline.getId()) final PipelinePersonalNotify.PipelinePersonalNotifyBuilder builder = PipelinePersonalNotify.builder()
.refName(pipeline.getRef()) .newStatus(pipeline.getStatus())
.webUrl(pipeline.getWebUrl()) .refName(pipeline.getRef())
.oldStatus(PipelineStatus.NULL) .webUrl(pipeline.getWebUrl())
.build() .oldStatus(PipelineStatus.NEW);
);
optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
} }
if (isNeedPersonalGroupPipeline(pipeline)) {
final Optional<String> optTelegramUsername = personService.getTelegramUsernamesByPersonIds(pipeline.getPerson().getId());
if (optTelegramUsername.isPresent()) {
final String telegramUsername = optTelegramUsername.get();
final PipelineGroupNotify.PipelineGroupNotifyBuilder builder = PipelineGroupNotify.builder()
.newStatus(pipeline.getStatus())
.refName(pipeline.getRef())
.webUrl(pipeline.getWebUrl())
.oldStatus(PipelineStatus.NEW)
.ownerTelegramUsername(telegramUsername);
optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
}
}
} }
@EventListener @EventListener
@ -55,22 +84,43 @@ public class PipelineHandler {
final Pipeline oldPipeline = event.getOldPipeline(); final Pipeline oldPipeline = event.getOldPipeline();
final Pipeline newPipeline = event.getNewPipeline(); final Pipeline newPipeline = event.getNewPipeline();
if (!Objects.equals(oldPipeline.getUpdated(), newPipeline.getUpdated())) { if (!Objects.equals(oldPipeline.getUpdated(), newPipeline.getUpdated())) {
if (isNeedNotifyNewPipeline(newPipeline)) { final Optional<String> optProjectName = projectService.getProjectNameById(newPipeline.getProjectId())
notifyService.send( .map(Strings::escapeMarkdown);
PipelinePersonalNotify.builder()
.projectId(newPipeline.getProjectId()) if (isNeedPersonalNotifyPipeline(newPipeline)) {
.newStatus(newPipeline.getStatus()) final PipelinePersonalNotify.PipelinePersonalNotifyBuilder builder = PipelinePersonalNotify.builder()
.pipelineId(newPipeline.getId()) .newStatus(newPipeline.getStatus())
.refName(newPipeline.getRef()) .refName(newPipeline.getRef())
.webUrl(newPipeline.getWebUrl()) .webUrl(newPipeline.getWebUrl())
.oldStatus(oldPipeline.getStatus()) .oldStatus(oldPipeline.getStatus());
.build()
); optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
} }
if (isNeedPersonalGroupPipeline(newPipeline)) {
final Optional<String> optTelegramUsername = personService.getTelegramUsernamesByPersonIds(newPipeline.getPerson().getId());
if (optTelegramUsername.isEmpty()) {
final String telegramUsername = optTelegramUsername.get();
final PipelineGroupNotify.PipelineGroupNotifyBuilder builder = PipelineGroupNotify.builder()
.newStatus(newPipeline.getStatus())
.refName(newPipeline.getRef())
.webUrl(newPipeline.getWebUrl())
.oldStatus(oldPipeline.getStatus())
.ownerTelegramUsername(telegramUsername);
optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
}
}
} }
} }
private boolean isNeedNotifyNewPipeline(@NonNull Pipeline pipeline) { private boolean isNeedPersonalNotifyPipeline(@NonNull Pipeline pipeline) {
final Person personPipelineCreator = pipeline.getPerson(); final Person personPipelineCreator = pipeline.getPerson();
return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления
&& checkNotNull(personPipelineCreator) // Создатель пайплайна не null && checkNotNull(personPipelineCreator) // Создатель пайплайна не null
@ -78,4 +128,11 @@ public class PipelineHandler {
&& LocalDateTime.now().minusDays(1).isBefore(pipeline.getCreated()); // Пайплан был создан не более 24 часов назад && LocalDateTime.now().minusDays(1).isBefore(pipeline.getCreated()); // Пайплан был создан не более 24 часов назад
} }
private boolean isNeedPersonalGroupPipeline(@NonNull Pipeline pipeline) {
final Person personPipelineCreator = pipeline.getPerson();
return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления
&& checkNotNull(personPipelineCreator) // Создатель пайплайна не null
&& LocalDateTime.now().minusDays(1).isBefore(pipeline.getCreated()); // Пайплан был создан не более 24 часов назад
}
} }

View File

@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -61,9 +62,14 @@ public class PersonServiceImpl implements PersonService {
.toList(); .toList();
} }
@Override
public Optional<String> getTelegramUsernamesByPersonIds(Long personId) {
return repository.findTelegramInfoById(personId).map(PersonTelegram::getTelegramUserName);
}
@Override @Override
public Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds) { public Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds) {
return repository.getAllTelegramInfoByIds(personIds).stream() return repository.findAllTelegramInfoByIds(personIds).stream()
.collect(Collectors.toMap(PersonTelegram::getId, PersonTelegram::getTelegramUserName)); .collect(Collectors.toMap(PersonTelegram::getId, PersonTelegram::getTelegramUserName));
} }

View File

@ -38,7 +38,12 @@ public class PersonRepositoryImpl implements PersonRepository {
} }
@Override @Override
public List<PersonTelegram> getAllTelegramInfoByIds(Set<Long> personIds) { public Optional<PersonTelegram> findTelegramInfoById(Long personId) {
return jpaTelegramRepository.findById(personId);
}
@Override
public List<PersonTelegram> findAllTelegramInfoByIds(Set<Long> personIds) {
return jpaTelegramRepository.findAllById(personIds); return jpaTelegramRepository.findAllById(personIds);
} }

View File

@ -8,4 +8,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
*/ */
public interface PersonJpaRepository extends JpaRepository<Person, Long> { public interface PersonJpaRepository extends JpaRepository<Person, Long> {
} }

View File

@ -1,41 +1,33 @@
package dev.struchkov.bot.gitlab.telegram.service.notify; package dev.struchkov.bot.gitlab.telegram.service.notify;
import dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePersonalNotify;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.bot.gitlab.context.utils.Icons; import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.godfather.simple.domain.BoxAnswer; import dev.struchkov.godfather.simple.domain.BoxAnswer;
import dev.struchkov.haiti.utils.Strings;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Optional;
import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE; import static dev.struchkov.bot.gitlab.telegram.utils.UnitName.DELETE_MESSAGE;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard; import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
import static dev.struchkov.godfather.telegram.domain.keyboard.SimpleKeyBoardLine.keyBoardLine; import static dev.struchkov.godfather.telegram.domain.keyboard.SimpleKeyBoardLine.keyBoardLine;
import static dev.struchkov.godfather.telegram.domain.keyboard.button.SimpleButton.simpleButton; import static dev.struchkov.godfather.telegram.domain.keyboard.button.SimpleButton.simpleButton;
import static dev.struchkov.godfather.telegram.domain.keyboard.button.UrlButton.urlButton; import static dev.struchkov.godfather.telegram.domain.keyboard.button.UrlButton.urlButton;
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN; import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class PipelineNotifyGenerator implements NotifyBoxAnswerGenerator<PipelinePersonalNotify> { public class PipelineNotifyGenerator implements NotifyBoxAnswerGenerator<PipelinePersonalNotify> {
private final ProjectService projectService;
@Override @Override
public BoxAnswer generate(PipelinePersonalNotify notify) { public BoxAnswer generate(PipelinePersonalNotify notify) {
final StringBuilder builder = new StringBuilder(Icons.BUILD).append(" *New pipeline | ").append(notify.getPipelineId()).append("*"); final StringBuilder builder = new StringBuilder(Icons.BUILD).append(" *New pipeline* ");
builder builder
.append(Icons.HR); .append(Icons.HR);
final Optional<String> optProjectName = projectService.getProjectNameById(notify.getProjectId())
.map(Strings::escapeMarkdown);
if (optProjectName.isPresent()) { if (checkNotEmpty(notify.getProjectName())) {
final String projectName = optProjectName.get(); builder.append(Icons.PROJECT).append(": ").append(notify.getProjectName()).append("\n");
builder.append(Icons.PROJECT).append(": ").append(projectName).append("\n");
} }
builder builder

View File

@ -0,0 +1,49 @@
package dev.struchkov.bot.gitlab.telegram.service.notify.group;
import dev.struchkov.bot.gitlab.context.domain.notify.group.pipeline.PipelineGroupNotify;
import dev.struchkov.bot.gitlab.context.utils.Icons;
import dev.struchkov.bot.gitlab.telegram.service.notify.NotifyBoxAnswerGenerator;
import dev.struchkov.godfather.simple.domain.BoxAnswer;
import org.springframework.stereotype.Component;
import static dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard.inlineKeyBoard;
import static dev.struchkov.godfather.telegram.domain.keyboard.SimpleKeyBoardLine.keyBoardLine;
import static dev.struchkov.godfather.telegram.domain.keyboard.button.UrlButton.urlButton;
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@Component
public class PipelineGroupNotifyGenerator implements NotifyBoxAnswerGenerator<PipelineGroupNotify> {
@Override
public BoxAnswer generate(PipelineGroupNotify notify) {
final StringBuilder builder = new StringBuilder(Icons.BUILD).append(" ").append(notify.getOwnerTelegramUsername()).append(" *you started a new pipeline*")
.append(Icons.HR);
if (checkNotEmpty(notify.getProjectName())) {
builder.append(Icons.PROJECT).append(": ").append(notify.getProjectName()).append("\n");
}
builder
.append(Icons.TREE).append(": ").append(notify.getRefName())
.append(Icons.HR)
.append(notify.getOldStatus().getIcon()).append(" ").append(notify.getOldStatus()).append(Icons.ARROW).append(notify.getNewStatus().getIcon()).append(" ").append(notify.getNewStatus());
return BoxAnswer.builder()
.message(builder.toString())
.keyBoard(inlineKeyBoard(
keyBoardLine(
urlButton(Icons.LINK, notify.getWebUrl())
)
))
.payload(ENABLE_MARKDOWN)
.build();
}
@Override
public String getNotifyType() {
return PipelineGroupNotify.TYPE;
}
}