Compare commits

..

12 Commits

Author SHA1 Message Date
e662a96eea Merge remote-tracking branch 'origin/feature/issues_v1' into feature/issues_v1
# Conflicts:
#	bot-context/src/main/java/dev/struchkov/bot/gitlab/context/domain/entity/Issue.java
#	bot-context/src/main/java/dev/struchkov/bot/gitlab/context/utils/Icons.java
#	bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/IssueParser.java
#	bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetAllIssueForProjectTask.java
#	bot-core/src/main/java/dev/struchkov/bot/gitlab/core/service/parser/forktask/GetSingleIssueTask.java
#	gitlab-app/src/main/java/dev/struchkov/bot/gitlab/scheduler/SchedulerService.java
#	gitlab-app/src/main/resources/application.yml
#	gitlab-app/src/main/resources/liquibase/v.1.0.0/2023-01-19-create-tables-for-issue.xml
#	telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DeleteFromAssigneesOfIssueNotifyGenerator.java
#	telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DescriptionIssueNotifyGenerator.java
#	telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/DueDateIssueNotifyGenerator.java
#	telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/NewIssueNotifyGenerator.java
#	telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/StatusIssueNotifyGenerator.java
#	telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TitleIssueNotifyGenerator.java
#	telegram-bot/src/main/java/dev/struchkov/bot/gitlab/telegram/service/notify/TypeIssueNotifyGenerator.java
2023-03-11 13:13:21 +03:00
ee1bebb5e0 Feat: устранены конфликты с версией V.1.0.1 2023-03-11 13:08:03 +03:00
887f5a36f3 Feat: завершен основной функционал issue, выгрузка, удаление, уведомления 2023-03-08 13:37:54 +03:00
a514fcb74f Fix: внесены правки по результатам ревью 1. 2023-03-08 13:33:47 +03:00
35783a55e7 Упростил сущность Issue 2023-03-08 13:33:47 +03:00
7396c2a2ef Feat: создан changelog для таблиц сущности issue. 2023-03-08 13:33:41 +03:00
b5ff193b49 Feat: создана сущность Issue и вспомогательные, приступаю к миграциям БД. 2023-03-08 13:31:45 +03:00
7e1c6c1f44 Feat: завершен основной функционал issue, выгрузка, удаление, уведомления 2023-02-03 20:15:46 +03:00
30ff49eb75 Fix: внесены правки по результатам ревью 1. 2023-01-22 00:56:50 +03:00
331ddd7314
Упростил сущность Issue 2023-01-20 18:36:39 +03:00
b0fb70e150 Feat: создан changelog для таблиц сущности issue. 2023-01-19 18:39:06 +03:00
f146165024 Feat: создана сущность Issue и вспомогательные, приступаю к миграциям БД. 2023-01-18 21:52:25 +03:00
186 changed files with 4787 additions and 3109 deletions

View File

@ -3,17 +3,13 @@ kind: pipeline
type: docker type: docker
name: develop build name: develop build
image_pull_secrets:
- DOCKER_AUTH
trigger: trigger:
branch: branch:
- develop - develop
services: services:
- name: docker - name: docker
# https://hub.docker.com/r/library/docker image: docker:20.10.22-dind-alpine3.17
image: hub.docker.struchkov.dev/docker:27.1.2-dind-alpine3.20
privileged: true privileged: true
volumes: volumes:
- name: dockersock - name: dockersock
@ -28,8 +24,7 @@ volumes:
steps: steps:
- name: create jar - name: create jar
# https://hub.docker.com/_/maven image: maven:3.8.6-eclipse-temurin-17
image: hub.docker.struchkov.dev/maven:3.9-eclipse-temurin-22-alpine
volumes: volumes:
- name: m2 - name: m2
path: /root/.m2/repository path: /root/.m2/repository
@ -37,19 +32,26 @@ steps:
- mvn -U clean package - mvn -U clean package
- name: docker publish develop - name: docker publish develop
image: docker.struchkov.dev/docker-buildx:latest image: upagge/docker-buildx:latest
environment: environment:
STRUCHKOV_DOCKER_REGISTRY_TOKEN:
from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN
STRUCHKOV_DOCKER_IMAGE_NAME:
from_secret: STRUCHKOV_DOCKER_IMAGE_NAME
DOCKER_REGISTRY_TOKEN: DOCKER_REGISTRY_TOKEN:
from_secret: DOCKER_REGISTRY_TOKEN from_secret: DOCKER_REGISTRY_TOKEN
DOCKER_IMAGE_NAME:
from_secret: DOCKER_IMAGE_NAME
DOCKER_REGISTRY_USER: DOCKER_REGISTRY_USER:
from_secret: DOCKER_REGISTRY_USER from_secret: DOCKER_REGISTRY_USER
volumes: volumes:
- name: dockersock - name: dockersock
path: /var/run path: /var/run
commands: commands:
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin - echo "$STRUCHKOV_DOCKER_REGISTRY_TOKEN" | docker login git.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin
- docker buildx create --use - docker buildx create --use
- docker buildx build -f Dockerfile-develop --push --platform linux/amd64,linux/arm64/v8 -t "docker.struchkov.dev/gitlab-notify:develop" . - docker buildx build -f Dockerfile-develop --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:develop" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:develop" .
--- ---
@ -57,17 +59,13 @@ kind: pipeline
type: docker type: docker
name: release build name: release build
image_pull_secrets:
- DOCKER_AUTH
trigger: trigger:
ref: ref:
- refs/tags/v.*.*.* - refs/tags/v.*.*.*
services: services:
- name: docker - name: docker
# https://hub.docker.com/r/library/docker image: docker:20.10.22-dind-alpine3.17
image: hub.docker.struchkov.dev/docker:27.1.2-dind-alpine3.20
privileged: true privileged: true
volumes: volumes:
- name: dockersock - name: dockersock
@ -82,7 +80,7 @@ volumes:
steps: steps:
- name: create jar - name: create jar
image: hub.docker.struchkov.dev/maven:3.9-eclipse-temurin-22-alpine image: maven:3.8.6-eclipse-temurin-17
volumes: volumes:
- name: m2 - name: m2
path: /root/.m2/repository path: /root/.m2/repository
@ -90,117 +88,122 @@ steps:
- mvn -U clean package - mvn -U clean package
- name: docker publish release - name: docker publish release
image: docker.struchkov.dev/docker-buildx:latest image: upagge/docker-buildx:latest
environment:
STRUCHKOV_DOCKER_REGISTRY_TOKEN:
from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN
STRUCHKOV_DOCKER_IMAGE_NAME:
from_secret: STRUCHKOV_DOCKER_IMAGE_NAME
DOCKER_REGISTRY_TOKEN:
from_secret: DOCKER_REGISTRY_TOKEN
DOCKER_IMAGE_NAME:
from_secret: DOCKER_IMAGE_NAME
DOCKER_REGISTRY_USER:
from_secret: DOCKER_REGISTRY_USER
volumes: volumes:
- name: dockersock - name: dockersock
path: /var/run path: /var/run
environment:
DOCKER_REGISTRY_TOKEN:
from_secret: DOCKER_REGISTRY_TOKEN
DOCKER_REGISTRY_USER:
from_secret: DOCKER_REGISTRY_USER
commands: commands:
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin - echo "$STRUCHKOV_DOCKER_REGISTRY_TOKEN" | docker login git.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin
- docker buildx create --use - docker buildx create --use
- docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t "docker.struchkov.dev/gitlab-notify:latest" -t "docker.struchkov.dev/gitlab-notify:$DRONE_TAG" . - docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:latest" -t "$DOCKER_IMAGE_NAME:$DRONE_TAG" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:latest" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:$DRONE_TAG" .
#--- ---
#kind: pipeline kind: pipeline
#type: docker type: docker
#name: create-develop-docs-site name: create-develop-docs-site
#
#trigger:
# branch:
# - develop
# - docs
#
#clone:
# disable: true
#
#steps:
#
# - name: build docs
# image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
# volumes:
# - name: mkdocs_cache
# path: ${DRONE_WORKSPACE}/documentation/ru/.cache
# environment:
# GIT_SSH:
# from_secret: GIT_SSH
# GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
# commands:
# - eval $(ssh-agent -s)
# - mkdir -p ~/.ssh
# - chmod 700 ~/.ssh
# - echo "$GIT_SSH" >> ~/.ssh/id_rsa
# - chmod 600 ~/.ssh/id_rsa
# - ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
# - chmod 644 ~/.ssh/known_hosts
# - git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
# - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
# - git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
# - git checkout $DRONE_COMMIT
# - cd documentation/ru
# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases develop
#
#image_pull_secrets:
# - DOCKER_AUTH
#
#volumes:
# - name: mkdocs_cache
# host:
# path: /drone/volume/mkdocs_cache/gitlab_notification/ru
#
#---
#kind: pipeline
#type: docker
#name: create-release-docs-site
#
#trigger:
# ref:
# - refs/tags/v.*.*.*
#
#clone:
# disable: true
#
#steps:
#
# - name: build docs
# image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
# volumes:
# - name: mkdocs_cache
# path: ${DRONE_WORKSPACE}/documentation/ru/.cache
# environment:
# GIT_SSH:
# from_secret: GIT_SSH
# GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
# commands:
# - eval $(ssh-agent -s)
# - mkdir -p ~/.ssh
# - chmod 700 ~/.ssh
# - echo "$GIT_SSH" >> ~/.ssh/id_rsa
# - chmod 600 ~/.ssh/id_rsa
# - ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
# - chmod 644 ~/.ssh/known_hosts
# - git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
# - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
# - git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
# - git checkout $DRONE_COMMIT
# - cd documentation/ru
# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases ${DRONE_TAG}
# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases latest
#
#image_pull_secrets:
# - DOCKER_AUTH
#
#volumes:
# - name: mkdocs_cache
# host:
# path: /drone/volume/mkdocs_cache/gitlab_notification\
# drone sign --save Telegram-Bots/gitlab-notification trigger:
branch:
- develop
- docs
clone:
disable: true
steps:
- name: build docs
image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
volumes:
- name: mkdocs_cache
path: ${DRONE_WORKSPACE}/documentation/ru/.cache
environment:
GIT_SSH:
from_secret: GIT_SSH
GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
commands:
- eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$GIT_SSH" >> ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
- git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
- git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
- git checkout $DRONE_COMMIT
- cd documentation/ru
- mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases develop
image_pull_secrets:
- DOCKER_AUTH
volumes:
- name: mkdocs_cache
host:
path: /drone/volume/mkdocs_cache/gitlab_notification/ru
---
kind: pipeline
type: docker
name: create-release-docs-site
trigger:
ref:
- refs/tags/v.*.*.*
clone:
disable: true
steps:
- name: build docs
image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
volumes:
- name: mkdocs_cache
path: ${DRONE_WORKSPACE}/documentation/ru/.cache
environment:
GIT_SSH:
from_secret: GIT_SSH
GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
commands:
- eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$GIT_SSH" >> ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
- git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
- git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
- git checkout $DRONE_COMMIT
- cd documentation/ru
- mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases ${DRONE_TAG}
- mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases latest
image_pull_secrets:
- DOCKER_AUTH
volumes:
- name: mkdocs_cache
host:
path: /drone/volume/mkdocs_cache/gitlab_notification
--- ---
kind: signature kind: signature
hmac: b0704e1ed6c75469f2197afa7de1fb6f4a96765bb00f766be8a3da6fc4a87267 hmac: cf1bd0800e8f6bb49dae0a6c5f607676b87d5ee713f4203f4f1ed08a17f71f68
... ...

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: ["https://docs.struchkov.dev/gitlab-notification/ru/latest/support-development/"] custom: ["https://docs.struchkov.dev/gitlab-notification/support-development/"]

View File

@ -1,4 +1,4 @@
FROM eclipse-temurin:17 AS app-build FROM eclipse-temurin:17 as app-build
ENV RELEASE=17 ENV RELEASE=17
WORKDIR /opt/build WORKDIR /opt/build

View File

@ -1,4 +1,4 @@
FROM eclipse-temurin:17 AS app-build FROM eclipse-temurin:17 as app-build
ENV RELEASE=17 ENV RELEASE=17
WORKDIR /opt/build WORKDIR /opt/build

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>dev.struchkov.bot.gitlab</groupId> <groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-bot</artifactId> <artifactId>gitlab-bot</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>1.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bot-context</artifactId> <artifactId>bot-context</artifactId>

View File

@ -1,10 +1,11 @@
package dev.struchkov.bot.gitlab.context.domain.changed; package dev.struchkov.bot.gitlab.context.domain;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.Optional; import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static dev.struchkov.haiti.utils.Checker.checkNull;
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
@ -17,18 +18,18 @@ public enum AssigneeChanged {
private final boolean changed; private final boolean changed;
public static AssigneeChanged valueOf(Long gitlabUserId, Optional<Person> oldAssignee, Optional<Person> newAssignee) { public static AssigneeChanged valueOf(Long gitlabUserId, Person oldAssignee, Person newAssignee) {
if (oldAssignee.isEmpty() && newAssignee.isPresent() && gitlabUserId.equals(newAssignee.get().getId())) { if (checkNull(oldAssignee) && checkNotNull(newAssignee) && gitlabUserId.equals(newAssignee.getId())) {
return AssigneeChanged.BECOME; return AssigneeChanged.BECOME;
} }
if (oldAssignee.isPresent() && newAssignee.isEmpty() && gitlabUserId.equals(oldAssignee.get().getId())) { if (checkNotNull(oldAssignee) && checkNull(newAssignee) && gitlabUserId.equals(oldAssignee.getId())) {
return AssigneeChanged.DELETED; return AssigneeChanged.DELETED;
} }
if (oldAssignee.isPresent() && newAssignee.isPresent() && !oldAssignee.get().getId().equals(newAssignee.get().getId())) { if (checkNotNull(oldAssignee) && checkNotNull(newAssignee) && !oldAssignee.getId().equals(newAssignee.getId())) {
if (gitlabUserId.equals(oldAssignee.get().getId())) { if (gitlabUserId.equals(oldAssignee.getId())) {
return AssigneeChanged.DELETED; return AssigneeChanged.DELETED;
} }
if (gitlabUserId.equals(newAssignee.get().getId())) { if (gitlabUserId.equals(newAssignee.getId())) {
return AssigneeChanged.BECOME; return AssigneeChanged.BECOME;
} }
return AssigneeChanged.NOT_AFFECT_USER; return AssigneeChanged.NOT_AFFECT_USER;

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

@ -0,0 +1,10 @@
package dev.struchkov.bot.gitlab.context.domain;
/**
* @author Dmitry Sheyko 21.01.2021
*/
public enum IssueState {
OPENED, CLOSED
}

View File

@ -0,0 +1,10 @@
package dev.struchkov.bot.gitlab.context.domain;
/**
* @author Dmitry Sheyko 21.01.2021
*/
public enum IssueType {
ISSUE, INCIDENT
}

View File

@ -1,10 +1,6 @@
package dev.struchkov.bot.gitlab.context.domain; package dev.struchkov.bot.gitlab.context.domain;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
/** /**
@ -12,9 +8,6 @@ import lombok.Setter;
*/ */
@Getter @Getter
@Setter @Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PersonInformation { public class PersonInformation {
private String username; private String username;

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"),
NEW("\uD83C\uDD95"); NULL("\uD83C\uDD95");
private final String icon; private final String icon;

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.context.domain.changed; package dev.struchkov.bot.gitlab.context.domain;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.Getter; import lombok.Getter;

View File

@ -1,51 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.changed;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ApprovalChanged {
private Set<Person> newApproval;
private Set<Person> dontApprove;
public static Optional<ApprovalChanged> approvalChanged(List<Person> oldApproval, List<Person> newApproval) {
// Если списки одинаковы, возвращаем пустой Optional
if (Objects.equals(oldApproval, newApproval)) {
return Optional.empty();
}
// Преобразуем списки в множества для удобства вычислений
Set<Person> oldApprovalSet = new HashSet<>(oldApproval);
Set<Person> newApprovalSet = new HashSet<>(newApproval);
// Вычисляем новые одобрения: те, кто есть в новом списке, но нет в старом
final Set<Person> newApprovalSetOnly = new HashSet<>(newApprovalSet);
newApprovalSetOnly.removeAll(oldApprovalSet);
// Вычисляем тех, кто больше не одобряет: те, кто есть в старом списке, но нет в новом
final Set<Person> dontApproveSet = new HashSet<>(oldApprovalSet);
dontApproveSet.removeAll(newApprovalSet);
if (!newApprovalSetOnly.isEmpty() || !dontApproveSet.isEmpty()) {
return Optional.of(new ApprovalChanged(newApprovalSetOnly, dontApproveSet));
}
return Optional.empty();
}
}

View File

@ -10,12 +10,8 @@ import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import java.util.List; import java.util.List;
@ -29,10 +25,7 @@ import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@Getter @Getter
@Setter @Setter
@Entity @Entity
@NoArgsConstructor
@Builder(toBuilder = true)
@Table(name = "discussion") @Table(name = "discussion")
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Discussion { public class Discussion {

View File

@ -0,0 +1,145 @@
package dev.struchkov.bot.gitlab.context.domain.entity;
import dev.struchkov.bot.gitlab.context.domain.IssueState;
import dev.struchkov.bot.gitlab.context.domain.IssueType;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import dev.struchkov.haiti.utils.fieldconstants.domain.Mode;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Сущность Issue.
*
* @author Dmitry Sheyko [17.01.2023]
*/
@Getter
@Setter
@Entity
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@Table(name = "issue")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Issue {
@Id
@Column(name = "id")
@EqualsAndHashCode.Include
private Long id;
@Column(name = "two_id")
private Long twoId;
@Column(name = "project_id")
private Long projectId;
@Column(name = "title")
private String title;
@Column(name = "description")
private String description;
@Enumerated(value = EnumType.STRING)
@Column(name = "state")
private IssueState state;
@Column(name = "created_date")
private LocalDateTime createdDate;
@Column(name = "updated_date")
private LocalDateTime updatedDate;
@Column(name = "closed_at")
private LocalDateTime closeDate;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "closed_by_id")
private Person closedBy;
@ElementCollection
@CollectionTable(name = "issue_label", joinColumns = @JoinColumn(name = "issue_id"))
@Column(name = "label")
private Set<String> labels = new HashSet<>();
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE}
)
@JoinTable(
name = "issue_assignees",
joinColumns = @JoinColumn(name = "issue_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id")
)
private List<Person> assignees = new ArrayList<>();
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "author_id")
private Person author;
@Enumerated(value = EnumType.STRING)
@Column(name = "type")
private IssueType type;
@Column(name = "user_notes_count")
private Integer userNotesCount;
@Column(name = "merge_requests_count")
private Integer mergeRequestsCount;
@Column(name = "up_votes")
private Integer upVotes;
@Column(name = "down_votes")
private Integer downVotes;
@Column(name = "due_date")
private LocalDate dueDate;
@Column(name = "confidential")
private Boolean confidential;
@Column(name = "discussion_locked")
private Integer discussionLocked;
@Column(name = "task_count")
private Integer taskCount;
@Column(name = "task_completed_count")
private Integer taskCompletedCount;
@Column(name = "web_url")
private String webUrl;
@Column(name = "blocking_issues_count")
private Integer blockingIssuesCount;
@Column(name = "has_tasks")
private Boolean hasTasks;
@Column(name = "notification")
private boolean notification;
@Column(name = "is_assignee")
private boolean userAssignee;
}

View File

@ -17,14 +17,9 @@ import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -40,17 +35,12 @@ import java.util.Set;
@Getter @Getter
@Setter @Setter
@Entity @Entity
@ToString(onlyExplicitlyIncluded = true)
@NoArgsConstructor
@Builder(toBuilder = true)
@Table(name = "merge_request")
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE}) @FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@Table(name = "merge_request")
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MergeRequest { public class MergeRequest {
@Id @Id
@ToString.Include
@Column(name = "id") @Column(name = "id")
@EqualsAndHashCode.Include @EqualsAndHashCode.Include
private Long id; private Long id;
@ -61,16 +51,12 @@ public class MergeRequest {
@Column(name = "project_id") @Column(name = "project_id")
private Long projectId; private Long projectId;
@ToString.Include
@Column(name = "title") @Column(name = "title")
private String title; private String title;
@Column(name = "description") @Column(name = "description")
private String description; private String description;
@Column(name = "milestone")
private String milestone;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "state") @Column(name = "state")
private MergeRequestState state; private MergeRequestState state;
@ -121,17 +107,6 @@ public class MergeRequest {
@Column(name = "is_reviewer") @Column(name = "is_reviewer")
private boolean userReviewer; private boolean userReviewer;
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE}
)
@JoinTable(
name = "merge_request_approvals",
joinColumns = @JoinColumn(name = "merge_request_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id")
)
private List<Person> approvals = new ArrayList<>();
@ElementCollection @ElementCollection
@CollectionTable(name = "merge_request_label", joinColumns = @JoinColumn(name = "merge_request_id")) @CollectionTable(name = "merge_request_label", joinColumns = @JoinColumn(name = "merge_request_id"))
@Column(name = "label") @Column(name = "label")

View File

@ -9,7 +9,6 @@ import jakarta.persistence.Table;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
/** /**
* @author upagge 14.01.2021 * @author upagge 14.01.2021
@ -17,10 +16,9 @@ import lombok.ToString;
@Entity @Entity
@Getter @Getter
@Setter @Setter
@ToString @EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Table(name = "person") @Table(name = "person")
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE}) @FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Person { public class Person {
@Id @Id

View File

@ -1,31 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.entity;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import dev.struchkov.haiti.utils.fieldconstants.domain.Mode;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Entity
@Getter
@Setter
@ToString
@Table(name = "person_telegram")
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class PersonTelegram {
@Id
@EqualsAndHashCode.Include
@Column(name = "id")
private Long id;
@Column(name = "telegram_username")
private String telegramUserName;
}

View File

@ -11,12 +11,8 @@ import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -29,11 +25,8 @@ import java.time.LocalDateTime;
@Getter @Getter
@Setter @Setter
@FieldNames @FieldNames
@NoArgsConstructor
@Table(name = "pipeline") @Table(name = "pipeline")
@Builder(toBuilder = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Pipeline { public class Pipeline {
@Id @Id

View File

@ -1,19 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newDiscussion")
public class NewDiscussionEvent {
private Discussion discussion;
}

View File

@ -1,19 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newMergeRequest")
public class NewMergeRequestEvent {
private MergeRequest mergeRequest;
}

View File

@ -1,19 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newPipeline")
public class NewPipelineEvent {
private Pipeline pipeline;
}

View File

@ -1,19 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newProject")
public class NewProjectEvent {
private Project project;
}

View File

@ -1,20 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "updateDiscussion")
public class UpdateDiscussionEvent {
private Discussion oldDiscussion;
private Discussion newDiscussion;
}

View File

@ -1,29 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.changed.ApprovalChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Optional;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "updateMergeRequest")
public class UpdateMergeRequestEvent {
private MergeRequest oldMergeRequest;
private MergeRequest newMergeRequest;
private AssigneeChanged assigneeChanged;
private ReviewerChanged reviewerChanged;
private Optional<ApprovalChanged> optApprovalChanged;
}

View File

@ -1,20 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "updatePipeline")
public class UpdatePipelineEvent {
private Pipeline oldPipeline;
private Pipeline newPipeline;
}

View File

@ -1,5 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.notify;
public interface GroupNotify extends Notify {
}

View File

@ -1,5 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.notify;
public interface PersonalNotify extends Notify{
}

View File

@ -1,13 +1,13 @@
package dev.struchkov.bot.gitlab.context.domain.notify.comment; package dev.struchkov.bot.gitlab.context.domain.notify.comment;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames; import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@FieldNames @FieldNames
public final class NewCommentPersonalNotify implements PersonalNotify { public final class NewCommentNotify implements Notify {
public static final String TYPE = "NewCommentNotify"; public static final String TYPE = "NewCommentNotify";
@ -23,7 +23,7 @@ public final class NewCommentPersonalNotify implements PersonalNotify {
private final int numberNotes; private final int numberNotes;
@Builder @Builder
public NewCommentPersonalNotify( public NewCommentNotify(
String threadId, String threadId,
String mergeRequestName, String mergeRequestName,
String url, String url,

View File

@ -1,38 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.mr;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.group.mr.ConflictMrGroupNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class ConflictMrGroupNotify implements GroupNotify {
public static final String TYPE = CLASS_NAME;
protected final String sourceBranch;
protected final String projectName;
protected final String title;
protected final String url;
protected final String milestone;
protected final String authorTelegramUsername;
@Builder
public ConflictMrGroupNotify(String sourceBranch, String projectName, String title, String url, String milestone, String authorTelegramUsername) {
this.sourceBranch = sourceBranch;
this.projectName = projectName;
this.title = title;
this.url = url;
this.milestone = milestone;
this.authorTelegramUsername = authorTelegramUsername;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -1,51 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.mr;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import java.util.Map;
import static dev.struchkov.bot.gitlab.context.domain.notify.group.mr.NewMergeRequestGroupPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class NewMergeRequestGroupNotify implements GroupNotify {
public static final String TYPE = CLASS_NAME;
protected final Long mrId;
protected final String projectName;
protected final String title;
protected final String url;
protected final String milestone;
protected final String description;
protected final String author;
protected final String targetBranch;
protected final String sourceBranch;
protected final Map<Long, String> reviewerTelegramUsernames;
protected final Person assignee;
@Builder
public NewMergeRequestGroupNotify(Long mrId, String projectName, String title, String url, String milestone, String description, String author, String targetBranch, String sourceBranch, Map<Long, String> reviewerTelegramUsernames, Person assignee) {
this.mrId = mrId;
this.projectName = projectName;
this.title = title;
this.url = url;
this.milestone = milestone;
this.description = description;
this.author = author;
this.targetBranch = targetBranch;
this.sourceBranch = sourceBranch;
this.reviewerTelegramUsernames = reviewerTelegramUsernames;
this.assignee = assignee;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -1,41 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.mr;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
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 NoReviewerGroupNotify implements GroupNotify {
public static final String TYPE = "NoReviewersGroupNotify";
protected final String projectName;
protected final String title;
protected final String url;
protected final String milestone;
protected final String targetBranch;
protected final String sourceBranch;
protected final Person assignee;
protected final String ownerTelegramUsername;
@Builder
public NoReviewerGroupNotify(String projectName, String title, String url, String milestone, String targetBranch, String sourceBranch, Person assignee, String ownerTelegramUsername) {
this.projectName = projectName;
this.title = title;
this.url = url;
this.milestone = milestone;
this.targetBranch = targetBranch;
this.sourceBranch = sourceBranch;
this.assignee = assignee;
this.ownerTelegramUsername = ownerTelegramUsername;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -1,39 +0,0 @@
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;
import static dev.struchkov.bot.gitlab.context.domain.notify.group.pipeline.PipelineGroupNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class PipelineGroupNotify implements GroupNotify {
public static final String TYPE = CLASS_NAME;
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

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

@ -1,42 +0,0 @@
package dev.struchkov.bot.gitlab.context.domain.notify.mergerequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import java.util.Set;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ApprovalChangedMrPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class ApprovalChangedMrPersonalNotify extends MrPersonalNotify {
public static final String TYPE = CLASS_NAME;
private final Set<Person> newApproval;
private final Set<Person> dontApproval;
@Builder
public ApprovalChangedMrPersonalNotify(
Long mrId,
String projectName,
String title,
String url,
String milestone,
Set<Person> newApproval,
Set<Person> dontApproval
) {
super(mrId, projectName, title, url, milestone);
this.newApproval = newApproval;
this.dontApproval = dontApproval;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -4,26 +4,25 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrPersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrNotifyFields.CLASS_NAME;
@Getter @Getter
@FieldNames @FieldNames
public class ConflictMrPersonalNotify extends MrPersonalNotify { public class ConflictMrNotify extends MrNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
private final String sourceBranch; private final String sourceBranch;
@Builder @Builder
private ConflictMrPersonalNotify( private ConflictMrNotify(
Long mrId, Long mrId,
String name, String name,
String url, String url,
String projectKey, String projectKey,
String sourceBranch, String sourceBranch
String milestone
) { ) {
super(mrId, projectKey, name, url, milestone); super(mrId, projectKey, name, url);
this.sourceBranch = sourceBranch; this.sourceBranch = sourceBranch;
} }

View File

@ -4,26 +4,25 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrPersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrNotifyFields.CLASS_NAME;
@Getter @Getter
@FieldNames @FieldNames
public class ConflictResolveMrPersonalNotify extends MrPersonalNotify { public class ConflictResolveMrNotify extends MrNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
private final String sourceBranch; private final String sourceBranch;
@Builder @Builder
private ConflictResolveMrPersonalNotify( private ConflictResolveMrNotify(
Long mrId, Long mrId,
String name, String name,
String url, String url,
String projectKey, String projectKey,
String sourceBranch, String sourceBranch
String milestone
) { ) {
super(mrId, projectKey, name, url, milestone); super(mrId, projectKey, name, url);
this.sourceBranch = sourceBranch; this.sourceBranch = sourceBranch;
} }

View File

@ -1,29 +1,26 @@
package dev.struchkov.bot.gitlab.context.domain.notify.mergerequest; package dev.struchkov.bot.gitlab.context.domain.notify.mergerequest;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public abstract class MrPersonalNotify implements PersonalNotify { public abstract class MrNotify implements Notify {
protected final Long mrId; protected final Long mrId;
protected final String projectName; protected final String projectName;
protected final String title; protected final String title;
protected final String url; protected final String url;
protected final String milestone;
protected MrPersonalNotify( protected MrNotify(
Long mrId, Long mrId,
String projectName, String projectName,
String title, String title,
String url, String url
String milestone
) { ) {
this.mrId = mrId; this.mrId = mrId;
this.projectName = projectName; this.projectName = projectName;
this.title = title; this.title = title;
this.url = url; this.url = url;
this.milestone = milestone;
} }
} }

View File

@ -12,7 +12,7 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrF
@Getter @Getter
@FieldNames @FieldNames
public class NewMrForAssignee extends NewMrPersonalNotify { public class NewMrForAssignee extends NewMrNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
@ -33,8 +33,7 @@ public class NewMrForAssignee extends NewMrPersonalNotify {
Set<String> labels, Set<String> labels,
@Singular List<String> reviewers, @Singular List<String> reviewers,
String oldAssigneeName, String oldAssigneeName,
String newAssigneeName, String newAssigneeName
String milestone
) { ) {
super( super(
mrId, mrId,
@ -45,8 +44,7 @@ public class NewMrForAssignee extends NewMrPersonalNotify {
projectName, projectName,
targetBranch, targetBranch,
sourceBranch, sourceBranch,
labels, labels
milestone
); );
this.reviewers = reviewers; this.reviewers = reviewers;
this.oldAssigneeName = oldAssigneeName; this.oldAssigneeName = oldAssigneeName;

View File

@ -10,7 +10,7 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrF
@Getter @Getter
@FieldNames @FieldNames
public class NewMrForReview extends NewMrPersonalNotify { public class NewMrForReview extends NewMrNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
@ -27,8 +27,7 @@ public class NewMrForReview extends NewMrPersonalNotify {
String targetBranch, String targetBranch,
String sourceBranch, String sourceBranch,
Set<String> labels, Set<String> labels,
String assignee, String assignee
String milestone
) { ) {
super( super(
mrId, mrId,
@ -39,8 +38,7 @@ public class NewMrForReview extends NewMrPersonalNotify {
projectName, projectName,
targetBranch, targetBranch,
sourceBranch, sourceBranch,
labels, labels
milestone
); );
this.assignee = assignee; this.assignee = assignee;
} }

View File

@ -5,7 +5,7 @@ import lombok.Getter;
import java.util.Set; import java.util.Set;
@Getter @Getter
public abstract class NewMrPersonalNotify extends MrPersonalNotify { public abstract class NewMrNotify extends MrNotify {
protected final String description; protected final String description;
protected final String author; protected final String author;
@ -13,7 +13,7 @@ public abstract class NewMrPersonalNotify extends MrPersonalNotify {
protected final String sourceBranch; protected final String sourceBranch;
protected final Set<String> labels; protected final Set<String> labels;
protected NewMrPersonalNotify( protected NewMrNotify(
Long mrId, Long mrId,
String title, String title,
String url, String url,
@ -22,10 +22,9 @@ public abstract class NewMrPersonalNotify extends MrPersonalNotify {
String projectName, String projectName,
String targetBranch, String targetBranch,
String sourceBranch, String sourceBranch,
Set<String> labels, Set<String> labels
String milestone
) { ) {
super(mrId, projectName, title, url, milestone); super(mrId, projectName, title, url);
this.description = description; this.description = description;
this.author = author; this.author = author;
this.targetBranch = targetBranch; this.targetBranch = targetBranch;

View File

@ -5,11 +5,11 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrPersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrNotifyFields.CLASS_NAME;
@Getter @Getter
@FieldNames @FieldNames
public class StatusMrPersonalNotify extends MrPersonalNotify { public class StatusMrNotify extends MrNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
@ -17,16 +17,15 @@ public class StatusMrPersonalNotify extends MrPersonalNotify {
private final MergeRequestState newStatus; private final MergeRequestState newStatus;
@Builder @Builder
private StatusMrPersonalNotify( private StatusMrNotify(
Long mrId, Long mrId,
String name, String name,
String url, String url,
String projectName, String projectName,
MergeRequestState oldStatus, MergeRequestState oldStatus,
MergeRequestState newStatus, MergeRequestState newStatus
String milestone
) { ) {
super(mrId, projectName, name, url, milestone); super(mrId, projectName, name, url);
this.oldStatus = oldStatus; this.oldStatus = oldStatus;
this.newStatus = newStatus; this.newStatus = newStatus;
} }

View File

@ -4,11 +4,11 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrPersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrNotifyFields.CLASS_NAME;
@Getter @Getter
@FieldNames @FieldNames
public class UpdateMrPersonalNotify extends MrPersonalNotify { public class UpdateMrNotify extends MrNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
@ -20,7 +20,7 @@ public class UpdateMrPersonalNotify extends MrPersonalNotify {
private final String comment; private final String comment;
@Builder @Builder
private UpdateMrPersonalNotify( private UpdateMrNotify(
Long mrId, Long mrId,
String name, String name,
String url, String url,
@ -30,10 +30,9 @@ public class UpdateMrPersonalNotify extends MrPersonalNotify {
Long allResolvedTasks, Long allResolvedTasks,
Long personTasks, Long personTasks,
Long personResolvedTasks, Long personResolvedTasks,
String comment, String comment
String milestone
) { ) {
super(mrId, projectName, name, url, milestone); super(mrId, projectName, name, url);
this.author = author; this.author = author;
this.allTasks = allTasks; this.allTasks = allTasks;
this.allResolvedTasks = allResolvedTasks; this.allResolvedTasks = allResolvedTasks;

View File

@ -1,12 +1,12 @@
package dev.struchkov.bot.gitlab.context.domain.notify.pipeline; package dev.struchkov.bot.gitlab.context.domain.notify.pipeline;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus; import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames; import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelineNotifyFields.CLASS_NAME;
/** /**
* @author upagge 17.01.2021 * @author upagge 17.01.2021
@ -14,25 +14,28 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePe
//TODO [16.12.2022|uPagge]: Нужно реализовать заполнение projectName //TODO [16.12.2022|uPagge]: Нужно реализовать заполнение projectName
@Getter @Getter
@FieldNames @FieldNames
public final class PipelinePersonalNotify implements PersonalNotify { public final class PipelineNotify implements Notify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
private final String projectName; private final Long projectId;
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;
private final String webUrl; private final String webUrl;
@Builder @Builder
public PipelinePersonalNotify( public PipelineNotify(
String projectName, Long projectId,
Long pipelineId,
String refName, String refName,
PipelineStatus oldStatus, PipelineStatus oldStatus,
PipelineStatus newStatus, PipelineStatus newStatus,
String webUrl String webUrl
) { ) {
this.projectName = projectName; this.projectId = projectId;
this.pipelineId = pipelineId;
this.refName = refName; this.refName = refName;
this.oldStatus = oldStatus; this.oldStatus = oldStatus;
this.newStatus = newStatus; this.newStatus = newStatus;

View File

@ -1,18 +1,18 @@
package dev.struchkov.bot.gitlab.context.domain.notify.project; package dev.struchkov.bot.gitlab.context.domain.notify.project;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames; import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectPersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectNotifyFields.CLASS_NAME;
/** /**
* @author upagge 15.01.2021 * @author upagge 15.01.2021
*/ */
@Getter @Getter
@FieldNames @FieldNames
public final class NewProjectPersonalNotify implements PersonalNotify { public final class NewProjectNotify implements Notify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
@ -25,7 +25,7 @@ public final class NewProjectPersonalNotify implements PersonalNotify {
private final String httpUrlToRepo; private final String httpUrlToRepo;
@Builder @Builder
public NewProjectPersonalNotify( public NewProjectNotify(
Long projectId, Long projectId,
String projectName, String projectName,
String projectUrl, String projectUrl,

View File

@ -8,14 +8,14 @@ import lombok.Singular;
import java.util.List; import java.util.List;
import static dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewPersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewNotifyFields.CLASS_NAME;
/** /**
* @author upagge 10.09.2020 * @author upagge 10.09.2020
*/ */
@Getter @Getter
@FieldNames @FieldNames
public class DiscussionNewPersonalNotify extends ThreadPersonalNotify { public class DiscussionNewNotify extends ThreadNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
@ -23,7 +23,7 @@ public class DiscussionNewPersonalNotify extends ThreadPersonalNotify {
private final List<Pair<String, String>> notes; private final List<Pair<String, String>> notes;
@Builder @Builder
public DiscussionNewPersonalNotify( public DiscussionNewNotify(
String threadId, String threadId,
String mergeRequestName, String mergeRequestName,
String authorName, String authorName,

View File

@ -4,14 +4,14 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadClosePersonalNotifyFields.CLASS_NAME; import static dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadCloseNotifyFields.CLASS_NAME;
/** /**
* @author upagge 10.09.2020 * @author upagge 10.09.2020
*/ */
@Getter @Getter
@FieldNames @FieldNames
public class ThreadClosePersonalNotify extends ThreadPersonalNotify { public class ThreadCloseNotify extends ThreadNotify {
public static final String TYPE = CLASS_NAME; public static final String TYPE = CLASS_NAME;
@ -21,7 +21,7 @@ public class ThreadClosePersonalNotify extends ThreadPersonalNotify {
private final String messageLastNote; private final String messageLastNote;
@Builder @Builder
protected ThreadClosePersonalNotify( protected ThreadCloseNotify(
String mergeRequestName, String mergeRequestName,
String authorName, String authorName,
String url, String url,

View File

@ -1,17 +1,17 @@
package dev.struchkov.bot.gitlab.context.domain.notify.task; package dev.struchkov.bot.gitlab.context.domain.notify.task;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public abstract class ThreadPersonalNotify implements PersonalNotify { public abstract class ThreadNotify implements Notify {
protected final String mergeRequestName; protected final String mergeRequestName;
protected final String authorName; protected final String authorName;
protected final String url; protected final String url;
protected final String messageTask; protected final String messageTask;
protected ThreadPersonalNotify( protected ThreadNotify(
String mergeRequestName, String mergeRequestName,
String authorName, String authorName,
String url, String url,

View File

@ -1,17 +0,0 @@
package dev.struchkov.bot.gitlab.context.prop;
import lombok.Getter;
import lombok.Setter;
/**
* Основные настройки приложения.
*
* @author upagge 11.10.2020
*/
@Getter
@Setter
public class AppProperty {
private String version;
}

View File

@ -1,15 +0,0 @@
package dev.struchkov.bot.gitlab.context.prop;
import lombok.Getter;
import lombok.Setter;
import java.util.Optional;
@Getter
@Setter
public class GroupNotifyProperty {
private Optional<String> chatId;
private Optional<Integer> threadId;
}

View File

@ -1,16 +0,0 @@
package dev.struchkov.bot.gitlab.context.prop;
import lombok.Getter;
import lombok.Setter;
/**
* @author upagge 15.01.2021
*/
@Getter
@Setter
public class PersonProperty {
private String telegramId;
}

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

@ -1,7 +1,6 @@
package dev.struchkov.bot.gitlab.context.repository; package dev.struchkov.bot.gitlab.context.repository;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.PersonTelegram;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -18,8 +17,4 @@ public interface PersonRepository {
List<Person> findAllById(Set<Long> personIds); List<Person> findAllById(Set<Long> personIds);
Optional<PersonTelegram> findTelegramInfoById(Long personId);
List<PersonTelegram> findAllTelegramInfoByIds(Set<Long> personIds);
} }

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

@ -1,13 +1,11 @@
package dev.struchkov.bot.gitlab.context.service; package dev.struchkov.bot.gitlab.context.service;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import lombok.NonNull; import lombok.NonNull;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
@FunctionalInterface
public interface MessageSendService { public interface MessageSendService {
void send(@NonNull PersonalNotify notify); void send(@NonNull Notify notify);
void send(@NonNull GroupNotify notify);
} }

View File

@ -1,18 +1,15 @@
package dev.struchkov.bot.gitlab.context.service; package dev.struchkov.bot.gitlab.context.service;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify; import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
/** /**
* Сервис по работе с изменениями в битбакете. * Сервис по работе с изменениями в битбакете.
* *
* @author upagge * @author upagge
* @see PersonalNotify * @see Notify
*/ */
public interface NotifyService { public interface NotifyService {
<T extends PersonalNotify> void send(T notify); <T extends Notify> void send(T notify);
<T extends GroupNotify> void send(T notify);
} }

View File

@ -5,8 +5,6 @@ import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.NonNull; import lombok.NonNull;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
/** /**
@ -16,14 +14,12 @@ public interface PersonService {
Person create(@NonNull Person person); Person create(@NonNull Person person);
Person update(@NonNull Person person);
Person getByIdOrThrown(@NonNull Long personId); Person getByIdOrThrown(@NonNull Long personId);
ExistContainer<Person, Long> existsById(Set<Long> personIds); ExistContainer<Person, Long> existsById(Set<Long> personIds);
List<Person> createAll(List<Person> newPersons); List<Person> createAll(List<Person> newPersons);
Optional<String> getTelegramUsernamesByPersonIds(Long personId);
Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds);
} }

View File

@ -19,17 +19,16 @@ public class Icons {
public static final String GREEN_CIRCLE = "\uD83D\uDFE2"; public static final String GREEN_CIRCLE = "\uD83D\uDFE2";
public static final String PEN = "✏️"; public static final String PEN = "✏️";
public static final String ASSIGNEE = "\uD83C\uDFA9"; public static final String ASSIGNEE = "\uD83C\uDFA9";
public static final String APPROVAL = "\uD83D\uDC4D";
public static final String BUILD = "⚙️"; public static final String BUILD = "⚙️";
public static final String LINK = "\uD83D\uDD17"; public static final String LINK = "\uD83D\uDD17";
public static final String REVIEWER = "\uD83D\uDD0E"; public static final String REVIEWER = "\uD83D\uDD0E";
public static final String MILESTONE = "\uD83C\uDFAF";
public static final String PROJECT = "\uD83C\uDFD7"; public static final String PROJECT = "\uD83C\uDFD7";
public static final String DISABLE_NOTIFY = "\uD83D\uDD15"; public static final String DISABLE_NOTIFY = "\uD83D\uDD15";
public static final String YES = ""; public static final String YES = "";
public static final String NO = ""; public static final String NO = "";
public static final String NOTIFY = "\uD83D\uDD14"; public static final String NOTIFY = "\uD83D\uDD14";
public static final String GOOD = "\uD83D\uDC4D"; public static final String GOOD = "\uD83D\uDC4D";
public static final String BELL ="\uD83D\uDD14";
private Icons() { private Icons() {
utilityClass(); utilityClass();
@ -39,4 +38,4 @@ public class Icons {
return "[" + escapeMarkdown(title) + "](" + url + ")"; return "[" + escapeMarkdown(title) + "](" + url + ")";
} }
} }

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>dev.struchkov.bot.gitlab</groupId> <groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-bot</artifactId> <artifactId>gitlab-bot</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>1.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bot-core</artifactId> <artifactId>bot-core</artifactId>
@ -34,10 +34,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -56,6 +52,11 @@
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
</dependency> </dependency>
<dependency>
<groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-sdk</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@ -63,12 +64,10 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>dev.struchkov.sdk.gitlab</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>gitlab-sdk-spring-boot-starter</artifactId> <artifactId>okhttp</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>

View File

@ -0,0 +1,16 @@
package dev.struchkov.bot.gitlab.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ForkJoinPool;
@Configuration
public class CoreConfig {
@Bean("parserPool")
public ForkJoinPool parserPool() {
return new ForkJoinPool(4);
}
}

View File

@ -0,0 +1,21 @@
package dev.struchkov.bot.gitlab.core.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* Основные настройки приложения.
*
* @author upagge 11.10.2020
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "gitlab-bot")
public class AppProperty {
private String version;
}

View File

@ -0,0 +1,78 @@
package dev.struchkov.bot.gitlab.core.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Данные необходимые для взаимодействия с API GitLab.
*
* @author upagge [31.01.2020]
*/
@Getter
@Setter
@Component
@ConfigurationProperties("gitlab-bot.gitlab")
public class GitlabProperty {
private String baseUrl;
private String replaceUrl;
private String usersUrl;
private String userUrl;
private String projectsUrl;
/**
* Адрес, по которому можно получить открытые MR
*/
private String openMergeRequestsUrl;
/**
* Адрес, по которому можно получить закрытые MR
*/
private String closeMergeRequestsUrl;
/**
* Адрес, по которому можно получить комментарии к MR
*/
private String commentsOfMergeRequestUrl;
/**
* Адрес MR
*/
private String mergeRequestUrl;
private String projectAddUrl;
private String noteUrl;
private String notesOfMergeRequestUrl;
private String pipelinesUrl;
private String pipelineUrl;
private String lastCommitOfMergeRequestUrl;
private String newNoteUrl;
/**
* Адрес дискуссий для MR
*/
private String discussionsUrl;
private String discussionUrl;
/**
* Адрес, по которому можно получить ISSUE
*/
private String issueUrl;
private String openIssueUrl;
}

View File

@ -0,0 +1,21 @@
package dev.struchkov.bot.gitlab.core.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author upagge 15.01.2021
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "gitlab-bot.person")
public class PersonProperty {
private String token;
private String telegramId;
}

View File

@ -1,388 +0,0 @@
package dev.struchkov.bot.gitlab.core.handler;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.changed.ApprovalChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
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.event.NewMergeRequestEvent;
import dev.struchkov.bot.gitlab.context.domain.event.UpdateMergeRequestEvent;
import dev.struchkov.bot.gitlab.context.domain.notify.group.mr.ConflictMrGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.group.mr.NewMergeRequestGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.group.mr.NoReviewerGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ApprovalChangedMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForAssignee;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForReview;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
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 lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
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;
import static java.lang.Boolean.TRUE;
@Component
@RequiredArgsConstructor
public class MergeRequestHandler {
private final NotifyService notifyService;
private final PersonService personService;
private final PersonInformation personInformation;
private final ProjectService projectService;
private final DiscussionService discussionService;
@EventListener
public void newMrHandle(NewMergeRequestEvent event) {
final MergeRequest mergeRequest = event.getMergeRequest();
final String projectName = projectService.getByIdOrThrow(mergeRequest.getProjectId()).getName();
final boolean userReviewer = mergeRequest.isUserReviewer();
final boolean userAssignee = mergeRequest.isUserAssignee();
if (mergeRequest.isConflict()) {
sendGroupNotifyAboutConflict(mergeRequest, projectName);
} else {
if (mergeRequest.isNotification()) {
if (userReviewer || userAssignee) {
if (userReviewer) sendNotifyNewMrReview(mergeRequest, projectName);
if (userAssignee) sendNotifyNewAssignee(mergeRequest, projectName, null);
}
}
sendGroupNotifyForReviewers(mergeRequest, projectName);
}
}
@EventListener
public void updateMrHandle(UpdateMergeRequestEvent event) {
final MergeRequest oldMergeRequest = event.getOldMergeRequest();
final MergeRequest newMergeRequest = event.getNewMergeRequest();
final AssigneeChanged assigneeChanged = event.getAssigneeChanged();
final ReviewerChanged reviewerChanged = event.getReviewerChanged();
final Optional<ApprovalChanged> optApprovalChanged = event.getOptApprovalChanged();
final boolean resolveConflict = oldMergeRequest.isConflict() && !newMergeRequest.isConflict();
final boolean newConflict = !oldMergeRequest.isConflict() && newMergeRequest.isConflict();
final boolean isChangedMr = !oldMergeRequest.getUpdatedDate().equals(newMergeRequest.getUpdatedDate());
final Project project = projectService.getByIdOrThrow(newMergeRequest.getProjectId());
if (oldMergeRequest.isNotification()) {
if (newConflict) {
personalNotifyAboutNewConflict(oldMergeRequest, newMergeRequest, project);
}
if (resolveConflict) {
personalNotifyAboutResolveConflict(oldMergeRequest, newMergeRequest, project);
}
if (isChangedMr) {
personalNotifyAboutStatus(oldMergeRequest, newMergeRequest, project);
personalNotifyAboutUpdate(oldMergeRequest, newMergeRequest, project);
}
if (reviewerChanged.isChanged()) {
notifyReviewer(reviewerChanged, newMergeRequest, project);
}
if (assigneeChanged.isChanged()) {
notifyAssignee(assigneeChanged, oldMergeRequest, newMergeRequest, project);
}
if (optApprovalChanged.isPresent()) {
final ApprovalChanged approvalChanged = optApprovalChanged.get();
notifyApproval(newMergeRequest, project, approvalChanged);
}
}
//групповые уведомления
if (newConflict) {
sendGroupNotifyAboutConflict(newMergeRequest, project.getName());
}
if (reviewerChanged.isChanged()) {
sendGroupNotifyForReviewers(newMergeRequest, project.getName());
}
}
private void sendGroupNotifyAboutConflict(MergeRequest mergeRequest, String projectName) {
final Optional<String> optAuthorUserName = personService.getTelegramUsernamesByPersonIds(mergeRequest.getAuthor().getId());
if (optAuthorUserName.isPresent()) {
final String authorUserName = optAuthorUserName.get();
notifyService.send(
ConflictMrGroupNotify.builder()
.projectName(projectName)
.url(mergeRequest.getWebUrl())
.title(mergeRequest.getTitle())
.milestone(mergeRequest.getMilestone())
.sourceBranch(mergeRequest.getSourceBranch())
.authorTelegramUsername(authorUserName)
.build()
);
}
}
private void sendGroupNotifyAboutNoReviewers(MergeRequest mergeRequest, String projectName) {
final Optional<String> optAuthorUserName = personService.getTelegramUsernamesByPersonIds(mergeRequest.getAuthor().getId());
if (optAuthorUserName.isPresent()) {
final String authorUsername = optAuthorUserName.get();
notifyService.send(
NoReviewerGroupNotify.builder()
.ownerTelegramUsername(authorUsername)
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.milestone(mergeRequest.getMilestone())
.projectName(projectName)
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.build()
);
}
}
private void sendGroupNotifyForReviewers(MergeRequest mergeRequest, String projectName) {
if (checkNotEmpty(mergeRequest.getReviewers())) {
final Map<Long, String> reviewerTelegramUsernames = personService.getTelegramUsernamesByPersonIds(mergeRequest.getReviewers().stream().map(Person::getId).collect(Collectors.toSet()));
if (!reviewerTelegramUsernames.isEmpty()) {
notifyService.send(
NewMergeRequestGroupNotify.builder()
.mrId(mergeRequest.getId())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.author(mergeRequest.getAuthor().getName())
.reviewerTelegramUsernames(reviewerTelegramUsernames)
.milestone(mergeRequest.getMilestone())
.projectName(projectName)
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.description(mergeRequest.getDescription())
.build()
);
}
} else {
sendGroupNotifyAboutNoReviewers(mergeRequest, projectName);
}
}
private void notifyApproval(MergeRequest mergeRequest, Project project, ApprovalChanged approvalChanged) {
final Set<Person> newApproval = approvalChanged.getNewApproval().stream()
.filter(approval -> !personInformation.getId().equals(approval.getId()))
.collect(Collectors.toSet());
final Set<Person> dontApproval = approvalChanged.getDontApprove().stream()
.filter(approval -> !personInformation.getId().equals(approval.getId()))
.collect(Collectors.toSet());
if (checkNotEmpty(newApproval) || checkNotEmpty(dontApproval)) {
notifyService.send(
ApprovalChangedMrPersonalNotify.builder()
.mrId(mergeRequest.getId())
.milestone(mergeRequest.getMilestone())
.projectName(project.getName())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.newApproval(newApproval)
.dontApproval(dontApproval)
.build()
);
}
}
private void sendNotifyNewMrReview(MergeRequest mergeRequest, String projectName) {
final NewMrForReview.NewMrForReviewBuilder builder = NewMrForReview.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.milestone(mergeRequest.getMilestone())
.description(mergeRequest.getDescription())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch());
getAssignee(mergeRequest)
.map(Person::getName)
.ifPresent(builder::assignee);
notifyService.send(builder.build());
}
private void sendNotifyNewAssignee(MergeRequest mergeRequest, String projectName, String oldAssigneeName) {
final NewMrForAssignee.NewMrForAssigneeBuilder builder = NewMrForAssignee.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.description(mergeRequest.getDescription())
.milestone(mergeRequest.getMilestone())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.milestone(mergeRequest.getMilestone())
.reviewers(mergeRequest.getReviewers().stream().map(Person::getName).toList());
if (checkNotNull(oldAssigneeName)) {
builder.oldAssigneeName(oldAssigneeName);
getAssignee(mergeRequest)
.map(Person::getName)
.ifPresent(builder::newAssigneeName);
}
notifyService.send(builder.build());
}
private void personalNotifyAboutUpdate(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long botUserGitlabId = personInformation.getId();
if (
!botUserGitlabId.equals(mergeRequest.getAuthor().getId()) // Автор MR не пользователь приложения
&& !Objects.equals(oldMergeRequest.getDateLastCommit(), mergeRequest.getDateLastCommit())
&& !mergeRequest.isConflict() // MR не находится в состоянии конфликта
&& !botUserGitlabId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан НЕ пользователем бота
) {
long allTask = 0;
long resolvedTask = 0;
long allYouTasks = 0;
long resolvedYouTask = 0;
final List<Discussion> discussions = discussionService.getAllByMergeRequestId(oldMergeRequest.getId());
for (Discussion discussion : discussions) {
if (checkNotNull(discussion.getResponsible())) {
final boolean isBotUserAuthorDiscussion = botUserGitlabId.equals(discussion.getFirstNote().getAuthor().getId());
allTask += 1;
if (isBotUserAuthorDiscussion) {
allYouTasks += 1;
}
if (TRUE.equals(discussion.getResolved())) {
resolvedTask += 1;
if (isBotUserAuthorDiscussion) {
resolvedYouTask += 1;
}
}
}
}
final UpdateMrPersonalNotify.UpdateMrPersonalNotifyBuilder notifyBuilder = UpdateMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.author(oldMergeRequest.getAuthor().getName())
.name(oldMergeRequest.getTitle())
.projectName(project.getName())
.url(oldMergeRequest.getWebUrl())
.allTasks(allTask)
.milestone(mergeRequest.getMilestone())
.allResolvedTasks(resolvedTask)
.personTasks(allYouTasks)
.personResolvedTasks(resolvedYouTask);
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
notifyBuilder.comment("The conflict has been resolved");
}
notifyService.send(notifyBuilder.build());
}
}
protected void personalNotifyAboutNewConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (
!oldMergeRequest.isConflict() // У старого MR не было конфликта
&& mergeRequest.isConflict() // А у нового есть
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан пользователем бота
) {
notifyService.send(
ConflictMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.milestone(mergeRequest.getMilestone())
.build()
);
}
}
private void personalNotifyAboutResolveConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
// проверяем даты коммитов, так как при пуше в target ветку MR у которого есть конфликт, конфликт на время пропадает. Судя по всему GitLab после пуша заново проверяет вероятность конфликта. Чаще всего конфликт никуда не девается.
if (Objects.equals(oldMergeRequest.getDateLastCommit(), mergeRequest.getDateLastCommit())) {
mergeRequest.setConflict(true);
} else {
if (gitlabUserId.equals(oldMergeRequest.getAuthor().getId())) {
notifyService.send(
ConflictResolveMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.milestone(mergeRequest.getMilestone())
.build()
);
}
}
}
}
protected void personalNotifyAboutStatus(MergeRequest oldMergeRequest, MergeRequest newMergeRequest, Project project) {
final MergeRequestState oldStatus = oldMergeRequest.getState();
final MergeRequestState newStatus = newMergeRequest.getState();
final Long gitlabUserId = personInformation.getId();
if (
!oldStatus.equals(newStatus) // статус изменился
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // создатель MR является пользователем бота
) {
notifyService.send(
StatusMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.name(newMergeRequest.getTitle())
.url(oldMergeRequest.getWebUrl())
.projectName(project.getName())
.newStatus(newStatus)
.oldStatus(oldStatus)
.milestone(newMergeRequest.getMilestone())
.build()
);
}
}
private Optional<Person> getAssignee(MergeRequest mergeRequest) {
return Optional.ofNullable(mergeRequest.getAssignee());
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление
private void notifyAssignee(AssigneeChanged assigneeChanged, MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
switch (assigneeChanged) {
case BECOME -> sendNotifyNewAssignee(
mergeRequest, project.getName(), getAssignee(oldMergeRequest).map(Person::getName).orElse(null)
);
}
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление ревьювера
private void notifyReviewer(ReviewerChanged reviewerChanged, MergeRequest mergeRequest, Project project) {
switch (reviewerChanged) {
case BECOME -> sendNotifyNewMrReview(mergeRequest, project.getName());
}
}
}

View File

@ -1,138 +0,0 @@
package dev.struchkov.bot.gitlab.core.handler;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
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.event.NewPipelineEvent;
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.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.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CANCELED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.FAILED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SKIPPED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SUCCESS;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
@Component
@RequiredArgsConstructor
public class PipelineHandler {
// Статусы пайплайнов, о которых нужно уведомить
private static final Set<PipelineStatus> notificationStatus = Set.of(FAILED, SUCCESS, CANCELED, SKIPPED);
private final PersonService personService;
private final PersonInformation personInformation;
private final NotifyService notifyService;
private final ProjectService projectService;
@EventListener
public void newPipelineHandle(NewPipelineEvent event) {
final Pipeline pipeline = event.getPipeline();
final Optional<String> optProjectName = projectService.getProjectNameById(pipeline.getProjectId())
.map(Strings::escapeMarkdown);
if (isNeedPersonalNotifyPipeline(pipeline)) {
final PipelinePersonalNotify.PipelinePersonalNotifyBuilder builder = PipelinePersonalNotify.builder()
.newStatus(pipeline.getStatus())
.refName(pipeline.getRef())
.webUrl(pipeline.getWebUrl())
.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
public void updatePipelineHandle(UpdatePipelineEvent event) {
final Pipeline oldPipeline = event.getOldPipeline();
final Pipeline newPipeline = event.getNewPipeline();
if (!Objects.equals(oldPipeline.getUpdated(), newPipeline.getUpdated())) {
final Optional<String> optProjectName = projectService.getProjectNameById(newPipeline.getProjectId())
.map(Strings::escapeMarkdown);
if (isNeedPersonalNotifyPipeline(newPipeline)) {
final PipelinePersonalNotify.PipelinePersonalNotifyBuilder builder = PipelinePersonalNotify.builder()
.newStatus(newPipeline.getStatus())
.refName(newPipeline.getRef())
.webUrl(newPipeline.getWebUrl())
.oldStatus(oldPipeline.getStatus());
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 isNeedPersonalNotifyPipeline(@NonNull Pipeline pipeline) {
final Person personPipelineCreator = pipeline.getPerson();
return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления
&& checkNotNull(personPipelineCreator) // Создатель пайплайна не null
&& personInformation.getId().equals(personPipelineCreator.getId()) // Пользователь приложения является инициатором пайплайна
&& 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

@ -1,36 +0,0 @@
package dev.struchkov.bot.gitlab.core.handler;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.domain.event.NewProjectEvent;
import dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectPersonalNotify;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ProjectHandler {
private final NotifyService notifyService;
private final PersonService personService;
@EventListener
public void handleNewProjectEvent(NewProjectEvent event) {
final Project newProject = event.getProject();
final String authorName = personService.getByIdOrThrown(newProject.getCreatorId()).getName();
notifyService.send(
NewProjectPersonalNotify.builder()
.projectId(newProject.getId())
.projectDescription(newProject.getDescription())
.projectName(newProject.getName())
.projectUrl(newProject.getWebUrl())
.sshUrlToRepo(newProject.getSshUrlToRepo())
.httpUrlToRepo(newProject.getHttpUrlToRepo())
.authorName(authorName)
.build()
);
}
}

View File

@ -1,111 +0,0 @@
package dev.struchkov.bot.gitlab.core.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.context.service.PipelineService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.haiti.utils.container.Pair;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import dev.struchkov.sdk.gitlab.schema.pipeline.PipelineShortJson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CREATED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.MANUAL;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PENDING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PREPARING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.RUNNING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.WAITING_FOR_RESOURCE;
import static dev.struchkov.haiti.utils.Checker.checkFalse;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
/**
* Парсер пайплайнов.
*
* @author upagge 17.01.2021
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PipelineParser {
private static final Set<PipelineStatus> oldSOLD_STATUSES = Set.of(
CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, MANUAL
);
private final GitlabSdkManager gitlabSdkManager;
private final PipelineService pipelineService;
private final ProjectService projectService;
private final ConversionService conversionService;
private LocalDateTime lastUpdate = LocalDateTime.now();
public void scanNewPipeline() {
log.debug("Старт обработки новых пайплайнов");
final Set<Long> projectIds = projectService.getAllIdByProcessingEnable();
final Map<Long, Long> pipelineProjectMap = getPipelineShortJsons(projectIds).stream()
.collect(toMap(PipelineShortJson::getId, PipelineShortJson::getProjectId));
if (checkNotEmpty(pipelineProjectMap)) {
final ExistContainer<Pipeline, Long> existContainer = pipelineService.existsById(pipelineProjectMap.keySet());
if (checkFalse(existContainer.isAllFound())) {
final Set<Pair<Long, Long>> idsNotFound = existContainer.getIdNoFound().stream()
.map(pipelineId -> new Pair<>(pipelineProjectMap.get(pipelineId), pipelineId))
.collect(toSet());
final List<Pipeline> newPipelines = gitlabSdkManager.getAllPipelineForProject(idsNotFound).stream()
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
;
if (checkNotEmpty(newPipelines)) {
pipelineService.createAll(newPipelines);
}
}
}
log.debug("Конец обработки новых пайплайнов");
}
private List<PipelineShortJson> getPipelineShortJsons(Set<Long> projectIds) {
final LocalDateTime newLastUpdate = LocalDateTime.now();
final List<PipelineShortJson> pipelineJsons = gitlabSdkManager.getAllPipeline(projectIds, lastUpdate.minusHours(12L));
lastUpdate = newLastUpdate;
return pipelineJsons;
}
public void scanOldPipeline() {
log.debug("Старт обработки старых пайплайнов");
final List<Pipeline> pipelines = pipelineService.getAllByStatuses(oldSOLD_STATUSES);
final List<Pipeline> newPipelines = gitlabSdkManager.getAllPipelineForProject(
pipelines.stream()
.map(pipeline -> new Pair<>(pipeline.getProjectId(), pipeline.getId()))
.collect(toSet())
).stream()
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
if (checkNotEmpty(newPipelines)) {
pipelineService.updateAll(newPipelines);
}
log.debug("Конец обработки старых пайплайнов");
}
}

View File

@ -1,174 +0,0 @@
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
import dev.struchkov.bot.gitlab.context.repository.DiscussionRepository;
import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.event.NewDiscussionEvent.newDiscussion;
import static dev.struchkov.bot.gitlab.context.domain.event.UpdateDiscussionEvent.updateDiscussion;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.WITHOUT_NOTIFY;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
/**
* Сервис для работы с дискуссиями.
*
* @author upagge 11.02.2021
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscussionServiceImpl implements DiscussionService {
private final DiscussionRepository repository;
private final AppSettingService settingService;
private final GitlabSdkManager gitlabSdkManager;
private final ApplicationEventPublisher eventPublisher;
@Override
@Transactional
public Discussion create(@NonNull Discussion discussion) {
final DiscussionLevel levelDiscussionNotify = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(levelDiscussionNotify)) {
discussion.setNotification(true);
eventPublisher.publishEvent(newDiscussion(discussion));
} else {
discussion.setNotification(false);
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
return repository.save(discussion);
}
@Override
@Transactional
public Discussion update(@NonNull Discussion discussion) {
final Discussion oldDiscussion = repository.findById(discussion.getId())
.orElseThrow(notFoundException("Дискуссия не найдена"));
discussion.setResponsible(oldDiscussion.getResponsible());
discussion.setMergeRequest(oldDiscussion.getMergeRequest());
discussion.setNotification(oldDiscussion.isNotification());
final Person responsiblePerson = discussion.getResponsible();
if (checkNotNull(responsiblePerson)) {
for (Note note : discussion.getNotes()) {
if (responsiblePerson.getId().equals(note.getAuthor().getId())) {
note.setAuthor(responsiblePerson);
}
final Person resolvedBy = note.getResolvedBy();
if (checkNotNull(resolvedBy)) {
if (responsiblePerson.getId().equals(resolvedBy.getId())) {
note.setResolvedBy(responsiblePerson);
}
}
}
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
eventPublisher.publishEvent(updateDiscussion(oldDiscussion, discussion));
return repository.save(discussion);
}
@Override
public List<Discussion> updateAll(@NonNull List<Discussion> discussions) {
return discussions.stream()
.map(this::update)
.collect(Collectors.toList());
}
@Override
public void answer(@NonNull String discussionId, @NonNull String text) {
final Discussion discussion = repository.findById(discussionId)
.orElseThrow(notFoundException("Дисскусия {0} не найдена", discussionId));
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final Long projectId = mergeRequest.getProjectId();
gitlabSdkManager.sendMessageToDiscussion(projectId, mergeRequest.getTwoId(), discussionId, text);
}
@Override
public List<Discussion> getAllByMergeRequestId(@NonNull Long mergeRequestId) {
return repository.findAllByMergeRequestId(mergeRequestId);
}
@Override
public ExistContainer<Discussion, String> existsById(@NonNull Set<String> discussionIds) {
final List<Discussion> existsEntity = repository.findAllById(discussionIds);
final Set<String> existsIds = existsEntity.stream().map(Discussion::getId).collect(Collectors.toSet());
if (existsIds.containsAll(discussionIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<String> noExistsId = discussionIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<Discussion> createAll(@NonNull List<Discussion> newDiscussions) {
return newDiscussions.stream()
.map(this::create)
.toList();
}
@Override
public List<Discussion> getAll() {
return repository.findAll();
}
@Override
public Set<String> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void deleteById(@NonNull String discussionId) {
repository.deleteById(discussionId);
}
@Override
@Transactional
public void cleanOld() {
log.debug("Старт очистки старых дискуссий");
repository.cleanOld();
log.debug("Конец очистки старых дискуссий");
}
@Override
@Transactional
public void notification(boolean enable, String discussionId) {
repository.notification(enable, discussionId);
}
}

View File

@ -1,199 +0,0 @@
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.IdAndStatusPr;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.changed.ApprovalChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.repository.MergeRequestRepository;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.CLOSED;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.MERGED;
import static dev.struchkov.bot.gitlab.context.domain.event.NewMergeRequestEvent.newMergeRequest;
import static dev.struchkov.bot.gitlab.context.domain.event.UpdateMergeRequestEvent.updateMergeRequest;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@Slf4j
@Service
@RequiredArgsConstructor
public class MergeRequestsServiceImpl implements MergeRequestsService {
public static final Set<MergeRequestState> DELETE_STATES = Set.of(MERGED, CLOSED);
private final ApplicationEventPublisher eventPublisher;
private final MergeRequestRepository repository;
private final PersonInformation personInformation;
@Override
@Transactional
public MergeRequest create(@NonNull MergeRequest mergeRequest) {
final boolean botUserReviewer = isBotUserReviewer(mergeRequest);
final boolean botUserAssignee = isBotUserAssigneeAndNotAuthor(mergeRequest);
final boolean botUserAuthor = personInformation.getId().equals(mergeRequest.getAuthor().getId());
mergeRequest.setNotification(botUserReviewer || botUserAssignee || botUserAuthor);
mergeRequest.setUserAssignee(botUserAssignee);
mergeRequest.setUserReviewer(botUserReviewer);
final MergeRequest savedMergeRequest = repository.save(mergeRequest);
eventPublisher.publishEvent(newMergeRequest(savedMergeRequest));
return savedMergeRequest;
}
private boolean isBotUserAssigneeAndNotAuthor(MergeRequest mergeRequest) {
final Long gitlabUserId = personInformation.getId();
final Optional<Person> optAssignee = getAssignee(mergeRequest);
final Person author = mergeRequest.getAuthor();
if (optAssignee.isPresent()) {
final Person assignee = optAssignee.get();
if (gitlabUserId.equals(assignee.getId()) && !isAuthorSameAssignee(author, assignee)) {
return true;
}
}
return false;
}
/**
* Создатель MR является ответственным за этот MR
*
* @return true, если автор и ответственный один и тот же человек.
*/
private boolean isAuthorSameAssignee(Person author, Person assignee) {
return author.getId().equals(assignee.getId());
}
private boolean isBotUserReviewer(MergeRequest savedMergeRequest) {
final List<Person> reviewers = savedMergeRequest.getReviewers();
final Long botUserGitlabId = personInformation.getId();
if (checkNotEmpty(reviewers)) {
for (Person reviewer : reviewers) {
if (botUserGitlabId.equals(reviewer.getId())) {
return true;
}
}
}
return false;
}
@Override
@Transactional
public MergeRequest update(@NonNull MergeRequest mergeRequest) {
final MergeRequest oldMergeRequest = repository.findById(mergeRequest.getId())
.orElseThrow(notFoundException("MergeRequest не найден"));
mergeRequest.setNotification(oldMergeRequest.isNotification());
final Long gitlabUserId = personInformation.getId();
final AssigneeChanged assigneeChanged = AssigneeChanged.valueOf(gitlabUserId, getAssignee(oldMergeRequest), getAssignee(mergeRequest));
final ReviewerChanged reviewerChanged = ReviewerChanged.valueOf(gitlabUserId, oldMergeRequest.getReviewers(), mergeRequest.getReviewers());
final Optional<ApprovalChanged> optApprovalChanged = ApprovalChanged.approvalChanged(oldMergeRequest.getApprovals(), mergeRequest.getApprovals());
mergeRequest.setUserAssignee(assigneeChanged.getNewStatus(oldMergeRequest.isUserAssignee()));
mergeRequest.setUserReviewer(reviewerChanged.getNewStatus(oldMergeRequest.isUserReviewer()));
eventPublisher.publishEvent(
updateMergeRequest(oldMergeRequest, mergeRequest, assigneeChanged, reviewerChanged, optApprovalChanged)
);
return repository.save(mergeRequest);
}
@Override
@Transactional
public List<MergeRequest> updateAll(@NonNull List<MergeRequest> mergeRequests) {
return mergeRequests.stream()
.map(this::update)
.collect(Collectors.toList());
}
@Override
public Set<IdAndStatusPr> getAllId(Set<MergeRequestState> statuses) {
return repository.findAllIdByStateIn(statuses);
}
@Override
public List<MergeRequestForDiscussion> getAllForDiscussion() {
return repository.findAllForDiscussion();
}
@Override
public ExistContainer<MergeRequest, Long> existsById(@NonNull Set<Long> mergeRequestIds) {
final List<MergeRequest> existsEntity = repository.findAllById(mergeRequestIds);
final Set<Long> existsIds = existsEntity.stream().map(MergeRequest::getId).collect(Collectors.toSet());
if (existsIds.containsAll(mergeRequestIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<Long> noExistsId = mergeRequestIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<MergeRequest> createAll(List<MergeRequest> newMergeRequests) {
return newMergeRequests.stream()
.map(this::create)
.toList();
}
@Override
public List<MergeRequest> getAllByReviewerId(@NonNull Long personId) {
return repository.findAllByReviewerId(personId);
}
@Override
public void cleanOld() {
log.debug("Старт очистки старых MR");
repository.deleteByStates(DELETE_STATES);
log.debug("Конец очистки старых MR");
}
@Override
@Transactional(readOnly = true)
public Set<Long> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void notification(boolean enable, @NonNull Long mrId) {
repository.notification(enable, mrId);
}
@Override
@Transactional
public void notificationByProjectId(boolean enable, @NonNull Set<Long> projectIds) {
repository.notificationByProjectId(enable, projectIds);
}
private Optional<Person> getAssignee(MergeRequest mergeRequest) {
return Optional.ofNullable(mergeRequest.getAssignee());
}
}

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.convert; package dev.struchkov.bot.gitlab.core.service.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion; import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.sdk.gitlab.schema.note.DiscussionJson; import dev.struchkov.bot.gitlab.sdk.domain.DiscussionJson;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

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

@ -1,11 +1,11 @@
package dev.struchkov.bot.gitlab.core.convert; package dev.struchkov.bot.gitlab.core.service.convert;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState; import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest; import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.sdk.gitlab.schema.common.PersonJson; import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestJson;
import dev.struchkov.sdk.gitlab.schema.mergerequest.MergeRequestJson; import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestStateJson;
import dev.struchkov.sdk.gitlab.schema.mergerequest.MergeRequestStateJson; import dev.struchkov.bot.gitlab.sdk.domain.PersonJson;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -43,10 +43,6 @@ public class MergeRequestJsonConverter implements Converter<MergeRequestJson, Me
convertLabels(mergeRequest, source.getLabels()); convertLabels(mergeRequest, source.getLabels());
convertReviewers(mergeRequest, source.getReviewers()); convertReviewers(mergeRequest, source.getReviewers());
if (checkNotNull(source.getMilestone())) {
mergeRequest.setMilestone(source.getMilestone().getTitle());
}
if (checkNotNull(source.getAssignee())) { if (checkNotNull(source.getAssignee())) {
mergeRequest.setAssignee(convertPerson.convert(source.getAssignee())); mergeRequest.setAssignee(convertPerson.convert(source.getAssignee()));
} }

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.convert; package dev.struchkov.bot.gitlab.core.service.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Note; import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.sdk.gitlab.schema.note.NoteJson; import dev.struchkov.bot.gitlab.sdk.domain.NoteJson;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.convert; package dev.struchkov.bot.gitlab.core.service.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.sdk.gitlab.schema.common.PersonJson; import dev.struchkov.bot.gitlab.sdk.domain.PersonJson;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -1,9 +1,9 @@
package dev.struchkov.bot.gitlab.core.convert; package dev.struchkov.bot.gitlab.core.service.convert;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus; import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline; import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.sdk.gitlab.schema.pipeline.PipelineJson; import dev.struchkov.bot.gitlab.sdk.domain.PipelineJson;
import dev.struchkov.sdk.gitlab.schema.pipeline.PipelineStatusJson; import dev.struchkov.bot.gitlab.sdk.domain.PipelineStatusJson;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.convert; package dev.struchkov.bot.gitlab.core.service.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Project; import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.sdk.gitlab.schema.repository.ProjectJson; import dev.struchkov.bot.gitlab.sdk.domain.ProjectJson;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.core.service; package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.entity.AppSetting; import dev.struchkov.bot.gitlab.context.domain.entity.AppSetting;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel; import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;

View File

@ -1,23 +1,35 @@
package dev.struchkov.bot.gitlab.core.handler; package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation; import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion; import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion; import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Note; import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.domain.event.NewDiscussionEvent; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.event.UpdateDiscussionEvent; import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel; import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
import dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewPersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadClosePersonalNotify; import dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadCloseNotify;
import dev.struchkov.bot.gitlab.context.repository.DiscussionRepository;
import dev.struchkov.bot.gitlab.context.service.AppSettingService; import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService; import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.NotifyService; import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.haiti.utils.container.Pair; import dev.struchkov.haiti.utils.container.Pair;
import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -30,100 +42,95 @@ import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.NOTIFY_WITH_CONTEXT; import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.NOTIFY_WITH_CONTEXT;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.WITHOUT_NOTIFY; import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.WITHOUT_NOTIFY;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotNull; import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static dev.struchkov.haiti.utils.Checker.checkNull; import static dev.struchkov.haiti.utils.Checker.checkNull;
import static java.lang.Boolean.FALSE; import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
@Component /**
* Сервис для работы с дискуссиями.
*
* @author upagge 11.02.2021
*/
@Slf4j
@Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DiscussionHandler { public class DiscussionServiceImpl implements DiscussionService {
protected static final Pattern PATTERN = Pattern.compile("@[\\w]+"); protected static final Pattern PATTERN = Pattern.compile("@[\\w]+");
private final OkHttpClient client = new OkHttpClient();
private final PersonInformation personInformation; private final DiscussionRepository repository;
private final AppSettingService settingService;
private final DiscussionService discussionService;
private final NotifyService notifyService; private final NotifyService notifyService;
private final AppSettingService settingService;
@EventListener private final PersonInformation personInformation;
public void newDiscussionEvent(NewDiscussionEvent event) { private final GitlabProperty gitlabProperty;
final Discussion discussion = event.getDiscussion(); private final PersonProperty personProperty;
@Override
@Transactional
public Discussion create(@NonNull Discussion discussion) {
final List<Note> notes = discussion.getNotes(); final List<Note> notes = discussion.getNotes();
if (isNeedNotifyNewNote(discussion)) { final DiscussionLevel levelDiscussionNotify = settingService.getLevelDiscussionNotify();
notifyNewThread(discussion); if (!WITHOUT_NOTIFY.equals(levelDiscussionNotify)) {
discussion.setNotification(true);
if (isNeedNotifyNewNote(discussion)) {
notifyNewThread(discussion);
} else {
notes.forEach(note -> notifyAboutPersonalAnswer(discussion, note));
}
} else { } else {
notes.forEach(note -> notifyAboutPersonalAnswer(discussion, note)); discussion.setNotification(false);
} }
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
return repository.save(discussion);
} }
@EventListener @Override
public void updateDiscussionHandle(UpdateDiscussionEvent event) { @Transactional
final Discussion oldDiscussion = event.getOldDiscussion(); public Discussion update(@NonNull Discussion discussion) {
final Discussion newDiscussion = event.getNewDiscussion(); final Discussion oldDiscussion = repository.findById(discussion.getId())
.orElseThrow(notFoundException("Дискуссия не найдена"));
discussion.setResponsible(oldDiscussion.getResponsible());
discussion.setMergeRequest(oldDiscussion.getMergeRequest());
discussion.setNotification(oldDiscussion.isNotification());
final Person responsiblePerson = discussion.getResponsible();
if (checkNotNull(responsiblePerson)) {
for (Note note : discussion.getNotes()) {
if (responsiblePerson.getId().equals(note.getAuthor().getId())) {
note.setAuthor(responsiblePerson);
}
final Person resolvedBy = note.getResolvedBy();
if (checkNotNull(resolvedBy)) {
if (responsiblePerson.getId().equals(resolvedBy.getId())) {
note.setResolvedBy(responsiblePerson);
}
}
}
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
if (oldDiscussion.isNotification()) { if (oldDiscussion.isNotification()) {
final Map<Long, Note> oldNoteMap = oldDiscussion notifyUpdateNote(oldDiscussion, discussion);
.getNotes().stream()
.collect(Collectors.toMap(Note::getId, n -> n));
// Пользователь участвовал в обсуждении
final boolean userParticipatedInDiscussion = oldDiscussion.getNotes().stream()
.anyMatch(note -> personInformation.getId().equals(note.getAuthor().getId()));
final Note threadFirstNote = newDiscussion.getFirstNote();
if (TRUE.equals(newDiscussion.getResolved())) {
notifyAboutCloseThread(threadFirstNote, oldNoteMap.get(threadFirstNote.getId()), newDiscussion.getLastNote());
}
for (Note newNote : newDiscussion.getNotes()) {
final Long newNoteId = newNote.getId();
if (!oldNoteMap.containsKey(newNoteId)) {
if (userParticipatedInDiscussion) {
notifyAboutNewAnswer(newDiscussion, newNote);
} else {
notifyAboutPersonalAnswer(newDiscussion, newNote);
}
}
}
} }
}
/**
* <p>Уведомляет пользователя, если появился новый комментарий</p>
*/
private void notifyNewThread(Discussion discussion) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Note firstNote = discussion.getFirstNote();
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest(); return repository.save(discussion);
final DiscussionNewPersonalNotify.DiscussionNewPersonalNotifyBuilder messageBuilder = DiscussionNewPersonalNotify.builder()
.url(firstNote.getWebUrl())
.threadId(discussion.getId())
.mergeRequestName(mergeRequest.getTitle())
.authorName(firstNote.getAuthor().getName());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final List<Note> notes = discussion.getNotes();
messageBuilder
.discussionMessage(firstNote.getBody());
if (notes.size() > 1) {
for (int i = 1; i < notes.size(); i++) {
final Note note = notes.get(i);
messageBuilder.note(
new Pair<>(note.getAuthor().getName(), note.getBody())
);
}
}
}
notifyService.send(messageBuilder.build());
}
} }
private boolean isNeedNotifyNewNote(Discussion discussion) { private boolean isNeedNotifyNewNote(Discussion discussion) {
@ -135,80 +142,38 @@ public class DiscussionHandler {
&& FALSE.equals(firstNote.getResolved()); // Комментарий не отмечен как решенный && FALSE.equals(firstNote.getResolved()); // Комментарий не отмечен как решенный
} }
/** @Override
* Уведомляет пользователя, если его никнейм упоминается в комментарии. public List<Discussion> updateAll(@NonNull List<Discussion> discussions) {
*/ return discussions.stream()
private void notifyAboutPersonalAnswer(Discussion discussion, Note note) { .map(this::update)
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify(); .collect(Collectors.toList());
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Matcher matcher = PATTERN.matcher(note.getBody());
final Set<String> recipientsLogins = new HashSet<>();
while (matcher.find()) {
final String login = matcher.group(0).replace("@", "");
recipientsLogins.add(login);
}
if (recipientsLogins.contains(personInformation.getUsername())) {
final NewCommentPersonalNotify.NewCommentPersonalNotifyBuilder notifyBuilder = NewCommentPersonalNotify.builder()
.threadId(discussion.getId())
.mergeRequestName(discussion.getMergeRequest().getTitle())
.url(note.getWebUrl());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
final Note firstNote = discussion.getFirstNote();
if (!firstNote.equals(note)) {
notifyBuilder.message(note.getBody())
.authorName(note.getAuthor().getName());
}
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
} }
private void notifyAboutNewAnswer(Discussion discussion, Note note) { private void notifyUpdateNote(Discussion oldDiscussion, Discussion discussion) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify(); final Map<Long, Note> oldNoteMap = oldDiscussion
.getNotes().stream()
.collect(Collectors.toMap(Note::getId, n -> n));
if (!WITHOUT_NOTIFY.equals(discussionLevel) // Пользователь участвовал в обсуждении
&& !personInformation.getId().equals(note.getAuthor().getId())) { final boolean userParticipatedInDiscussion = oldDiscussion.getNotes().stream()
final Note firstNote = discussion.getFirstNote(); .anyMatch(note -> personInformation.getId().equals(note.getAuthor().getId()));
final NewCommentPersonalNotify.NewCommentPersonalNotifyBuilder notifyBuilder = NewCommentPersonalNotify.builder() final Note threadFirstNote = discussion.getFirstNote();
.threadId(discussion.getId()) if (TRUE.equals(discussion.getResolved())) {
.url(note.getWebUrl()) notifyAboutCloseThread(threadFirstNote, oldNoteMap.get(threadFirstNote.getId()), discussion.getLastNote());
.mergeRequestName(discussion.getMergeRequest().getTitle());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName())
.message(note.getBody())
.authorName(note.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
} }
for (Note newNote : discussion.getNotes()) {
final Long newNoteId = newNote.getId();
if (!oldNoteMap.containsKey(newNoteId)) {
if (userParticipatedInDiscussion) {
notifyAboutNewAnswer(discussion, newNote);
} else {
notifyAboutPersonalAnswer(discussion, newNote);
}
}
}
} }
private void notifyAboutCloseThread(Note newNote, Note oldNote, Optional<Note> lastNote) { private void notifyAboutCloseThread(Note newNote, Note oldNote, Optional<Note> lastNote) {
@ -218,7 +183,7 @@ public class DiscussionHandler {
if (isResolved(newNote, oldNote)) { if (isResolved(newNote, oldNote)) {
final MergeRequestForDiscussion mergeRequest = oldNote.getDiscussion().getMergeRequest(); final MergeRequestForDiscussion mergeRequest = oldNote.getDiscussion().getMergeRequest();
final List<Discussion> discussions = discussionService.getAllByMergeRequestId(mergeRequest.getId()) final List<Discussion> discussions = getAllByMergeRequestId(mergeRequest.getId())
.stream() .stream()
.filter(discussion -> Objects.nonNull(discussion.getResponsible())) .filter(discussion -> Objects.nonNull(discussion.getResponsible()))
.toList(); .toList();
@ -229,7 +194,7 @@ public class DiscussionHandler {
.filter(discussion -> personInformation.getId().equals(discussion.getFirstNote().getAuthor().getId()) && discussion.getResolved()) .filter(discussion -> personInformation.getId().equals(discussion.getFirstNote().getAuthor().getId()) && discussion.getResolved())
.count(); .count();
final ThreadClosePersonalNotify.ThreadClosePersonalNotifyBuilder notifyBuilder = ThreadClosePersonalNotify.builder() final ThreadCloseNotify.ThreadCloseNotifyBuilder notifyBuilder = ThreadCloseNotify.builder()
.mergeRequestName(mergeRequest.getTitle()) .mergeRequestName(mergeRequest.getTitle())
.url(oldNote.getWebUrl()) .url(oldNote.getWebUrl())
.personTasks(allYouTasks) .personTasks(allYouTasks)
@ -262,4 +227,195 @@ public class DiscussionHandler {
&& !note.getResolvedBy().getId().equals(oldNote.getAuthor().getId()); // и решающий не является автором треда && !note.getResolvedBy().getId().equals(oldNote.getAuthor().getId()); // и решающий не является автором треда
} }
@Override
public void answer(@NonNull String discussionId, @NonNull String text) {
final Discussion discussion = repository.findById(discussionId)
.orElseThrow(notFoundException("Дисскусия {0} не найдена", discussionId));
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final Long projectId = mergeRequest.getProjectId();
final String requestUrl = MessageFormat.format(gitlabProperty.getNewNoteUrl(), projectId, mergeRequest.getTwoId(), discussion.getId(), text);
final RequestBody formBody = new FormBody.Builder().build();
final Request request = new Request.Builder()
.post(formBody)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.url(requestUrl)
.build();
try {
client.newCall(request).execute();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
@Override
public List<Discussion> getAllByMergeRequestId(@NonNull Long mergeRequestId) {
return repository.findAllByMergeRequestId(mergeRequestId);
}
@Override
public ExistContainer<Discussion, String> existsById(@NonNull Set<String> discussionIds) {
final List<Discussion> existsEntity = repository.findAllById(discussionIds);
final Set<String> existsIds = existsEntity.stream().map(Discussion::getId).collect(Collectors.toSet());
if (existsIds.containsAll(discussionIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<String> noExistsId = discussionIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<Discussion> createAll(@NonNull List<Discussion> newDiscussions) {
return newDiscussions.stream()
.map(this::create)
.toList();
}
@Override
public List<Discussion> getAll() {
return repository.findAll();
}
@Override
public Set<String> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void deleteById(@NonNull String discussionId) {
repository.deleteById(discussionId);
}
@Override
@Transactional
public void cleanOld() {
log.debug("Старт очистки старых дискуссий");
repository.cleanOld();
log.debug("Конец очистки старых дискуссий");
}
@Override
@Transactional
public void notification(boolean enable, String discussionId) {
repository.notification(enable, discussionId);
}
private void notifyAboutNewAnswer(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)
&& !personInformation.getId().equals(note.getAuthor().getId())) {
final Note firstNote = discussion.getFirstNote();
final NewCommentNotify.NewCommentNotifyBuilder notifyBuilder = NewCommentNotify.builder()
.threadId(discussion.getId())
.url(note.getWebUrl())
.mergeRequestName(discussion.getMergeRequest().getTitle());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName())
.message(note.getBody())
.authorName(note.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
/**
* Уведомляет пользователя, если его никнейм упоминается в комментарии.
*/
private void notifyAboutPersonalAnswer(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Matcher matcher = PATTERN.matcher(note.getBody());
final Set<String> recipientsLogins = new HashSet<>();
while (matcher.find()) {
final String login = matcher.group(0).replace("@", "");
recipientsLogins.add(login);
}
if (recipientsLogins.contains(personInformation.getUsername())) {
final NewCommentNotify.NewCommentNotifyBuilder notifyBuilder = NewCommentNotify.builder()
.threadId(discussion.getId())
.mergeRequestName(discussion.getMergeRequest().getTitle())
.url(note.getWebUrl());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
final Note firstNote = discussion.getFirstNote();
if (!firstNote.equals(note)) {
notifyBuilder.message(note.getBody())
.authorName(note.getAuthor().getName());
}
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
}
/**
* <p>Уведомляет пользователя, если появился новый комментарий</p>
*/
private void notifyNewThread(Discussion discussion) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Note firstNote = discussion.getFirstNote();
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final DiscussionNewNotify.DiscussionNewNotifyBuilder messageBuilder = DiscussionNewNotify.builder()
.url(firstNote.getWebUrl())
.threadId(discussion.getId())
.mergeRequestName(mergeRequest.getTitle())
.authorName(firstNote.getAuthor().getName());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final List<Note> notes = discussion.getNotes();
messageBuilder
.discussionMessage(firstNote.getBody());
if (notes.size() > 1) {
for (int i = 1; i < notes.size(); i++) {
final Note note = notes.get(i);
messageBuilder.note(
new Pair<>(note.getAuthor().getName(), note.getBody())
);
}
}
}
notifyService.send(messageBuilder.build());
}
}
} }

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,396 @@
package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.IdAndStatusPr;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
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.mergerequest.ConflictMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForAssignee;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForReview;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrNotify;
import dev.struchkov.bot.gitlab.context.repository.MergeRequestRepository;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
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.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.CLOSED;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.MERGED;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static java.lang.Boolean.TRUE;
@Slf4j
@Service
@RequiredArgsConstructor
public class MergeRequestsServiceImpl implements MergeRequestsService {
public static final Set<MergeRequestState> DELETE_STATES = Set.of(MERGED, CLOSED);
private final MergeRequestRepository repository;
private final NotifyService notifyService;
private final ProjectService projectService;
private final DiscussionService discussionService;
private final PersonInformation personInformation;
@Override
@Transactional
public MergeRequest create(@NonNull MergeRequest mergeRequest) {
final boolean botUserReviewer = isBotUserReviewer(mergeRequest);
final boolean botUserAssignee = isBotUserAssigneeAndNotAuthor(mergeRequest);
final boolean botUserAuthor = personInformation.getId().equals(mergeRequest.getAuthor().getId());
mergeRequest.setNotification(botUserReviewer || botUserAssignee || botUserAuthor);
mergeRequest.setUserAssignee(botUserAssignee);
mergeRequest.setUserReviewer(botUserReviewer);
final MergeRequest savedMergeRequest = repository.save(mergeRequest);
if (botUserReviewer || botUserAssignee) {
if (!mergeRequest.isConflict()) {
final String projectName = projectService.getByIdOrThrow(savedMergeRequest.getProjectId()).getName();
if (botUserReviewer) sendNotifyNewMrReview(savedMergeRequest, projectName);
if (botUserAssignee) sendNotifyNewAssignee(mergeRequest, projectName, null);
}
}
return savedMergeRequest;
}
private boolean isBotUserAssigneeAndNotAuthor(MergeRequest mergeRequest) {
final Long gitlabUserId = personInformation.getId();
final Person assignee = mergeRequest.getAssignee();
final Person author = mergeRequest.getAuthor();
if (checkNotNull(assignee)) {
if (gitlabUserId.equals(assignee.getId()) && !isAuthorSameAssignee(author, assignee)) {
return true;
}
}
return false;
}
/**
* Создатель MR является ответственным за этот MR
*
* @return true, если автор и ответственный один и тот же человек.
*/
private boolean isAuthorSameAssignee(Person author, Person assignee) {
return author.getId().equals(assignee.getId());
}
private boolean isBotUserReviewer(MergeRequest savedMergeRequest) {
final List<Person> reviewers = savedMergeRequest.getReviewers();
final Long botUserGitlabId = personInformation.getId();
if (checkNotEmpty(reviewers)) {
for (Person reviewer : reviewers) {
if (botUserGitlabId.equals(reviewer.getId())) {
return true;
}
}
}
return false;
}
private void sendNotifyNewMrReview(MergeRequest mergeRequest, String projectName) {
notifyService.send(
NewMrForReview.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.description(mergeRequest.getDescription())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.assignee(mergeRequest.getAssignee().getName())
.build()
);
}
private void sendNotifyNewAssignee(MergeRequest mergeRequest, String projectName, String oldAssigneeName) {
final NewMrForAssignee.NewMrForAssigneeBuilder builder = NewMrForAssignee.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.description(mergeRequest.getDescription())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.reviewers(mergeRequest.getReviewers().stream().map(Person::getName).toList());
if (checkNotNull(oldAssigneeName)) {
builder.oldAssigneeName(oldAssigneeName);
if (checkNotNull(mergeRequest.getAssignee())) {
builder.newAssigneeName(mergeRequest.getAssignee().getName());
}
}
notifyService.send(builder.build());
}
@Override
@Transactional
public MergeRequest update(@NonNull MergeRequest mergeRequest) {
final MergeRequest oldMergeRequest = repository.findById(mergeRequest.getId())
.orElseThrow(notFoundException("MergeRequest не найден"));
mergeRequest.setNotification(oldMergeRequest.isNotification());
final Long gitlabUserId = personInformation.getId();
final AssigneeChanged assigneeChanged = AssigneeChanged.valueOf(gitlabUserId, oldMergeRequest.getAssignee(), mergeRequest.getAssignee());
final ReviewerChanged reviewerChanged = ReviewerChanged.valueOf(gitlabUserId, oldMergeRequest.getReviewers(), mergeRequest.getReviewers());
mergeRequest.setUserAssignee(assigneeChanged.getNewStatus(oldMergeRequest.isUserAssignee()));
mergeRequest.setUserReviewer(reviewerChanged.getNewStatus(oldMergeRequest.isUserReviewer()));
final boolean isChangedMr = !oldMergeRequest.getUpdatedDate().equals(mergeRequest.getUpdatedDate()) || oldMergeRequest.isConflict() != mergeRequest.isConflict();
final boolean isChangedLinkedEntity = reviewerChanged.isChanged() || assigneeChanged.isChanged();
if (isChangedMr || isChangedLinkedEntity) {
if (oldMergeRequest.isNotification()) {
final Project project = projectService.getByIdOrThrow(mergeRequest.getProjectId());
if (isChangedMr) {
notifyAboutStatus(oldMergeRequest, mergeRequest, project);
notifyAboutNewConflict(oldMergeRequest, mergeRequest, project);
notifyAboutResolveConflict(oldMergeRequest, mergeRequest, project);
notifyAboutUpdate(oldMergeRequest, mergeRequest, project);
}
if (isChangedLinkedEntity) {
notifyReviewer(reviewerChanged, mergeRequest, project);
notifyAssignee(assigneeChanged, oldMergeRequest, mergeRequest, project);
}
}
return repository.save(mergeRequest);
}
return oldMergeRequest;
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление
private void notifyAssignee(AssigneeChanged assigneeChanged, MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
switch (assigneeChanged) {
case BECOME ->
sendNotifyNewAssignee(mergeRequest, project.getName(), Optional.ofNullable(oldMergeRequest.getAssignee()).map(Person::getName).orElse(null));
}
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление ревьювера
private void notifyReviewer(ReviewerChanged reviewerChanged, MergeRequest mergeRequest, Project project) {
switch (reviewerChanged) {
case BECOME -> sendNotifyNewMrReview(mergeRequest, project.getName());
}
}
@Override
@Transactional
public List<MergeRequest> updateAll(@NonNull List<MergeRequest> mergeRequests) {
return mergeRequests.stream()
.map(this::update)
.collect(Collectors.toList());
}
@Override
public Set<IdAndStatusPr> getAllId(Set<MergeRequestState> statuses) {
return repository.findAllIdByStateIn(statuses);
}
@Override
public List<MergeRequestForDiscussion> getAllForDiscussion() {
return repository.findAllForDiscussion();
}
@Override
public ExistContainer<MergeRequest, Long> existsById(@NonNull Set<Long> mergeRequestIds) {
final List<MergeRequest> existsEntity = repository.findAllById(mergeRequestIds);
final Set<Long> existsIds = existsEntity.stream().map(MergeRequest::getId).collect(Collectors.toSet());
if (existsIds.containsAll(mergeRequestIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<Long> noExistsId = mergeRequestIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<MergeRequest> createAll(List<MergeRequest> newMergeRequests) {
return newMergeRequests.stream()
.map(this::create)
.toList();
}
@Override
public List<MergeRequest> getAllByReviewerId(@NonNull Long personId) {
return repository.findAllByReviewerId(personId);
}
@Override
public void cleanOld() {
log.debug("Старт очистки старых MR");
repository.deleteByStates(DELETE_STATES);
log.debug("Конец очистки старых MR");
}
@Override
@Transactional(readOnly = true)
public Set<Long> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void notification(boolean enable, @NonNull Long mrId) {
repository.notification(enable, mrId);
}
@Override
@Transactional
public void notificationByProjectId(boolean enable, @NonNull Set<Long> projectIds) {
repository.notificationByProjectId(enable, projectIds);
}
private void notifyAboutUpdate(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long botUserGitlabId = personInformation.getId();
if (
!botUserGitlabId.equals(mergeRequest.getAuthor().getId()) // Автор MR не пользователь приложения
&& !oldMergeRequest.getDateLastCommit().equals(mergeRequest.getDateLastCommit()) // Изменилась дата последнего коммита
&& !mergeRequest.isConflict() // MR не находится в состоянии конфликта
&& !botUserGitlabId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан НЕ пользователем бота
) {
long allTask = 0;
long resolvedTask = 0;
long allYouTasks = 0;
long resolvedYouTask = 0;
final List<Discussion> discussions = discussionService.getAllByMergeRequestId(oldMergeRequest.getId());
for (Discussion discussion : discussions) {
if (checkNotNull(discussion.getResponsible())) {
final boolean isBotUserAuthorDiscussion = botUserGitlabId.equals(discussion.getFirstNote().getAuthor().getId());
allTask += 1;
if (isBotUserAuthorDiscussion) {
allYouTasks += 1;
}
if (TRUE.equals(discussion.getResolved())) {
resolvedTask += 1;
if (isBotUserAuthorDiscussion) {
resolvedYouTask += 1;
}
}
}
}
final UpdateMrNotify.UpdateMrNotifyBuilder notifyBuilder = UpdateMrNotify.builder()
.mrId(oldMergeRequest.getId())
.author(oldMergeRequest.getAuthor().getName())
.name(oldMergeRequest.getTitle())
.projectName(project.getName())
.url(oldMergeRequest.getWebUrl())
.allTasks(allTask)
.allResolvedTasks(resolvedTask)
.personTasks(allYouTasks)
.personResolvedTasks(resolvedYouTask);
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
notifyBuilder.comment("The conflict has been resolved");
}
notifyService.send(notifyBuilder.build());
}
}
protected void notifyAboutNewConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (
!oldMergeRequest.isConflict() // У старого MR не было конфликта
&& mergeRequest.isConflict() // А у нового есть
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан пользователем бота
) {
notifyService.send(
ConflictMrNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.build()
);
}
}
private void notifyAboutResolveConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
// проверяем даты коммитов, так как при пуше в target ветку MR у которого есть конфликт, конфликт на время пропадает. Судя по всему GitLab после пуша заново проверяет вероятность конфликта. Чаще всего конфликт никуда не девается.
if (oldMergeRequest.getDateLastCommit().equals(mergeRequest.getDateLastCommit())) {
mergeRequest.setConflict(true);
} else {
if (gitlabUserId.equals(oldMergeRequest.getAuthor().getId())) {
notifyService.send(
ConflictResolveMrNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.build()
);
}
}
}
}
protected void notifyAboutStatus(MergeRequest oldMergeRequest, MergeRequest newMergeRequest, Project project) {
final MergeRequestState oldStatus = oldMergeRequest.getState();
final MergeRequestState newStatus = newMergeRequest.getState();
final Long gitlabUserId = personInformation.getId();
if (
!oldStatus.equals(newStatus) // статус изменился
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // создатель MR является пользователем бота
) {
notifyService.send(
StatusMrNotify.builder()
.mrId(oldMergeRequest.getId())
.name(newMergeRequest.getTitle())
.url(oldMergeRequest.getWebUrl())
.projectName(project.getName())
.newStatus(newStatus)
.oldStatus(oldStatus)
.build()
);
}
}
}

View File

@ -1,8 +1,6 @@
package dev.struchkov.bot.gitlab.core.service; package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify; import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import dev.struchkov.bot.gitlab.context.prop.GroupNotifyProperty;
import dev.struchkov.bot.gitlab.context.service.AppSettingService; import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.MessageSendService; import dev.struchkov.bot.gitlab.context.service.MessageSendService;
import dev.struchkov.bot.gitlab.context.service.NotifyService; import dev.struchkov.bot.gitlab.context.service.NotifyService;
@ -12,33 +10,22 @@ import org.springframework.stereotype.Service;
@Service @Service
public class NotifyServiceImpl implements NotifyService { public class NotifyServiceImpl implements NotifyService {
private final GroupNotifyProperty groupNotifyProperty;
private final MessageSendService messageSendService; private final MessageSendService messageSendService;
private final AppSettingService settingService; private final AppSettingService settingService;
public NotifyServiceImpl( public NotifyServiceImpl(
GroupNotifyProperty groupNotifyProperty, @Lazy MessageSendService messageSendService, @Lazy MessageSendService messageSendService,
AppSettingService settingService AppSettingService settingService
) { ) {
this.groupNotifyProperty = groupNotifyProperty;
this.messageSendService = messageSendService; this.messageSendService = messageSendService;
this.settingService = settingService; this.settingService = settingService;
} }
@Override @Override
public <T extends PersonalNotify> void send(T notify) { public <T extends Notify> void send(T notify) {
if (settingService.isEnableAllNotify()) { if (settingService.isEnableAllNotify()) {
messageSendService.send(notify); messageSendService.send(notify);
} }
} }
@Override
public <T extends GroupNotify> void send(T notify) {
if (settingService.isEnableAllNotify()) {
if (groupNotifyProperty.getChatId().isPresent()) {
messageSendService.send(notify);
}
}
}
} }

View File

@ -1,8 +1,7 @@
package dev.struchkov.bot.gitlab.core.service; package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer; import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.PersonTelegram;
import dev.struchkov.bot.gitlab.context.repository.PersonRepository; import dev.struchkov.bot.gitlab.context.repository.PersonRepository;
import dev.struchkov.bot.gitlab.context.service.PersonService; import dev.struchkov.bot.gitlab.context.service.PersonService;
import lombok.NonNull; import lombok.NonNull;
@ -11,8 +10,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
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;
@ -32,6 +29,11 @@ public class PersonServiceImpl implements PersonService {
return repository.save(person); return repository.save(person);
} }
@Override
public Person update(@NonNull Person person) {
return repository.save(person);
}
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Person getByIdOrThrown(@NonNull Long personId) { public Person getByIdOrThrown(@NonNull Long personId) {
@ -62,15 +64,4 @@ public class PersonServiceImpl implements PersonService {
.toList(); .toList();
} }
@Override
public Optional<String> getTelegramUsernamesByPersonIds(Long personId) {
return repository.findTelegramInfoById(personId).map(PersonTelegram::getTelegramUserName);
}
@Override
public Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds) {
return repository.findAllTelegramInfoByIds(personIds).stream()
.collect(Collectors.toMap(PersonTelegram::getId, PersonTelegram::getTelegramUserName));
}
} }

View File

@ -1,14 +1,17 @@
package dev.struchkov.bot.gitlab.core.service; package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer; import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus; import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
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.notify.pipeline.PipelineNotify;
import dev.struchkov.bot.gitlab.context.repository.PipelineRepository; import dev.struchkov.bot.gitlab.context.repository.PipelineRepository;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PipelineService; import dev.struchkov.bot.gitlab.context.service.PipelineService;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -17,9 +20,12 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.event.NewPipelineEvent.newPipeline; import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CANCELED;
import static dev.struchkov.bot.gitlab.context.domain.event.UpdatePipelineEvent.updatePipeline; import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.FAILED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SKIPPED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SUCCESS;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException; import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
/** /**
* Реализация сервиса для работы с пайплайнами. * Реализация сервиса для работы с пайплайнами.
@ -31,15 +37,19 @@ import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundEx
@RequiredArgsConstructor @RequiredArgsConstructor
public class PipelineServiceImpl implements PipelineService { public class PipelineServiceImpl implements PipelineService {
private final ApplicationEventPublisher eventPublisher; // Статусы пайплайнов, о которых нужно уведомить
private static final Set<PipelineStatus> notificationStatus = Set.of(FAILED, SUCCESS, CANCELED, SKIPPED);
private final NotifyService notifyService;
private final PipelineRepository repository; private final PipelineRepository repository;
private final PersonInformation personInformation;
@Override @Override
@Transactional @Transactional
public Pipeline create(@NonNull Pipeline pipeline) { public Pipeline create(@NonNull Pipeline pipeline) {
final Pipeline newPipeline = repository.save(pipeline); final Pipeline newPipeline = repository.save(pipeline);
eventPublisher.publishEvent(newPipeline(newPipeline)); notifyNewPipeline(pipeline, PipelineStatus.NULL);
return newPipeline; return newPipeline;
} }
@ -51,6 +61,21 @@ public class PipelineServiceImpl implements PipelineService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void notifyNewPipeline(Pipeline pipeline, PipelineStatus oldStatus) {
if (isNeedNotifyNewPipeline(pipeline)) {
notifyService.send(
PipelineNotify.builder()
.projectId(pipeline.getProjectId())
.newStatus(pipeline.getStatus())
.pipelineId(pipeline.getId())
.refName(pipeline.getRef())
.webUrl(pipeline.getWebUrl())
.oldStatus(oldStatus)
.build()
);
}
}
@Override @Override
public Pipeline update(@NonNull Pipeline pipeline) { public Pipeline update(@NonNull Pipeline pipeline) {
final Pipeline oldPipeline = repository.findById(pipeline.getId()) final Pipeline oldPipeline = repository.findById(pipeline.getId())
@ -58,8 +83,12 @@ public class PipelineServiceImpl implements PipelineService {
pipeline.setProjectId(oldPipeline.getProjectId()); pipeline.setProjectId(oldPipeline.getProjectId());
eventPublisher.publishEvent(updatePipeline(oldPipeline.toBuilder().build(), pipeline)); if (!oldPipeline.getUpdated().equals(pipeline.getUpdated())) {
return repository.save(pipeline); notifyNewPipeline(pipeline, oldPipeline.getStatus());
return repository.save(pipeline);
}
return oldPipeline;
} }
@Override @Override
@ -69,6 +98,13 @@ public class PipelineServiceImpl implements PipelineService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private boolean isNeedNotifyNewPipeline(@NonNull Pipeline pipeline) {
final Person personPipelineCreator = pipeline.getPerson();
return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления
&& checkNotNull(personPipelineCreator) // Создатель пайплайна не null
&& personInformation.getId().equals(personPipelineCreator.getId()); // Пользователь приложения является инициатором пайплайна
}
@Override @Override
public List<Pipeline> getAllByStatuses(@NonNull Set<PipelineStatus> statuses) { public List<Pipeline> getAllByStatuses(@NonNull Set<PipelineStatus> statuses) {
return repository.findAllByStatuses(statuses); return repository.findAllByStatuses(statuses);
@ -92,7 +128,7 @@ public class PipelineServiceImpl implements PipelineService {
@Override @Override
public void cleanOld() { public void cleanOld() {
log.debug("Старт очистки старых пайплайнов"); log.debug("Старт очистки старых пайплайнов");
repository.deleteByCreatedBefore(LocalDateTime.now().minusDays(7L)); repository.deleteByCreatedBefore(LocalDateTime.now().minusDays(1L));
log.debug("Конец очистки старых пайплайнов"); log.debug("Конец очистки старых пайплайнов");
} }

View File

@ -1,12 +1,14 @@
package dev.struchkov.bot.gitlab.core.service; package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer; import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Project; import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectNotify;
import dev.struchkov.bot.gitlab.context.repository.ProjectRepository; import dev.struchkov.bot.gitlab.context.repository.ProjectRepository;
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.bot.gitlab.context.service.ProjectService;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -15,7 +17,6 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.event.NewProjectEvent.newProject;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException; import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
/** /**
@ -27,7 +28,8 @@ public class ProjectServiceImpl implements ProjectService {
private final ProjectRepository repository; private final ProjectRepository repository;
private final ApplicationEventPublisher eventPublisher; private final NotifyService notifyService;
private final PersonService personService;
@Override @Override
@Transactional @Transactional
@ -35,7 +37,8 @@ public class ProjectServiceImpl implements ProjectService {
final Project newProject = repository.save(project); final Project newProject = repository.save(project);
if (sendNotify) { if (sendNotify) {
eventPublisher.publishEvent(newProject(project)); final String authorName = personService.getByIdOrThrown(newProject.getCreatorId()).getName();
notifyAboutNewProject(newProject, authorName);
} }
return newProject; return newProject;
@ -108,4 +111,18 @@ public class ProjectServiceImpl implements ProjectService {
repository.processing(enable, projectIds); repository.processing(enable, projectIds);
} }
private void notifyAboutNewProject(Project newProject, String authorName) {
notifyService.send(
NewProjectNotify.builder()
.projectId(newProject.getId())
.projectDescription(newProject.getDescription())
.projectName(newProject.getName())
.projectUrl(newProject.getWebUrl())
.sshUrlToRepo(newProject.getSshUrlToRepo())
.httpUrlToRepo(newProject.getHttpUrlToRepo())
.authorName(authorName)
.build()
);
}
} }

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.core.service; package dev.struchkov.bot.gitlab.core.service.impl.note;
import dev.struchkov.bot.gitlab.context.domain.entity.Note; import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.repository.NoteRepository; import dev.struchkov.bot.gitlab.context.repository.NoteRepository;

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.core.parser; package dev.struchkov.bot.gitlab.core.service.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer; import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion; import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
@ -7,11 +7,12 @@ import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.service.DiscussionService; import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService; import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager; import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.sdk.gitlab.domain.GitlabUrl; import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.sdk.gitlab.schema.note.DiscussionJson; import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import lombok.RequiredArgsConstructor; import dev.struchkov.bot.gitlab.sdk.domain.DiscussionJson;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,11 +21,15 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.bot.gitlab.core.utils.StringUtils.H_PRIVATE_TOKEN;
import static dev.struchkov.haiti.utils.Checker.checkFalse; import static dev.struchkov.haiti.utils.Checker.checkFalse;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty; import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Checker.checkNotNull; import static dev.struchkov.haiti.utils.Checker.checkNotNull;
@ -36,20 +41,35 @@ import static dev.struchkov.haiti.utils.Checker.checkNotNull;
*/ */
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor
public class DiscussionParser { public class DiscussionParser {
public static final int PAGE_COUNT = 100; public static final int PAGE_COUNT = 100;
//TODO [23.08.2024|uPagge]: Убрать
private final GitlabUrl gitlabUrl;
private final GitlabSdkManager gitlabSdkManager;
private final DiscussionService discussionService; private final DiscussionService discussionService;
private final MergeRequestsService mergeRequestsService; private final MergeRequestsService mergeRequestsService;
private final ConversionService conversionService; private final ConversionService conversionService;
private final ForkJoinPool forkJoinPool;
private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty;
public DiscussionParser(
DiscussionService discussionService,
MergeRequestsService mergeRequestsService,
ConversionService conversionService,
@Qualifier("parserPool") ForkJoinPool forkJoinPool,
GitlabProperty gitlabProperty,
PersonProperty personProperty
) {
this.discussionService = discussionService;
this.mergeRequestsService = mergeRequestsService;
this.conversionService = conversionService;
this.forkJoinPool = forkJoinPool;
this.gitlabProperty = gitlabProperty;
this.personProperty = personProperty;
}
/** /**
* Поиск новых обсуждений * Поиск новых обсуждений
@ -65,11 +85,11 @@ public class DiscussionParser {
private void processingNewDiscussion(MergeRequestForDiscussion mergeRequest) { private void processingNewDiscussion(MergeRequestForDiscussion mergeRequest) {
int page = 1; int page = 1;
final List<DiscussionJson> discussionJson = gitlabSdkManager.getDiscussionForMergeRequest(mergeRequest.getProjectId(), mergeRequest.getTwoId(), page, PAGE_COUNT); final List<DiscussionJson> discussionJson = getDiscussionJson(mergeRequest, page);
if (checkNotEmpty(discussionJson)) { if (checkNotEmpty(discussionJson)) {
while (discussionJson.size() == PAGE_COUNT) { while (discussionJson.size() == PAGE_COUNT) {
discussionJson.addAll(gitlabSdkManager.getDiscussionForMergeRequest(mergeRequest.getProjectId(), mergeRequest.getTwoId(), ++page, PAGE_COUNT)); discussionJson.addAll(getDiscussionJson(mergeRequest, ++page));
} }
createNewDiscussion(discussionJson, mergeRequest); createNewDiscussion(discussionJson, mergeRequest);
} }
@ -150,12 +170,11 @@ public class DiscussionParser {
final List<Discussion> newDiscussions = new ArrayList<>(); final List<Discussion> newDiscussions = new ArrayList<>();
for (Discussion discussion : discussions) { for (Discussion discussion : discussions) {
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest(); if (checkNotNull(discussion.getMergeRequest())) {
if (checkNotNull(mergeRequest)) { getOldDiscussionJson(discussion)
gitlabSdkManager.getDiscussionById(mergeRequest.getProjectId(), mergeRequest.getTwoId(), discussion.getId())
.map(json -> { .map(json -> {
final Discussion newDiscussion = conversionService.convert(json, Discussion.class); final Discussion newDiscussion = conversionService.convert(json, Discussion.class);
newDiscussion.getNotes().forEach(createNoteLink(mergeRequest)); newDiscussion.getNotes().forEach(createNoteLink(discussion.getMergeRequest()));
return newDiscussion; return newDiscussion;
}).ifPresent(newDiscussions::add); }).ifPresent(newDiscussions::add);
} else { } else {
@ -171,10 +190,33 @@ public class DiscussionParser {
log.debug("Конец обработки старых дискуссий"); log.debug("Конец обработки старых дискуссий");
} }
private Optional<DiscussionJson> getOldDiscussionJson(Discussion discussion) {
return HttpParse.request(createLinkOldDiscussion(discussion))
.header(ACCEPT)
.header(H_PRIVATE_TOKEN, personProperty.getToken())
.execute(DiscussionJson.class);
}
private String createLinkOldDiscussion(Discussion discussion) {
return MessageFormat.format(
gitlabProperty.getDiscussionUrl(),
discussion.getMergeRequest().getProjectId(),
discussion.getMergeRequest().getTwoId(),
discussion.getId()
);
}
private List<DiscussionJson> getDiscussionJson(MergeRequestForDiscussion mergeRequest, int page) {
return HttpParse.request(MessageFormat.format(gitlabProperty.getDiscussionsUrl(), mergeRequest.getProjectId(), mergeRequest.getTwoId(), page, PAGE_COUNT))
.header(ACCEPT)
.header(H_PRIVATE_TOKEN, personProperty.getToken())
.executeList(DiscussionJson.class);
}
private Consumer<Note> createNoteLink(MergeRequestForDiscussion mergeRequest) { private Consumer<Note> createNoteLink(MergeRequestForDiscussion mergeRequest) {
return note -> { return note -> {
final String url = MessageFormat.format( final String url = MessageFormat.format(
gitlabUrl.getNote(), gitlabProperty.getNoteUrl(),
mergeRequest.getWebUrl(), mergeRequest.getWebUrl(),
note.getId() note.getId()
); );

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("Старт обработаки старых Issues");
final Set<IdAndStatusIssue> existIds = issueService.getAllId(OLD_STATUSES);
final List<Issue> newIssues = getOldIssues(existIds).stream()
.map(issueJson -> {
final Issue newIssue = conversionService.convert(issueJson, Issue.class);
return newIssue;
})
.collect(Collectors.toList());
if (checkNotEmpty(newIssues)) {
personMapping(newIssues);
issueService.updateAll(newIssues);
}
log.debug("Конец обработки старых Issues");
}
private List<IssueJson> getOldIssues(Set<IdAndStatusIssue> existIds) {
final List<ForkJoinTask<Optional<IssueJson>>> tasks = existIds.stream()
.map(
existId -> new GetSingleIssueTask(
gitlabProperty.getIssueUrl(),
existId.getProjectId(),
existId.getTwoId(),
personProperty.getToken()
)
).map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResult(tasks).stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
public void parsingNewIssue() {
log.debug("Старт обработки новых Issues");
/*
* получаем через репозиторий список id всех проектов хранящихся в нашей БД
*/
final Set<Long> projectIds = projectService.getAllIds();
/*
* На основе id проекта, url для получения issues по id проекта и токена пользователя
* выгружаем из GitLab список всех IssueJson. Получаем в многопоточном режиме.
*/
final List<IssueJson> issueJsons = getIssues(projectIds);
/*
* Получаем id всех IssueJson загруженных из GitLab
*/
if (checkNotEmpty(issueJsons)) {
final Set<Long> jsonIds = issueJsons.stream()
.map(IssueJson::getId)
.collect(Collectors.toSet());
final ExistContainer<Issue, Long> existContainer = issueService.existsById(jsonIds);
log.trace("Из {} полученных Issues не найдены в хранилище {}", jsonIds.size(), existContainer.getIdNoFound().size());
if (!existContainer.isAllFound()) {
final List<Issue> newIssues = issueJsons.stream()
.filter(json -> existContainer.getIdNoFound().contains(json.getId()))
.map(json -> {
final Issue issue = conversionService.convert(json, Issue.class);
return issue;
})
.toList();
log.trace("Пачка новых Issues обработана и отправлена на сохранение. Количество: {} шт.", newIssues.size());
issueService.createAll(newIssues);
}
}
log.debug("Конец обработки новых Issues");
}
private List<IssueJson> getIssues(Set<Long> projectIds) {
final List<ForkJoinTask<List<IssueJson>>> tasks = projectIds.stream()
.map(projectId -> new GetAllIssueForProjectTask(projectId, gitlabProperty.getOpenIssueUrl(), personProperty.getToken()))
.map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResults(tasks);
}
private static void personMapping(List<Issue> newIssues) {
final Map<Long, Person> personMap = Stream.concat(
newIssues.stream()
.map(Issue::getAuthor),
newIssues.stream()
.flatMap(issue -> issue.getAssignees().stream())
).distinct()
.filter(Objects::nonNull)
.collect(Collectors.toMap(Person::getId, p -> p));
for (Issue newIssue : newIssues) {
newIssue.setAuthor(personMap.get(newIssue.getAuthor().getId()));
newIssue.setAssignees(
newIssue.getAssignees().stream()
.map(reviewer -> personMap.get(reviewer.getId()))
.collect(Collectors.toList())
);
}
}
}

View File

@ -1,61 +1,85 @@
package dev.struchkov.bot.gitlab.core.parser; package dev.struchkov.bot.gitlab.core.service.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer; import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.IdAndStatusPr;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState; import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest; import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService; import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.ProjectService; import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.haiti.utils.container.Pair; import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager; import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.sdk.gitlab.schema.approval.ApprovalJson; import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetAllMergeRequestForProjectTask;
import dev.struchkov.sdk.gitlab.schema.mergerequest.MergeRequestJson; import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetSingleMergeRequestTask;
import dev.struchkov.sdk.gitlab.schema.repository.CommitJson; import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import lombok.RequiredArgsConstructor; import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.CommitJson;
import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestJson;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.text.MessageFormat;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty; import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Checker.checkNotNull; import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static java.util.stream.Collectors.toList; import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResult;
import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResults;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor
public class MergeRequestParser { public class MergeRequestParser {
private static final Set<MergeRequestState> OLD_STATUSES = Set.of( private static final Set<MergeRequestState> OLD_STATUSES = Set.of(
MergeRequestState.MERGED, MergeRequestState.OPENED, MergeRequestState.CLOSED MergeRequestState.MERGED, MergeRequestState.OPENED, MergeRequestState.CLOSED
); );
private final GitlabSdkManager gitlabSdkManager; private final GitlabProperty gitlabProperty;
private final MergeRequestsService mergeRequestsService; private final MergeRequestsService mergeRequestsService;
private final ProjectService projectService; private final ProjectService projectService;
private final ConversionService conversionService; private final ConversionService conversionService;
private final PersonProperty personProperty;
private final ForkJoinPool forkJoinPool;
public MergeRequestParser(
GitlabProperty gitlabProperty,
MergeRequestsService mergeRequestsService,
ProjectService projectService,
ConversionService conversionService,
PersonProperty personProperty,
@Qualifier("parserPool") ForkJoinPool forkJoinPool
) {
this.gitlabProperty = gitlabProperty;
this.mergeRequestsService = mergeRequestsService;
this.projectService = projectService;
this.conversionService = conversionService;
this.personProperty = personProperty;
this.forkJoinPool = forkJoinPool;
}
public void parsingOldMergeRequest() { public void parsingOldMergeRequest() {
log.debug("Старт обработки старых MR"); log.debug("Старт обработки старых MR");
final Set<Pair<Long, Long>> existIds = mergeRequestsService.getAllId(OLD_STATUSES).stream() final Set<IdAndStatusPr> existIds = mergeRequestsService.getAllId(OLD_STATUSES);
.map(idAndStatusPr -> new Pair<>(idAndStatusPr.getProjectId(), idAndStatusPr.getTwoId()))
.collect(Collectors.toSet());
final List<MergeRequest> newMergeRequests = gitlabSdkManager.getAllMergeRequestById(existIds).stream() final List<MergeRequest> newMergeRequests = getOldMergeRequests(existIds).stream()
.map(mergeRequestJson -> { .map(mergeRequestJson -> {
final MergeRequest newMergeRequest = conversionService.convert(mergeRequestJson, MergeRequest.class); final MergeRequest newMergeRequest = conversionService.convert(mergeRequestJson, MergeRequest.class);
parsingCommits(newMergeRequest); parsingCommits(newMergeRequest);
parsingApprovals(newMergeRequest);
return newMergeRequest; return newMergeRequest;
}) })
.collect(toList()); .collect(Collectors.toList());
if (checkNotEmpty(newMergeRequests)) { if (checkNotEmpty(newMergeRequests)) {
personMapping(newMergeRequests); personMapping(newMergeRequests);
@ -64,11 +88,28 @@ public class MergeRequestParser {
log.debug("Конец обработки старых MR"); log.debug("Конец обработки старых MR");
} }
private List<MergeRequestJson> getOldMergeRequests(Set<IdAndStatusPr> existIds) {
final List<ForkJoinTask<Optional<MergeRequestJson>>> tasks = existIds.stream()
.map(
existId -> new GetSingleMergeRequestTask(
gitlabProperty.getMergeRequestUrl(),
existId.getProjectId(),
existId.getTwoId(),
personProperty.getToken()
)
).map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResult(tasks).stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
public void parsingNewMergeRequest() { public void parsingNewMergeRequest() {
log.debug("Старт обработки новых MR"); log.debug("Старт обработки новых MR");
final Set<Long> projectIds = projectService.getAllIdByProcessingEnable(); final Set<Long> projectIds = projectService.getAllIdByProcessingEnable();
final List<MergeRequestJson> mergeRequestJsons = gitlabSdkManager.getAllMergeRequestByProjectIds(projectIds); final List<MergeRequestJson> mergeRequestJsons = getMergeRequests(projectIds);
if (checkNotEmpty(mergeRequestJsons)) { if (checkNotEmpty(mergeRequestJsons)) {
final Set<Long> jsonIds = mergeRequestJsons.stream() final Set<Long> jsonIds = mergeRequestJsons.stream()
@ -83,7 +124,6 @@ public class MergeRequestParser {
.map(json -> { .map(json -> {
final MergeRequest mergeRequest = conversionService.convert(json, MergeRequest.class); final MergeRequest mergeRequest = conversionService.convert(json, MergeRequest.class);
parsingCommits(mergeRequest); parsingCommits(mergeRequest);
parsingApprovals(mergeRequest);
return mergeRequest; return mergeRequest;
}) })
.toList(); .toList();
@ -98,13 +138,27 @@ public class MergeRequestParser {
log.debug("Конец обработки новых MR"); log.debug("Конец обработки новых MR");
} }
/**
* Позволяет получить MR для переданных идентификаторов проектов.
*
* @param projectIds идентификаторы проектов
* @return полученные у GitLab MergeRequests
*/
private List<MergeRequestJson> getMergeRequests(Set<Long> projectIds) {
final List<ForkJoinTask<List<MergeRequestJson>>> tasks = projectIds.stream()
.map(projectId -> new GetAllMergeRequestForProjectTask(projectId, gitlabProperty.getOpenMergeRequestsUrl(), personProperty.getToken()))
.map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResults(tasks);
}
private static void personMapping(List<MergeRequest> newMergeRequests) { private static void personMapping(List<MergeRequest> newMergeRequests) {
final Map<Long, Person> personMap = Stream.concat( final Map<Long, Person> personMap = Stream.concat(
newMergeRequests.stream() newMergeRequests.stream()
.flatMap(mergeRequest -> Stream.of(mergeRequest.getAssignee(), mergeRequest.getAuthor())), .flatMap(mergeRequest -> Stream.of(mergeRequest.getAssignee(), mergeRequest.getAuthor())),
newMergeRequests.stream() newMergeRequests.stream()
.flatMap(mergeRequest -> Stream.concat(mergeRequest.getReviewers().stream(), mergeRequest.getApprovals().stream())) .flatMap(mergeRequest -> mergeRequest.getReviewers().stream())
).distinct() ).distinct()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toMap(Person::getId, p -> p)); .collect(Collectors.toMap(Person::getId, p -> p));
@ -120,35 +174,21 @@ public class MergeRequestParser {
newMergeRequest.setReviewers( newMergeRequest.setReviewers(
newMergeRequest.getReviewers().stream() newMergeRequest.getReviewers().stream()
.map(reviewer -> personMap.get(reviewer.getId())) .map(reviewer -> personMap.get(reviewer.getId()))
.collect(toList()) .collect(Collectors.toList())
);
newMergeRequest.setApprovals(
newMergeRequest.getApprovals().stream()
.map(approval -> personMap.get(approval.getId()))
.collect(toList())
); );
} }
} }
//TODO [19.01.2024|uPagge]: Переделать в многопоточный режим
private void parsingCommits(MergeRequest mergeRequest) { private void parsingCommits(MergeRequest mergeRequest) {
final List<CommitJson> commitJson = gitlabSdkManager.getAllCommitByProjectId(mergeRequest.getProjectId(), mergeRequest.getTwoId()); final List<CommitJson> commitJson = HttpParse.request(
if (checkNotEmpty(commitJson)) { MessageFormat.format(gitlabProperty.getLastCommitOfMergeRequestUrl(), mergeRequest.getProjectId(), mergeRequest.getTwoId())
)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.executeList(CommitJson.class);
if (commitJson != null && !commitJson.isEmpty()) {
mergeRequest.setDateLastCommit(commitJson.get(0).getCreatedDate()); mergeRequest.setDateLastCommit(commitJson.get(0).getCreatedDate());
} }
} }
//TODO [19.01.2024|uPagge]: Переделать в многопоточный режим
private void parsingApprovals(MergeRequest mergeRequest) {
final List<Person> personApprovals = gitlabSdkManager.getAllApprovalForMergeRequest(mergeRequest.getProjectId(), mergeRequest.getTwoId()).stream()
.map(ApprovalJson::getUser)
.map(personJson -> conversionService.convert(personJson, Person.class))
.collect(toList());
if (checkNotEmpty(personApprovals)) {
mergeRequest.setApprovals(personApprovals);
}
}
} }

View File

@ -0,0 +1,166 @@
package dev.struchkov.bot.gitlab.core.service.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.context.service.PipelineService;
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.GetPipelineShortTask;
import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetPipelineTask;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineJson;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineShortJson;
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.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CREATED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.MANUAL;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PENDING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PREPARING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.RUNNING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.WAITING_FOR_RESOURCE;
import static dev.struchkov.haiti.utils.Checker.checkFalse;
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 upagge 17.01.2021
*/
@Slf4j
@Service
public class PipelineParser {
private static final Set<PipelineStatus> oldStatus = Set.of(
CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, MANUAL
);
private final PipelineService pipelineService;
private final ProjectService projectService;
private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty;
private final ConversionService conversionService;
private final ForkJoinPool forkJoinPool;
private LocalDateTime lastUpdate = LocalDateTime.now();
public PipelineParser(
PipelineService pipelineService,
ProjectService projectService,
GitlabProperty gitlabProperty,
PersonProperty personProperty,
ConversionService conversionService,
@Qualifier("parserPool") ForkJoinPool forkJoinPool
) {
this.pipelineService = pipelineService;
this.projectService = projectService;
this.gitlabProperty = gitlabProperty;
this.personProperty = personProperty;
this.conversionService = conversionService;
this.forkJoinPool = forkJoinPool;
}
public void scanNewPipeline() {
log.debug("Старт обработки новых пайплайнов");
final Set<Long> projectIds = projectService.getAllIdByProcessingEnable();
final Map<Long, Long> pipelineProjectMap = getPipelineShortJsons(projectIds).stream()
.collect(Collectors.toMap(PipelineShortJson::getId, PipelineShortJson::getProjectId));
if (checkNotEmpty(pipelineProjectMap)) {
final ExistContainer<Pipeline, Long> existContainer = pipelineService.existsById(pipelineProjectMap.keySet());
if (checkFalse(existContainer.isAllFound())) {
final Set<Long> idsNotFound = existContainer.getIdNoFound();
final List<Pipeline> newPipelines = getNewPipelines(pipelineProjectMap, idsNotFound);
if (checkNotEmpty(newPipelines)) {
pipelineService.createAll(newPipelines);
}
}
}
log.debug("Конец обработки новых пайплайнов");
}
private List<Pipeline> getNewPipelines(Map<Long, Long> pipelineProjectMap, Set<Long> idsNotFound) {
final List<ForkJoinTask<Optional<PipelineJson>>> tasks = idsNotFound.stream()
.map(pipelineId -> new GetPipelineTask(
gitlabProperty.getPipelineUrl(),
pipelineProjectMap.get(pipelineId),
pipelineId,
personProperty.getToken()
))
.map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResult(tasks).stream()
.flatMap(Optional::stream)
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
}
private List<PipelineShortJson> getPipelineShortJsons(Set<Long> projectIds) {
LocalDateTime newLastUpdate = LocalDateTime.now();
final List<ForkJoinTask<List<PipelineShortJson>>> tasks = projectIds.stream()
.map(projectId -> new GetPipelineShortTask(
gitlabProperty.getPipelinesUrl(),
projectId,
lastUpdate,
personProperty.getToken()
))
.map(forkJoinPool::submit)
.collect(Collectors.toList());
final List<PipelineShortJson> pipelineJsons = pullTaskResults(tasks);
lastUpdate = newLastUpdate;
return pipelineJsons;
}
public void scanOldPipeline() {
log.debug("Старт обработки старых пайплайнов");
final List<Pipeline> pipelines = pipelineService.getAllByStatuses(oldStatus);
final List<ForkJoinTask<Optional<PipelineJson>>> tasks = pipelines.stream()
.map(
pipeline -> new GetPipelineTask(
gitlabProperty.getPipelineUrl(),
pipeline.getProjectId(),
pipeline.getId(),
personProperty.getToken()
)
)
.map(forkJoinPool::submit)
.collect(Collectors.toList());
final List<Pipeline> newPipelines = pullTaskResult(tasks).stream()
.flatMap(Optional::stream)
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
if (checkNotEmpty(newPipelines)) {
pipelineService.updateAll(newPipelines);
}
log.debug("Конец обработки старых пайплайнов");
}
}

View File

@ -1,23 +1,29 @@
package dev.struchkov.bot.gitlab.core.parser; package dev.struchkov.bot.gitlab.core.service.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer; import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Person; 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.entity.Project;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.PersonService; import dev.struchkov.bot.gitlab.context.service.PersonService;
import dev.struchkov.bot.gitlab.context.service.ProjectService; import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager; import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.sdk.gitlab.domain.GitlabProjectParam; import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.sdk.gitlab.schema.repository.ProjectJson; import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.PersonJson;
import dev.struchkov.bot.gitlab.sdk.domain.ProjectJson;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.text.MessageFormat;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.haiti.context.exception.ConvertException.convertException; import static dev.struchkov.haiti.context.exception.ConvertException.convertException;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty; import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@ -31,28 +37,33 @@ import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ProjectParser { public class ProjectParser {
private final GitlabSdkManager gitlabSdkManager; public static final String OWNER = "&owned=true";
private final ConversionService conversionService; public static final String PRIVATE = "&visibility=private";
private final MergeRequestsService mergeRequestsService;
private final ProjectService projectService; private final ProjectService projectService;
private final PersonService personService; private final PersonService personService;
private final ConversionService conversionService;
private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty;
public void parseAllProjectOwner() { public void parseAllProjectOwner() {
log.debug("Старт обработки всех проектов, где пользователь владелец"); log.debug("Старт обработки всех проектов, где пользователь владелец");
parseProjects(GitlabProjectParam.OWNER); parseProjects(OWNER);
log.debug("Конец обработки всех проектов, где пользователь владелец"); log.debug("Конец обработки всех проектов, где пользователь владелец");
} }
public void parseAllPrivateProject() { public void parseAllPrivateProject() {
log.debug("Старт обработки приватных проектов"); log.debug("Старт обработки приватных проектов");
parseProjects(GitlabProjectParam.PRIVATE); parseProjects(PRIVATE);
log.debug("Конец обработки приватных проектов"); log.debug("Конец обработки приватных проектов");
} }
private void parseProjects(GitlabProjectParam param) { private void parseProjects(String param) {
int page = 1; int page = 1;
List<ProjectJson> projectJsons = gitlabSdkManager.getAllProject(page, param); List<ProjectJson> projectJsons = getProjectJsons(page, param);
while (checkNotEmpty(projectJsons)) { while (checkNotEmpty(projectJsons)) {
@ -72,12 +83,15 @@ public class ProjectParser {
projectService.createAll(newProjects); projectService.createAll(newProjects);
} }
projectJsons = gitlabSdkManager.getAllProject(++page, param); projectJsons = getProjectJsons(++page, param);
} }
} }
public Project parseByUrl(String projectUrl) { public Project parseByUrl(String projectUrl) {
final ProjectJson projectJson = gitlabSdkManager.getProjectByUrl(projectUrl) final ProjectJson projectJson = HttpParse.request(projectUrl)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.execute(ProjectJson.class)
.orElseThrow(convertException("Error adding a repository")); .orElseThrow(convertException("Error adding a repository"));
if (!projectService.existsById(projectJson.getId())) { if (!projectService.existsById(projectJson.getId())) {
createNewPersons(List.of(projectJson)); createNewPersons(List.of(projectJson));
@ -100,7 +114,10 @@ public class ProjectParser {
final List<Person> newPersons = notFoundId.stream() final List<Person> newPersons = notFoundId.stream()
.map( .map(
userId -> gitlabSdkManager.getPersonById(userId) userId -> HttpParse.request(gitlabProperty.getUsersUrl() + "/" + userId)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.execute(PersonJson.class)
.map(json -> conversionService.convert(json, Person.class)) .map(json -> conversionService.convert(json, Person.class))
.orElseThrow(convertException("Ошибка преобразования нового пользователя")) .orElseThrow(convertException("Ошибка преобразования нового пользователя"))
).toList(); ).toList();
@ -109,5 +126,13 @@ public class ProjectParser {
} }
} }
private List<ProjectJson> getProjectJsons(int page, String... params) {
String param = String.join("", params);
final String url = MessageFormat.format(gitlabProperty.getProjectsUrl(), page);
return HttpParse.request(url + param)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.executeList(ProjectJson.class);
}
} }

View File

@ -0,0 +1,49 @@
package dev.struchkov.bot.gitlab.core.service.parser.forktask;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.sdk.domain.DiscussionJson;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.bot.gitlab.core.utils.StringUtils.H_PRIVATE_TOKEN;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@AllArgsConstructor
@RequiredArgsConstructor
public class GetAllDiscussionForMergeRequestTask extends RecursiveTask<List<DiscussionJson>> {
private static final int PAGE_COUNT = 100;
private final String discussionsUrl;
private final long projectId;
private final long mergeRequestTwoId;
private final String personalGitlabToken;
private int page = 1;
@Override
@SneakyThrows
protected List<DiscussionJson> compute() {
Thread.sleep(100);
final List<DiscussionJson> jsons = getDiscussionJson();
if (checkNotEmpty(jsons) && jsons.size() == PAGE_COUNT) {
final var newTask = new GetAllDiscussionForMergeRequestTask(discussionsUrl, projectId, mergeRequestTwoId, personalGitlabToken, page + 1);
newTask.fork();
jsons.addAll(newTask.join());
}
return jsons;
}
private List<DiscussionJson> getDiscussionJson() {
return HttpParse.request(MessageFormat.format(discussionsUrl, projectId, mergeRequestTwoId, page, PAGE_COUNT))
.header(ACCEPT)
.header(H_PRIVATE_TOKEN, personalGitlabToken)
.executeList(DiscussionJson.class);
}
}

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.bot.gitlab.core.utils.HttpParse;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
/**
* @author Dmitry Sheyko [24.01.2023]
*/
@Slf4j
@AllArgsConstructor
@RequiredArgsConstructor
public class GetAllIssueForProjectTask extends RecursiveTask<List<IssueJson>> {
private static final int PAGE_COUNT = 100;
private final long projectId;
private int pageNumber = 1;
private final String urlIssueOpen;
private final String gitlabToken;
@Override
@SneakyThrows
protected List<IssueJson> compute() {
Thread.sleep(200);
final List<IssueJson> issueJson = getIssueJsons();
if (checkNotEmpty(issueJson) && issueJson.size() == PAGE_COUNT) {
final GetAllIssueForProjectTask newTask = new GetAllIssueForProjectTask(projectId, pageNumber + 1, urlIssueOpen, gitlabToken);
newTask.fork();
issueJson.addAll(newTask.join());
}
return issueJson;
}
private List<IssueJson> getIssueJsons() {
final List<IssueJson> jsons = HttpParse.request(MessageFormat.format(urlIssueOpen, projectId, pageNumber, PAGE_COUNT))
.header(StringUtils.H_PRIVATE_TOKEN, gitlabToken)
.header(ACCEPT)
.executeList(IssueJson.class);
log.trace("Получено {} шт потенциально новых Issue для проекта id:'{}' ", jsons.size(), projectId);
return jsons;
}
}

Some files were not shown because too many files have changed in this diff Show More