Skip to content

Commit

Permalink
#6 - stars-up bot: work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
iakunin committed Apr 13, 2020
1 parent 6717169 commit f95c4dd
Show file tree
Hide file tree
Showing 23 changed files with 798 additions and 25 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Bots list:
- stars-up: `The repo gained 120 stars in 7 days`
- forks-up: `The repo was forked 40 times in 7 days`
5% increase/decrease in both metrics
If the difference is smaller than 10 stars, the bot stays quiet.
From 0 to 200 stars bot will be sending 1 review per 10 stars increase.
From 200 to 220 stars bot will be sending 1 review per 11 stars increase.
From 220 to 240 stars bot will be sending 1 review per 12 stars increase.
And so on.


### Ideas backlog
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {

implementation 'de.siegmar:logback-gelf:2.0.1'
implementation 'org.cactoos:cactoos:0.43'
implementation 'org.javatuples:javatuples:1.2'

implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:2.1.1.RELEASE'
implementation 'io.github.openfeign:feign-jackson:9.3.1'
Expand All @@ -65,6 +66,7 @@ dependencies {
testImplementation 'com.github.javafaker:javafaker:1.0.2'
testImplementation 'com.github.tomakehurst:wiremock:2.26.3'
testImplementation 'org.codehaus.groovy:groovy-all:3.0.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.6.2'
}

springBoot {
Expand All @@ -84,6 +86,7 @@ lombok {
version = '1.18.10'
config['lombok.accessors.chain'] = 'true'
config['lombok.equalsAndHashCode.callSuper'] = 'skip'
config['lombok.toString.callSuper'] = 'call'
}

sonarqube {
Expand Down
1 change: 1 addition & 0 deletions lombok.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ config.stopBubbling = true
lombok.accessors.chain = true
lombok.addLombokGeneratedAnnotation = true
lombok.equalsAndHashCode.callSuper = skip
lombok.toString.callSuper = call
2 changes: 2 additions & 0 deletions src/main/java/dev/iakunin/codexiabot/bot/Bot.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ public interface Bot {
enum Type {
FOUND_ON_HACKERNEWS,
FOUND_ON_REDDIT,
STARS_UP,
FORKS_UP,
}
}
13 changes: 4 additions & 9 deletions src/main/java/dev/iakunin/codexiabot/bot/FoundOnHackernews.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,12 @@ public void run() {
githubRepo -> {
final Set<GithubRepoSource> allRepoSources = this.githubModule.findAllRepoSources(githubRepo);

final CodexiaProject codexiaProject = allRepoSources.stream()
.filter(source -> source.getSource() == GithubModule.Source.CODEXIA)
.findFirst()
.flatMap(
source -> this.codexiaModule.findByExternalId(Integer.valueOf(source.getExternalId()))
)
final CodexiaProject codexiaProject = this.codexiaModule
.findCodexiaProject(githubRepo)
.orElseThrow(
() -> new RuntimeException(
String.format(
"Unable to find source with type='%s' for githubRepoId='%s' " +
"or codexiaProject by externalId",
GithubModule.Source.CODEXIA.name(),
"Unable to find CodexiaProject for githubRepoId='%s'",
githubRepo.getId()
)
)
Expand Down Expand Up @@ -116,6 +110,7 @@ public void run() {
}

@Value
//@TODO: rewrite using org.javatuples.Pair
private static final class TmpDto {
private CodexiaProject codexiaProject;
private GithubRepoSource hackernewsSource;
Expand Down
13 changes: 4 additions & 9 deletions src/main/java/dev/iakunin/codexiabot/bot/FoundOnReddit.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,12 @@ public void run() {
githubRepo -> {
final Set<GithubRepoSource> allRepoSources = this.githubModule.findAllRepoSources(githubRepo);

final CodexiaProject codexiaProject = allRepoSources.stream()
.filter(source -> source.getSource() == GithubModule.Source.CODEXIA)
.findFirst()
.flatMap(
source -> this.codexiaModule.findByExternalId(Integer.valueOf(source.getExternalId()))
)
final CodexiaProject codexiaProject = this.codexiaModule
.findCodexiaProject(githubRepo)
.orElseThrow(
() -> new RuntimeException(
String.format(
"Unable to find source with type='%s' for githubRepoId='%s' " +
"or codexiaProject by externalId",
GithubModule.Source.CODEXIA.name(),
"Unable to find CodexiaProject for githubRepoId='%s'",
githubRepo.getId()
)
)
Expand Down Expand Up @@ -100,6 +94,7 @@ public void run() {
}

@Value
//@TODO: rewrite using org.javatuples.Pair
private static final class TmpDto {
private CodexiaProject codexiaProject;
private GithubRepoSource redditSource;
Expand Down
166 changes: 166 additions & 0 deletions src/main/java/dev/iakunin/codexiabot/bot/StarsUp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package dev.iakunin.codexiabot.bot;

import dev.iakunin.codexiabot.bot.entity.StarsUpResult;
import dev.iakunin.codexiabot.bot.repository.StarsUpResultRepository;
import dev.iakunin.codexiabot.codexia.CodexiaModule;
import dev.iakunin.codexiabot.codexia.entity.CodexiaReview;
import dev.iakunin.codexiabot.github.GithubModule;
import dev.iakunin.codexiabot.github.entity.GithubRepoStat;
import dev.iakunin.codexiabot.github.entity.GithubRepoStat.GithubApi;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Deque;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.javatuples.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Slf4j
@AllArgsConstructor(onConstructor_={@Autowired})
public class StarsUp {

private static final Bot.Type BOT_TYPE = Bot.Type.STARS_UP;

private final GithubModule githubModule;

private final CodexiaModule codexiaModule;

private final StarsUpResultRepository starsUpResultRepository;

@Scheduled(cron="${app.cron.bot.stars-up:-}")
public void run() {
log.info("Running {}", this.getClass().getName());

this.githubModule.findAllInCodexia()
.stream()
.map(
repo -> Pair.with(
repo,
this.starsUpResultRepository.findFirstByGithubRepoOrderByIdDesc(repo)
)
)
.map(
pair -> Pair.with(
pair.getValue0(),
pair.getValue1().map(
result -> result.getGithubRepoStat().getId()
).orElse(0L)
)
)
.map(
pair -> this.githubModule.findAllGithubApiStat(pair.getValue0(), pair.getValue1())
)
.filter(statList -> statList.size() >= 2)
.filter(
statList -> this.shouldReviewBeSubmitted(
(GithubApi) statList.getFirst().getStat(),
(GithubApi) statList.getLast().getStat()
)
)
.forEach(this::processStatList)
;

// Тест-кейс, когда githubModule.findAllGithubApiStat возвращает больше 2х записей
// Тест-кейс, когда githubModule.findAllGithubApiStat возвращает точно 2 записи
// Тест-кейс, когда githubModule.findAllGithubApiStat возвращает меньше 2х записей

// Тест-кейсы с двумя последовательными запусками с изменениями состояния бота (бот сработал)
// Тест-кейсы с двумя последовательными запусками с изменениями состояния github-статистики
// (бот может как сработать, так и нет)


log.info("Exiting from {}", this.getClass().getName());
}

@Transactional
protected void processStatList(Deque<GithubRepoStat> deque) {
final GithubRepoStat first = deque.getFirst();
final GithubRepoStat last = deque.getLast();
final GithubApi firstStat = (GithubApi) first.getStat();
final GithubApi lastStat = (GithubApi) last.getStat();

final CodexiaReview review = new CodexiaReview()
.setText(
String.format(
"The repo gained %d stars since %s " +
"(was: %d stars, now: %d stars). " +
"See the stars history [here](https://star-history.t9t.io/#%s).",
lastStat.getStars() - firstStat.getStars(),
ZonedDateTime.of(
first.getCreatedAt(),
ZoneOffset.UTC
).toString(), //@TODO: check the timezone
firstStat.getStars(),
lastStat.getStars(),
first.getGithubRepo().getFullName()
)
)
.setAuthor(BOT_TYPE.name())
.setReason(String.valueOf(lastStat.getStars()))
.setCodexiaProject(
this.codexiaModule
.findCodexiaProject(last.getGithubRepo())
.orElseThrow(
() -> new RuntimeException(
String.format(
"Unable to find CodexiaProject for githubRepoId='%s'",
last.getGithubRepo().getId()
)
)
)
);

this.codexiaModule.sendReview(review);
this.codexiaModule.sendMeta(
review.getCodexiaProject(),
"stars-count",
this.codexiaModule
.findAllReviews(review.getCodexiaProject(), review.getAuthor())
.stream()
.map(CodexiaReview::getReason)
.collect(Collectors.joining(","))
);
this.starsUpResultRepository.save(
new StarsUpResult()
.setGithubRepo(last.getGithubRepo())
.setGithubRepoStat(last)
);
}

private boolean shouldReviewBeSubmitted(GithubApi firstStat, GithubApi lastStat) {
final int increase = lastStat.getStars() - firstStat.getStars();

if (increase < 10) {
log.info(
"Increase is smaller than 10; increase={}; firstStat={}; lastStat={};",
increase,
firstStat,
lastStat
);
return false;
}

if (increase >= (firstStat.getStars() * 0.05)) {
log.info(
"Review should be submitted; increase={}; firstStat={}; lastStat={};",
increase,
firstStat,
lastStat
);
return true;
}

log.info(
"Review should not be submitted; increase={}; firstStat={}; lastStat={};",
increase,
firstStat,
lastStat
);
return false;
}
}
21 changes: 21 additions & 0 deletions src/main/java/dev/iakunin/codexiabot/bot/entity/StarsUpResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.iakunin.codexiabot.bot.entity;

import dev.iakunin.codexiabot.common.entity.AbstractEntity;
import dev.iakunin.codexiabot.github.entity.GithubRepo;
import dev.iakunin.codexiabot.github.entity.GithubRepoStat;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Entity
@Data
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
public final class StarsUpResult extends AbstractEntity {

@ManyToOne
private GithubRepo githubRepo;

@ManyToOne
private GithubRepoStat githubRepoStat;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.iakunin.codexiabot.bot.repository;

import dev.iakunin.codexiabot.bot.entity.StarsUpResult;
import dev.iakunin.codexiabot.github.entity.GithubRepo;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StarsUpResultRepository extends JpaRepository<StarsUpResult, Long> {

Optional<StarsUpResult> findFirstByGithubRepoOrderByIdDesc(GithubRepo repo);
}
12 changes: 11 additions & 1 deletion src/main/java/dev/iakunin/codexiabot/codexia/CodexiaModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@

import dev.iakunin.codexiabot.codexia.entity.CodexiaProject;
import dev.iakunin.codexiabot.codexia.entity.CodexiaReview;
import dev.iakunin.codexiabot.github.entity.GithubRepo;
import java.util.List;
import java.util.Optional;

public interface CodexiaModule {

//@TODO: split codexiaModule.sendReview() to codexiaModule.createReview() and codexiaModule.sendReview()
// codexiaModule.createReview() - just a saving to DB
// codexiaModule.sendReview() - real sending review to CDX api

//@TODO: codexiaModule.sendReview() must be used ONLY inside a cron-job
// in other code there should be only a codexiaModule.createReview() calls

//@TODO: check that all the codexiaModule.createReview() calls MUST be inside a transaction

void sendReview(CodexiaReview review);

void sendMeta(CodexiaProject codexiaProject, String metaKey, String metaValue);

Optional<CodexiaProject> findByExternalId(Integer externalId);
Optional<CodexiaProject> findCodexiaProject(GithubRepo repo);

boolean isReviewExist(CodexiaProject codexiaProject, String author, String reason);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import dev.iakunin.codexiabot.codexia.repository.CodexiaReviewNotificationRepository;
import dev.iakunin.codexiabot.codexia.repository.CodexiaReviewRepository;
import dev.iakunin.codexiabot.codexia.sdk.CodexiaClient;
import dev.iakunin.codexiabot.github.GithubModule;
import dev.iakunin.codexiabot.github.entity.GithubRepo;
import feign.FeignException;
import java.util.List;
import java.util.Optional;
Expand All @@ -31,6 +33,8 @@ public final class CodexiaModuleImpl implements CodexiaModule {

private final CodexiaClient codexiaClient;

private final GithubModule githubModule;

@Override
public void sendReview(CodexiaReview review) {
log.info("Got a review: {}", review);
Expand Down Expand Up @@ -95,8 +99,17 @@ public void sendMeta(CodexiaProject codexiaProject, String metaKey, String metaV
}

@Override
public Optional<CodexiaProject> findByExternalId(Integer externalId) {
return this.codexiaProjectRepository.findByExternalId(externalId);
public Optional<CodexiaProject> findCodexiaProject(GithubRepo repo) {
return this.githubModule
.findAllRepoSources(repo)
.stream()
.filter(source -> source.getSource() == GithubModule.Source.CODEXIA)
.findFirst()
.flatMap(
source -> this.codexiaProjectRepository.findByExternalId(
Integer.valueOf(source.getExternalId())
)
);
}

@Override
Expand All @@ -106,6 +119,6 @@ public boolean isReviewExist(CodexiaProject codexiaProject, String author, Strin

@Override
public List<CodexiaReview> findAllReviews(CodexiaProject codexiaProject, String author) {
return this.codexiaReviewRepository.findAllByCodexiaProjectAndAuthor(codexiaProject, author);
return this.codexiaReviewRepository.findAllByCodexiaProjectAndAuthorOrderByIdAsc(codexiaProject, author);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ public interface CodexiaReviewRepository extends JpaRepository<CodexiaReview, Lo

boolean existsByCodexiaProjectAndAuthorAndReason(CodexiaProject codexiaProject, String author, String reason);

List<CodexiaReview> findAllByCodexiaProjectAndAuthor(CodexiaProject codexiaProject, String author);
List<CodexiaReview> findAllByCodexiaProjectAndAuthorOrderByIdAsc(CodexiaProject codexiaProject, String author);
}
Loading

0 comments on commit f95c4dd

Please sign in to comment.