Новые уведомления на ответы комментарии

This commit is contained in:
upagge 2020-03-29 12:48:14 +03:00
parent d50af456a1
commit da8c0bb706
No known key found for this signature in database
GPG Key ID: 15CD012E46F6BA34
13 changed files with 262 additions and 76 deletions

View File

@ -13,7 +13,7 @@ public class AppConfig {
@Bean @Bean
public TaskScheduler taskScheduler() { public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(6); taskScheduler.setPoolSize(7);
return taskScheduler; return taskScheduler;
} }

View File

@ -4,11 +4,6 @@ import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
/**
* TODO: Добавить описание класса.
*
* @author upagge [03.02.2020]
*/
@Data @Data
@AllArgsConstructor(access = AccessLevel.PRIVATE) @AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Pagination { public class Pagination {

View File

@ -0,0 +1,50 @@
package com.tsc.bitbucketbot.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import java.time.LocalDate;
import java.util.Set;
@Getter
@Setter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(of = "id")
@ToString
public class Comment {
@Id
@Column(name = "id")
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "url")
private String url;
@Column(name = "telegram")
private Long telegram;
@Column(name = "date")
private LocalDate date;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "comment_tree", joinColumns = @JoinColumn(name = "parent_id"))
@Column(name = "child_id")
private Set<Long> answers;
}

View File

@ -5,6 +5,7 @@ import com.tsc.bitbucketbot.utils.LocalDateFromEpochDeserializer;
import lombok.Data; import lombok.Data;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
@Data @Data
public class CommentJson { public class CommentJson {
@ -12,6 +13,7 @@ public class CommentJson {
private Long id; private Long id;
private String text; private String text;
private UserJson author; private UserJson author;
private List<CommentJson> comments;
@JsonDeserialize(using = LocalDateFromEpochDeserializer.class) @JsonDeserialize(using = LocalDateFromEpochDeserializer.class)
private LocalDate createdDate; private LocalDate createdDate;

View File

@ -0,0 +1,16 @@
package com.tsc.bitbucketbot.repository.jpa;
import com.tsc.bitbucketbot.domain.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
public interface CommentRepository extends JpaRepository<Comment, Long> {
Optional<Comment> findFirstByOrderByIdDesc();
List<Comment> findByDateBetween(LocalDate dateFrom, LocalDate dateTo);
}

View File

@ -1,8 +0,0 @@
package com.tsc.bitbucketbot.repository.jpa;
import com.tsc.bitbucketbot.domain.entity.TechInfo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TechInfoRepository extends JpaRepository<TechInfo, Long> {
}

View File

@ -3,6 +3,7 @@ package com.tsc.bitbucketbot.scheduler;
import com.tsc.bitbucketbot.config.BitbucketConfig; import com.tsc.bitbucketbot.config.BitbucketConfig;
import com.tsc.bitbucketbot.domain.MessageSend; import com.tsc.bitbucketbot.domain.MessageSend;
import com.tsc.bitbucketbot.domain.Pagination; import com.tsc.bitbucketbot.domain.Pagination;
import com.tsc.bitbucketbot.domain.entity.Comment;
import com.tsc.bitbucketbot.domain.entity.PullRequest; import com.tsc.bitbucketbot.domain.entity.PullRequest;
import com.tsc.bitbucketbot.dto.bitbucket.CommentJson; import com.tsc.bitbucketbot.dto.bitbucket.CommentJson;
import com.tsc.bitbucketbot.service.CommentService; import com.tsc.bitbucketbot.service.CommentService;
@ -18,9 +19,13 @@ import org.springframework.data.domain.Page;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@ -28,7 +33,7 @@ import java.util.regex.Pattern;
public class SchedulerComments { public class SchedulerComments {
private static final Integer COUNT = 100; private static final Integer COUNT = 100;
private static final Integer NO_COMMENT = 30; private static final Integer NO_COMMENT = 100;
private static final Pattern PATTERN = Pattern.compile("@[\\w]+"); private static final Pattern PATTERN = Pattern.compile("@[\\w]+");
private final CommentService commentService; private final CommentService commentService;
@ -39,69 +44,110 @@ public class SchedulerComments {
private final BitbucketConfig bitbucketConfig; private final BitbucketConfig bitbucketConfig;
@Scheduled(cron = "0 */5 8-18 * * MON-FRI") @Scheduled(cron = "0 */5 8-18 * * MON-FRI")
public void test() { public void newComments() {
log.info("Начало сканирования комментариев"); log.info("Начало сканирования комментариев");
long newLastCommentId = commentService.getLastCommentId(); long commentId = commentService.getLastCommentId() + 1;
long commentId = newLastCommentId + 1;
long count = 0; long count = 0;
do { do {
int page = 0; int page = 0;
Page<PullRequest> pageRequestSheet = pullRequestsService.getAll(Pagination.of(page++, COUNT)); Page<PullRequest> pullRequestPage = pullRequestsService.getAll(Pagination.of(page++, COUNT));
while (pageRequestSheet.hasContent()) { while (pullRequestPage.hasContent()) {
boolean commentSearch = false; for (PullRequest pullRequest : pullRequestPage.getContent()) {
for (PullRequest pullRequest : pageRequestSheet.getContent()) { final String commentUrl = getCommentUrl(commentId, pullRequest);
final Optional<CommentJson> commentJson = Utils.urlToJson( final Optional<CommentJson> optCommentJson = Utils.urlToJson(commentUrl, bitbucketConfig.getToken(), CommentJson.class);
getPrUrl(commentId, pullRequest), if (optCommentJson.isPresent()) {
bitbucketConfig.getToken(), final CommentJson commentJson = optCommentJson.get();
CommentJson.class notification(commentJson, pullRequest);
); saveComments(commentJson, commentUrl);
if (commentJson.isPresent()) { count = 0;
commentSearch = true;
final CommentJson comment = commentJson.get();
notification(
comment,
pullRequest.getName(),
bitbucketConfig.getUrlPullRequest()
.replace("{projectKey}", pullRequest.getProjectKey())
.replace("{repositorySlug}", pullRequest.getRepositorySlug())
.replace("{pullRequestId}", pullRequest.getBitbucketId().toString())
);
newLastCommentId = commentId;
break; break;
} }
} }
if (commentSearch) { pullRequestPage = pullRequestsService.getAll(Pagination.of(page++, COUNT));
count = 0;
break;
} else {
count++;
}
pageRequestSheet = pullRequestsService.getAll(Pagination.of(page++, COUNT));
} }
count++;
commentId += 1; commentId += 1;
} while (count < NO_COMMENT); } while (count < NO_COMMENT);
commentService.saveLastCommentId(newLastCommentId);
log.info("Конец сканирования комментариев"); log.info("Конец сканирования комментариев");
} }
private String getPrUrl(long lastCommentId, PullRequest pullRequest) { @Scheduled(cron = "0 */1 8-18 * * MON-FRI")
public void oldComments() {
@NonNull final List<Comment> comments = commentService.getAllBetweenDate(LocalDate.now().minusDays(10), LocalDate.now());
for (Comment comment : comments) {
final Optional<CommentJson> optCommentJson = Utils.urlToJson(
comment.getUrl(),
bitbucketConfig.getToken(),
CommentJson.class
);
if (optCommentJson.isPresent()) {
final CommentJson commentJson = optCommentJson.get();
final Set<Long> oldAnswerIds = comment.getAnswers();
final List<CommentJson> answerJsons = commentJson.getComments().stream()
.filter(answerJson -> !oldAnswerIds.contains(answerJson.getId()))
.collect(Collectors.toList());
if (!answerJsons.isEmpty()) {
userService.getTelegramIdByLogin(commentJson.getAuthor().getName()).ifPresent(
telegramAuthorComment -> messageSendService.add(
MessageSend.builder()
.telegramId(telegramAuthorComment)
.message(Message.answerComment(commentJson.getText(), answerJsons))
.build()
)
);
comment.getAnswers().addAll(answerJsons.stream().map(CommentJson::getId).collect(Collectors.toList()));
commentService.save(comment);
}
} else {
commentService.delete(comment.getId());
}
}
}
@NonNull
private void saveComments(CommentJson comment, String commentUrl) {
final Comment newComment = new Comment();
newComment.setId(comment.getId());
newComment.setDate(LocalDate.now());
newComment.setUrl(commentUrl);
userService.getTelegramIdByLogin(comment.getAuthor().getName()).ifPresent(newComment::setTelegram);
commentService.save(newComment);
}
private String getCommentUrl(long commentId, PullRequest pullRequest) {
return bitbucketConfig.getUrlPullRequestComment() return bitbucketConfig.getUrlPullRequestComment()
.replace("{projectKey}", pullRequest.getProjectKey()) .replace("{projectKey}", pullRequest.getProjectKey())
.replace("{repositorySlug}", pullRequest.getRepositorySlug()) .replace("{repositorySlug}", pullRequest.getRepositorySlug())
.replace("{pullRequestId}", pullRequest.getBitbucketId().toString()) .replace("{pullRequestId}", pullRequest.getBitbucketId().toString())
.replace("{commentId}", String.valueOf(lastCommentId)); .replace("{commentId}", String.valueOf(commentId));
} }
private void notification(@NonNull CommentJson comment, @NonNull String namePr, @NonNull String urlPr) { private void notification(@NonNull CommentJson comment, @NonNull PullRequest pullRequest) {
final String message = comment.getText(); // notificationAuthorPr(comment, pullRequest);
Matcher matcher = PATTERN.matcher(message); notificationPersonal(comment, pullRequest);
}
private void notificationAuthorPr(@NonNull CommentJson comment, @NonNull PullRequest pullRequest) {
final Long authorTelegram = pullRequest.getAuthor().getTelegramId();
if (authorTelegram != null) {
messageSendService.add(
MessageSend.builder()
.telegramId(authorTelegram)
.message(Message.commentPr(comment, pullRequest.getName(), pullRequest.getUrl()))
.build()
);
}
}
private void notificationPersonal(@NonNull CommentJson comment, @NonNull PullRequest pullRequest) {
Matcher matcher = PATTERN.matcher(comment.getText());
while (matcher.find()) { while (matcher.find()) {
final String login = matcher.group(0).replace("@", ""); final String login = matcher.group(0).replace("@", "");
userService.getTelegramIdByLogin(login).ifPresent( userService.getTelegramIdByLogin(login).ifPresent(
telegramId -> messageSendService.add( telegramId -> messageSendService.add(
MessageSend.builder() MessageSend.builder()
.telegramId(telegramId) .telegramId(telegramId)
.message(Message.personalNotify(comment, namePr, urlPr)) .message(Message.personalNotify(comment, pullRequest.getName(), pullRequest.getUrl()))
.build() .build()
) )
); );

View File

@ -1,11 +1,24 @@
package com.tsc.bitbucketbot.service; package com.tsc.bitbucketbot.service;
import com.tsc.bitbucketbot.domain.Pagination;
import com.tsc.bitbucketbot.domain.entity.Comment;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.data.domain.Page;
import java.time.LocalDate;
import java.util.List;
public interface CommentService { public interface CommentService {
Long getLastCommentId(); Long getLastCommentId();
void saveLastCommentId(@NonNull Long commentId); Page<Comment> getAll(@NonNull Pagination pagination);
@NonNull
List<Comment> getAllBetweenDate(LocalDate dateFrom, LocalDate dateTo);
void save(@NonNull Comment comment);
void delete(@NonNull Long id);
} }

View File

@ -1,29 +1,47 @@
package com.tsc.bitbucketbot.service.impl; package com.tsc.bitbucketbot.service.impl;
import com.tsc.bitbucketbot.domain.entity.TechInfo; import com.tsc.bitbucketbot.domain.Pagination;
import com.tsc.bitbucketbot.repository.jpa.TechInfoRepository; import com.tsc.bitbucketbot.domain.entity.Comment;
import com.tsc.bitbucketbot.repository.jpa.CommentRepository;
import com.tsc.bitbucketbot.service.CommentService; import com.tsc.bitbucketbot.service.CommentService;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Optional; import java.time.LocalDate;
import java.util.List;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class CommentServiceImpl implements CommentService { public class CommentServiceImpl implements CommentService {
private final TechInfoRepository techInfoRepository; private final CommentRepository commentRepository;
@Override @Override
public Long getLastCommentId() { public Long getLastCommentId() {
final Optional<TechInfo> optLastCommentId = techInfoRepository.findById(1L); return commentRepository.findFirstByOrderByIdDesc().map(Comment::getId).orElse(0L);
return optLastCommentId.isPresent() ? optLastCommentId.get().getLastCommentId() : 0L;
} }
@Override @Override
public void saveLastCommentId(@NonNull Long commentId) { public Page<Comment> getAll(@NonNull Pagination pagination) {
techInfoRepository.saveAndFlush(new TechInfo(1L, commentId)); return commentRepository.findAll(PageRequest.of(pagination.getPage(), pagination.getSize()));
}
@Override
public @NonNull List<Comment> getAllBetweenDate(LocalDate dateFrom, LocalDate dateTo) {
return commentRepository.findByDateBetween(dateFrom, dateTo);
}
@Override
public void save(@NonNull Comment comment) {
commentRepository.save(comment);
}
@Override
public void delete(@NonNull Long id) {
commentRepository.deleteById(id);
} }
} }

View File

@ -131,6 +131,35 @@ public class Message {
return message.toString(); return message.toString();
} }
@NonNull
public static String goodWeekEnd() {
return "Ну вот и все! Веселых выходных " + Smile.MIG + Smile.BR +
"До понедельника" + Smile.BUY + Smile.TWO_BR;
}
public static String commentPr(@NonNull CommentJson comment, @NonNull String namePr, @NonNull String urlPr) {
return Smile.BELL + " *Новый комментарий к ПР*" + Smile.BR +
link(namePr, urlPr) +
Smile.HR +
comment.getAuthor().getName() + ": " + comment.getText().replaceAll("@[\\w]+", "");
}
public static String personalNotify(@NonNull CommentJson comment, @NonNull String namePr, @NonNull String urlPr) {
return Smile.BELL + " *Новое упоминание* | " + comment.getAuthor().getName() + Smile.BR +
link(namePr, urlPr) +
Smile.HR +
comment.getText().replaceAll("@[\\w]+", "");
}
public static String answerComment(@NonNull String commentMessage, @NonNull List<CommentJson> answerJsons) {
final StringBuilder message = new StringBuilder();
message.append(Smile.BELL).append("Новые ответы на ваш комментарий").append(Smile.HR)
.append(commentMessage, 0, Math.min(commentMessage.length(), 180)).append(Smile.HR);
answerJsons.forEach(answerJson -> message.append(answerJson.getAuthor().getName()).append(": ")
.append(answerJson.getText(), 0, Math.min(answerJson.getText().length(), 500)).append(Smile.TWO_BR));
return message.toString();
}
private static String needWorkPr(@NonNull List<PullRequest> pullRequestsNeedWork) { private static String needWorkPr(@NonNull List<PullRequest> pullRequestsNeedWork) {
final StringBuilder message = new StringBuilder(); final StringBuilder message = new StringBuilder();
pullRequestsNeedWork.stream() pullRequestsNeedWork.stream()
@ -157,17 +186,4 @@ public class Message {
return "[" + name + "](" + url + ")"; return "[" + name + "](" + url + ")";
} }
@NonNull
public static String goodWeekEnd() {
return "Ну вот и все! Веселых выходных " + Smile.MIG + Smile.BR +
"До понедельника" + Smile.BUY + Smile.TWO_BR;
}
public static String personalNotify(@NonNull CommentJson comment, @NonNull String namePr, @NonNull String urlPr) {
return Smile.BELL + " *Новое упоминание* | " + comment.getAuthor().getName() + Smile.BR +
link(namePr, urlPr) +
Smile.HR +
comment.getText().replaceAll("@[\\w]+", "");
}
} }

View File

@ -7,7 +7,7 @@ spring:
liquibase: liquibase:
change-log: classpath:liquibase/change-log.xml change-log: classpath:liquibase/change-log.xml
jpa: jpa:
show-sql: true show-sql: false
hibernate: hibernate:
ddl-auto: none ddl-auto: none
database-platform: org.hibernate.dialect.PostgreSQLDialect database-platform: org.hibernate.dialect.PostgreSQLDialect

View File

@ -6,5 +6,6 @@
<include file="liquibase/change-set/create-table.xml"/> <include file="liquibase/change-set/create-table.xml"/>
<include file="liquibase/change-set/v1.2.0.xml"/> <include file="liquibase/change-set/v1.2.0.xml"/>
<include file="liquibase/change-set/v1.3.0.xml"/> <include file="liquibase/change-set/v1.3.0.xml"/>
<include file="liquibase/change-set/v1.4.0.xml"/>
</databaseChangeLog> </databaseChangeLog>

View File

@ -0,0 +1,37 @@
<databaseChangeLog
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet id="create-table-comments" author="upagge">
<createTable tableName="comment">
<column name="id" type="int" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="url" type="varchar(300)"/>
<column name="telegram" type="integer"/>
<column name="date" type="datetime"/>
</createTable>
</changeSet>
<changeSet id="create-table-comment-tree" author="upagge">
<createTable tableName="comment_tree">
<column name="parent_id" type="int">
<constraints foreignKeyName="fk_parent_id_from_comment_id" references="comment(id)" nullable="false"/>
</column>
<column name="child_id" type="int">
<constraints foreignKeyName="fk_child_id_from_comment_id" references="comment(id)" nullable="false"/>
</column>
</createTable>
<addPrimaryKey tableName="comment_tree" columnNames="parent_id, child_id"/>
</changeSet>
<changeSet id="drop-table" author="upagge">
<dropTable tableName="tech_info"/>
</changeSet>
<changeSet id="dropForeignKeyConstraint-comment" author="upagge">
<dropForeignKeyConstraint baseTableName="comment_tree" constraintName="fk_child_id_from_comment_id"/>
</changeSet>
</databaseChangeLog>