[AB-76] adding evaluation job for suggestions

This commit is contained in:
Sheldan
2022-11-11 00:06:16 +01:00
parent ea2f62b721
commit d315113395
18 changed files with 199 additions and 29 deletions

View File

@@ -46,7 +46,7 @@ public class Accept extends AbstractConditionableCommand {
Long suggestionId = (Long) parameters.get(0);
String text = parameters.size() == 2 ? (String) parameters.get(1) : "";
log.debug("Using default reason for accept: {}.", parameters.size() != 2);
return suggestionService.acceptSuggestion(suggestionId, commandContext.getAuthor(), text)
return suggestionService.acceptSuggestion(commandContext.getGuild().getIdLong(), suggestionId, commandContext.getAuthor(), text)
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@@ -59,7 +59,7 @@ public class Accept extends AbstractConditionableCommand {
} else {
acceptText = "";
}
return suggestionService.acceptSuggestion(suggestionId, event.getMember(), acceptText)
return suggestionService.acceptSuggestion(event.getGuild().getIdLong(), suggestionId, event.getMember(), acceptText)
.thenCompose(unused -> interactionService.replyEmbed(ACCEPT_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -46,7 +46,7 @@ public class Reject extends AbstractConditionableCommand {
Long suggestionId = (Long) parameters.get(0);
String text = parameters.size() == 2 ? (String) parameters.get(1) : "";
log.debug("Using default reason for reject: {}.", parameters.size() != 2);
return suggestionService.rejectSuggestion(suggestionId, commandContext.getAuthor(), text)
return suggestionService.rejectSuggestion(commandContext.getGuild().getIdLong(), suggestionId, commandContext.getAuthor(), text)
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@@ -59,7 +59,7 @@ public class Reject extends AbstractConditionableCommand {
} else {
rejectText = "";
}
return suggestionService.rejectSuggestion(suggestionId, event.getMember(), rejectText)
return suggestionService.rejectSuggestion(event.getGuild().getIdLong(), suggestionId, event.getMember(), rejectText)
.thenCompose(unused -> interactionService.replyEmbed(REJECT_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -41,14 +41,14 @@ public class UnSuggest extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long suggestionId = (Long) parameters.get(0);
return suggestionService.removeSuggestion(suggestionId, commandContext.getAuthor())
return suggestionService.removeSuggestion(commandContext.getGuild().getIdLong(), suggestionId, commandContext.getAuthor())
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long suggestionId = slashCommandParameterService.getCommandOption(SUGGESTION_ID_PARAMETER, event, Long.class, Integer.class).longValue();
return suggestionService.removeSuggestion(suggestionId, event.getMember())
return suggestionService.removeSuggestion(event.getMember().getIdLong(), suggestionId, event.getMember())
.thenCompose(unused -> interactionService.replyEmbed(UN_SUGGEST_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -46,7 +46,7 @@ public class Veto extends AbstractConditionableCommand {
Long suggestionId = (Long) parameters.get(0);
String text = parameters.size() == 2 ? (String) parameters.get(1) : "";
log.debug("Using default reason for veto: {}.", parameters.size() != 2);
return suggestionService.vetoSuggestion(suggestionId, commandContext.getAuthor(), text)
return suggestionService.vetoSuggestion(commandContext.getGuild().getIdLong(), suggestionId, commandContext.getAuthor(), text)
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@@ -59,7 +59,7 @@ public class Veto extends AbstractConditionableCommand {
} else {
vetoText = "";
}
return suggestionService.vetoSuggestion(suggestionId, event.getMember(), vetoText)
return suggestionService.vetoSuggestion(event.getGuild().getIdLong(), suggestionId, event.getMember(), vetoText)
.thenCompose(unused -> interactionService.replyEmbed(VETO_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -0,0 +1,41 @@
package dev.sheldan.abstracto.suggestion.job;
import dev.sheldan.abstracto.suggestion.service.SuggestionService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class SuggestionEvaluationJob extends QuartzJobBean {
private Long suggestionId;
private Long serverId;
@Autowired
private SuggestionService suggestionService;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("Executing suggestion evaluation job for suggestion {} in server {}.", suggestionId, serverId);
try {
suggestionService.evaluateSuggestion(serverId, suggestionId)
.thenAccept(unused -> {
log.info("Finished evaluation suggestion {} in server {}.", suggestionId, serverId);
})
.exceptionally(throwable -> {
log.error("Failed to evaluate suggestion {} in server {}.", suggestionId, serverId, throwable);
return null;
});
} catch (Exception exception) {
log.error("Suggestion evaluation up job failed.", exception);
}
}
}

View File

@@ -126,6 +126,11 @@ public class SuggestionServiceBean implements SuggestionService {
AUserInAServer userSuggester = userInServerManagementService.loadOrCreateUser(suggester);
Long newSuggestionId = counterService.getNextCounterValue(server, SUGGESTION_COUNTER_KEY);
Boolean useButtons = featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, serverId, SuggestionFeatureMode.SUGGESTION_BUTTONS);
Boolean autoEvaluationEnabled = featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, serverId, SuggestionFeatureMode.SUGGESTION_AUTO_EVALUATE);
Long autoEvaluateDays = null;
if(autoEvaluationEnabled) {
autoEvaluateDays = configService.getLongValueOrConfigDefault(SUGGESTION_AUTO_EVALUATE_DAYS_CONFIG_KEY, serverId);
}
SuggestionLog model = SuggestionLog
.builder()
.suggestionId(newSuggestionId)
@@ -137,6 +142,8 @@ public class SuggestionServiceBean implements SuggestionService {
.useButtons(useButtons)
.suggester(suggester.getUser())
.text(text)
.autoEvaluationEnabled(autoEvaluationEnabled)
.autoEvaluationTargetDate(autoEvaluationEnabled ? Instant.now().plus(autoEvaluateDays, ChronoUnit.DAYS) : null)
.build();
if(useButtons) {
setupButtonIds(model);
@@ -200,6 +207,10 @@ public class SuggestionServiceBean implements SuggestionService {
String triggerKey = scheduleSuggestionReminder(serverId, suggestionId);
suggestion.setSuggestionReminderJobTriggerKey(triggerKey);
}
if(featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, serverId, SuggestionFeatureMode.SUGGESTION_AUTO_EVALUATE)) {
String triggerKey = scheduleEvaluationReminder(serverId, suggestionId);
suggestion.setSuggestionEvaluationJobTriggerKey(triggerKey);
}
}
private String scheduleSuggestionReminder(Long serverId, Long suggestionId) {
@@ -214,25 +225,50 @@ public class SuggestionServiceBean implements SuggestionService {
return triggerKey;
}
private String scheduleEvaluationReminder(Long serverId, Long suggestionId) {
HashMap<Object, Object> parameters = new HashMap<>();
parameters.put("serverId", serverId.toString());
parameters.put("suggestionId", suggestionId.toString());
JobParameters jobParameters = JobParameters.builder().parameters(parameters).build();
Long days = configService.getLongValueOrConfigDefault(SuggestionService.SUGGESTION_AUTO_EVALUATE_DAYS_CONFIG_KEY, serverId);
Instant targetDate = Instant.now().plus(days, ChronoUnit.DAYS);
String triggerKey = schedulerService.executeJobWithParametersOnce("suggestionEvaluationJob", "suggestion", jobParameters, Date.from(targetDate));
log.info("Starting scheduled job with trigger {} to execute suggestion evaluation in server {} for suggestion {}.", triggerKey, serverId, suggestionId);
return triggerKey;
}
@Override
public CompletableFuture<Void> acceptSuggestion(Long suggestionId, Member actingMember, String text) {
return self.setSuggestionToFinalState(actingMember, suggestionId, text, SuggestionState.ACCEPTED);
public CompletableFuture<Void> acceptSuggestion(Long serverId, Long suggestionId, Member actingMember, String text) {
return self.setSuggestionToFinalState(actingMember, serverId, suggestionId, text, SuggestionState.ACCEPTED);
}
@Override
public CompletableFuture<Void> evaluateSuggestion(Long serverId, Long suggestionId) {
Long approvalPercentage = configService.getLongValueOrConfigDefault(SUGGESTION_AUTO_EVALUATE_PERCENTAGE_CONFIG_KEY, serverId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
Long agreements = suggestionVoteManagementService.getDecisionsForSuggestion(suggestion, SuggestionDecision.AGREE);
Long disagreements = suggestionVoteManagementService.getDecisionsForSuggestion(suggestion, SuggestionDecision.DISAGREE);
double suggestionPercentage = ((double) agreements) / (disagreements + agreements) * 100;
if(suggestionPercentage > approvalPercentage) {
return acceptSuggestion(serverId, suggestionId, null, null);
} else {
return rejectSuggestion(serverId, suggestionId, null, null);
}
}
@Transactional
public CompletableFuture<Void> setSuggestionToFinalState(Member executingMember, Long suggestionId, String text, SuggestionState state) {
Long serverId = executingMember.getGuild().getIdLong();
public CompletableFuture<Void> setSuggestionToFinalState(Member executingMember, Long serverId, Long suggestionId, String text, SuggestionState state) {
postTargetService.validatePostTarget(SuggestionPostTarget.SUGGESTION, serverId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, state);
cancelSuggestionReminder(suggestion);
cancelSuggestionJobs(suggestion);
log.info("Setting suggestion {} in server {} to state {}", suggestionId, suggestion.getServer().getId(), state);
return updateSuggestion(executingMember, text, suggestion);
}
@Override
public CompletableFuture<Void> vetoSuggestion(Long suggestionId, Member actingMember, String text) {
return self.setSuggestionToFinalState(actingMember, suggestionId, text, SuggestionState.VETOED);
public CompletableFuture<Void> vetoSuggestion(Long serverId, Long suggestionId, Member actingMember, String text) {
return self.setSuggestionToFinalState(actingMember, serverId, suggestionId, text, SuggestionState.VETOED);
}
private CompletableFuture<Void> updateSuggestion(Member memberExecutingCommand, String reason, Suggestion suggestion) {
@@ -300,13 +336,12 @@ public class SuggestionServiceBean implements SuggestionService {
}
@Override
public CompletableFuture<Void> rejectSuggestion(Long suggestionId, Member actingMember, String text) {
return self.setSuggestionToFinalState(actingMember, suggestionId, text, SuggestionState.REJECTED);
public CompletableFuture<Void> rejectSuggestion(Long serverId, Long suggestionId, Member actingMember, String text) {
return self.setSuggestionToFinalState(actingMember, serverId, suggestionId, text, SuggestionState.REJECTED);
}
@Override
public CompletableFuture<Void> removeSuggestion(Long suggestionId, Member member) {
Long serverId = member.getGuild().getIdLong();
public CompletableFuture<Void> removeSuggestion(Long serverId, Long suggestionId, Member member) {
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
if(member.getIdLong() != suggestion.getSuggester().getUserReference().getId() ||
suggestion.getCreated().isBefore(Instant.now().minus(Duration.ofSeconds(removalMaxAgeSeconds)))) {
@@ -371,11 +406,15 @@ public class SuggestionServiceBean implements SuggestionService {
}
@Override
public void cancelSuggestionReminder(Suggestion suggestion) {
public void cancelSuggestionJobs(Suggestion suggestion) {
if(suggestion.getSuggestionReminderJobTriggerKey() != null) {
log.info("Cancelling reminder for suggestion {} in server {}.", suggestion.getSuggestionId().getId(), suggestion.getSuggestionId().getServerId());
schedulerService.stopTrigger(suggestion.getSuggestionReminderJobTriggerKey());
}
if(suggestion.getSuggestionEvaluationJobTriggerKey() != null) {
log.info("Cancelling evaluation job for suggestion {} in server {}.", suggestion.getSuggestionId().getId(), suggestion.getSuggestionId().getServerId());
schedulerService.stopTrigger(suggestion.getSuggestionEvaluationJobTriggerKey());
}
}
@Override
@@ -398,7 +437,7 @@ public class SuggestionServiceBean implements SuggestionService {
@Transactional
public void deleteSuggestion(Long suggestionId, Long serverId) {
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
cancelSuggestionReminder(suggestion);
cancelSuggestionJobs(suggestion);
suggestionVoteManagementService.deleteSuggestionVotes(suggestion);
suggestionManagementService.deleteSuggestion(suggestion);
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="suggestionEvaluationJob.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,18 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="suggestion-evaluation-job-insert">
<insert tableName="scheduler_job">
<column name="name" value="suggestionEvaluationJob"/>
<column name="group_name" value="suggestion"/>
<column name="clazz" value="dev.sheldan.abstracto.suggestion.job.SuggestionEvaluationJob"/>
<column name="active" value="true"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,15 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="suggestion-add-suggestion-evaluation-job-colum">
<addColumn tableName="suggestion" >
<column name="evaluation_job_trigger_key"
type="varchar(255)"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="suggestion.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -11,4 +11,5 @@
<include file="1.2.13/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.8/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.0/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.8/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -14,6 +14,16 @@ abstracto.featureModes.suggestionReminder.enabled=false
abstracto.systemConfigs.suggestionReminderDays.name=suggestionReminderDays
abstracto.systemConfigs.suggestionReminderDays.longValue=7
abstracto.systemConfigs.suggestionAutoEvaluateDays.name=suggestionAutoEvaluateDays
abstracto.systemConfigs.suggestionAutoEvaluateDays.longValue=7
abstracto.systemConfigs.suggestionAutoEvaluatePercentage.name=suggestionAutoEvaluatePercentage
abstracto.systemConfigs.suggestionAutoEvaluatePercentage.longValue=50
abstracto.featureModes.suggestionAutoEvaluate.featureName=suggestion
abstracto.featureModes.suggestionAutoEvaluate.mode=suggestionAutoEvaluate
abstracto.featureModes.suggestionAutoEvaluate.enabled=false
abstracto.featureModes.suggestionButton.featureName=suggestion
abstracto.featureModes.suggestionButton.mode=suggestionButton
abstracto.featureModes.suggestionButton.enabled=true

View File

@@ -33,11 +33,16 @@ public class SuggestionFeatureConfig implements FeatureConfig {
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(SuggestionFeatureMode.SUGGESTION_REMINDER, SuggestionFeatureMode.SUGGESTION_BUTTONS);
return Arrays.asList(
SuggestionFeatureMode.SUGGESTION_REMINDER,
SuggestionFeatureMode.SUGGESTION_BUTTONS,
SuggestionFeatureMode.SUGGESTION_AUTO_EVALUATE);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(SuggestionService.SUGGESTION_REMINDER_DAYS_CONFIG_KEY);
return Arrays.asList(SuggestionService.SUGGESTION_REMINDER_DAYS_CONFIG_KEY,
SuggestionService.SUGGESTION_AUTO_EVALUATE_DAYS_CONFIG_KEY,
SuggestionService.SUGGESTION_AUTO_EVALUATE_PERCENTAGE_CONFIG_KEY);
}
}

View File

@@ -5,7 +5,7 @@ import lombok.Getter;
@Getter
public enum SuggestionFeatureMode implements FeatureMode {
SUGGESTION_REMINDER("suggestionReminder"), SUGGESTION_BUTTONS("suggestionButton");
SUGGESTION_REMINDER("suggestionReminder"), SUGGESTION_BUTTONS("suggestionButton"), SUGGESTION_AUTO_EVALUATE("suggestionAutoEvaluate");
private final String key;

View File

@@ -69,4 +69,7 @@ public class Suggestion implements Serializable {
@Column(name = "job_trigger_key")
private String suggestionReminderJobTriggerKey;
@Column(name = "evaluation_job_trigger_key")
private String suggestionEvaluationJobTriggerKey;
}

View File

@@ -10,6 +10,8 @@ import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import java.time.Instant;
@Getter
@Setter
@SuperBuilder
@@ -29,6 +31,8 @@ public class SuggestionLog {
private ButtonConfigModel agreeButtonModel;
private ButtonConfigModel disAgreeButtonModel;
private ButtonConfigModel removeVoteButtonModel;
private Instant autoEvaluationTargetDate;
private Boolean autoEvaluationEnabled;
public String getOriginalMessageUrl() {
return MessageUtils.buildMessageUrl(serverId, originalChannelId , originalMessageId);

View File

@@ -10,14 +10,17 @@ import java.util.concurrent.CompletableFuture;
public interface SuggestionService {
String SUGGESTION_REMINDER_DAYS_CONFIG_KEY = "suggestionReminderDays";
String SUGGESTION_AUTO_EVALUATE_DAYS_CONFIG_KEY = "suggestionAutoEvaluateDays";
String SUGGESTION_AUTO_EVALUATE_PERCENTAGE_CONFIG_KEY = "suggestionAutoEvaluatePercentage";
CompletableFuture<Void> createSuggestionMessage(ServerChannelMessageUser cause, String text, String attachmentURL);
CompletableFuture<Void> acceptSuggestion(Long suggestionId, Member actingMember, String text);
CompletableFuture<Void> vetoSuggestion(Long suggestionId, Member actingMember, String text);
CompletableFuture<Void> rejectSuggestion(Long suggestionId, Member actingMember, String text);
CompletableFuture<Void> removeSuggestion(Long suggestionId, Member member);
CompletableFuture<Void> acceptSuggestion(Long serverId, Long suggestionId, Member actingMember, String text);
CompletableFuture<Void> evaluateSuggestion(Long serverId, Long suggestionId);
CompletableFuture<Void> vetoSuggestion(Long serverId, Long suggestionId, Member actingMember, String text);
CompletableFuture<Void> rejectSuggestion(Long serverId, Long suggestionId, Member actingMember, String text);
CompletableFuture<Void> removeSuggestion(Long serverId, Long suggestionId, Member member);
void cleanUpSuggestions();
CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId);
void cancelSuggestionReminder(Suggestion suggestion);
void cancelSuggestionJobs(Suggestion suggestion);
SuggestionInfoModel getSuggestionInfo(Long serverId, Long suggestionId);
SuggestionInfoModel getSuggestionInfo(ServerSpecificId suggestionId);
}