[AB-262] adding feature mode to suggestion to be automatically reminded of a suggestion after a configurable amount of time

This commit is contained in:
Sheldan
2021-05-24 17:39:19 +02:00
parent f4c1dcb27f
commit 7d30afbd2c
19 changed files with 284 additions and 14 deletions

View File

@@ -111,7 +111,7 @@ public class RemindServiceBean implements ReminderService {
parameters.put("reminderId", reminder.getId().toString());
JobParameters jobParameters = JobParameters.builder().parameters(parameters).build();
String triggerKey = schedulerService.executeJobWithParametersOnce("reminderJob", "utility", jobParameters, Date.from(reminder.getTargetDate()));
log.info("Starting scheduled job with trigger {} to execute reminder. {}", triggerKey, reminder.getId());
log.info("Starting scheduled job with trigger {} to execute reminder {}.", triggerKey, reminder.getId());
reminder.setJobTriggerKey(triggerKey);
reminderManagementService.saveReminder(reminder);
}

View File

@@ -21,6 +21,13 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>

View File

@@ -0,0 +1,40 @@
package dev.sheldan.abstracto.suggestion.job;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.suggestion.service.SuggestionService;
import lombok.Getter;
import lombok.Setter;
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
@Getter
@Setter
@PersistJobDataAfterExecution
public class SuggestionReminderJob extends QuartzJobBean {
@Autowired
private SuggestionService suggestionService;
private Long suggestionId;
private Long serverId;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("Executing suggestion reminder job for suggestion {} in server {}", suggestionId, serverId);
try {
suggestionService.remindAboutSuggestion(new ServerSpecificId(serverId, suggestionId));
} catch (Exception exception) {
log.error("Suggestion reminder job failed.", exception);
}
}
}

View File

@@ -1,5 +1,8 @@
package dev.sheldan.abstracto.suggestion.service;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.*;
@@ -8,11 +11,16 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureMode;
import dev.sheldan.abstracto.suggestion.config.SuggestionPostTarget;
import dev.sheldan.abstracto.suggestion.exception.UnSuggestNotPossibleException;
import dev.sheldan.abstracto.suggestion.model.database.Suggestion;
import dev.sheldan.abstracto.suggestion.model.database.SuggestionState;
import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog;
import dev.sheldan.abstracto.suggestion.model.template.SuggestionReminderModel;
import dev.sheldan.abstracto.suggestion.service.management.SuggestionManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
@@ -24,6 +32,8 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -36,6 +46,7 @@ public class SuggestionServiceBean implements SuggestionService {
public static final String SUGGESTION_YES_EMOTE = "suggestionYes";
public static final String SUGGESTION_NO_EMOTE = "suggestionNo";
public static final String SUGGESTION_COUNTER_KEY = "suggestion";
public static final String SUGGESTION_REMINDER_TEMPLATE_KEY = "suggest_suggestion_reminder";
@Autowired
private SuggestionManagementService suggestionManagementService;
@@ -73,6 +84,15 @@ public class SuggestionServiceBean implements SuggestionService {
@Autowired
private UserService userService;
@Autowired
private SchedulerService schedulerService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ConfigService configService;
@Value("${abstracto.feature.suggestion.removalMaxAge}")
private Long removalMaxAgeSeconds;
@@ -114,8 +134,25 @@ public class SuggestionServiceBean implements SuggestionService {
@Transactional
public void persistSuggestionInDatabase(Member member, String text, Message message, Long suggestionId, Message commandMessage) {
log.info("Persisting suggestion {} for server {} in database.", suggestionId, member.getGuild().getId());
suggestionManagementService.createSuggestion(member, text, message, suggestionId, commandMessage);
Long serverId = member.getGuild().getIdLong();
log.info("Persisting suggestion {} for server {} in database.", suggestionId, serverId);
Suggestion suggestion = suggestionManagementService.createSuggestion(member, text, message, suggestionId, commandMessage);
if(featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, serverId, SuggestionFeatureMode.SUGGESTION_REMINDER)) {
String triggerKey = scheduleSuggestionReminder(serverId, suggestionId);
suggestion.setSuggestionReminderJobTriggerKey(triggerKey);
}
}
private String scheduleSuggestionReminder(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_REMINDER_DAYS_CONFIG_KEY, serverId);
Instant targetDate = Instant.now().plus(days, ChronoUnit.DAYS);
String triggerKey = schedulerService.executeJobWithParametersOnce("suggestionReminderJob", "suggestion", jobParameters, Date.from(targetDate));
log.info("Starting scheduled job with trigger {} to execute suggestion reminder in server {} for suggestion {}.", triggerKey, serverId, suggestionId);
return triggerKey;
}
@Override
@@ -123,6 +160,7 @@ public class SuggestionServiceBean implements SuggestionService {
Long serverId = commandMessage.getGuild().getIdLong();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.ACCEPTED);
cancelSuggestionReminder(suggestion);
log.info("Accepting suggestion {} in server {}.", suggestionId, suggestion.getServer().getId());
return updateSuggestion(commandMessage.getMember(), text, suggestion);
}
@@ -132,6 +170,7 @@ public class SuggestionServiceBean implements SuggestionService {
Long serverId = commandMessage.getGuild().getIdLong();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.VETOED);
cancelSuggestionReminder(suggestion);
log.info("Vetoing suggestion {} in server {}.", suggestionId, suggestion.getServer().getId());
return updateSuggestion(commandMessage.getMember(), text, suggestion);
}
@@ -186,6 +225,7 @@ public class SuggestionServiceBean implements SuggestionService {
Long serverId = commandMessage.getGuild().getIdLong();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.REJECTED);
cancelSuggestionReminder(suggestion);
log.info("Rejecting suggestion {} in server {}.", suggestionId, suggestion.getServer().getId());
return updateSuggestion(commandMessage.getMember(), text, suggestion);
}
@@ -213,8 +253,50 @@ public class SuggestionServiceBean implements SuggestionService {
suggestionManagementService.deleteSuggestion(suggestionsToRemove);
}
@Override
@Transactional
public CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId) {
Long serverId = suggestionId.getServerId();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId.getId());
ServerChannelMessage suggestionServerChannelMessage = ServerChannelMessage
.builder()
.serverId(serverId)
.channelId(suggestion.getChannel().getId())
.messageId(suggestion.getMessageId())
.build();
ServerChannelMessage commandServerChannelMessage = ServerChannelMessage
.builder()
.serverId(serverId)
.channelId(suggestion.getCommandChannel().getId())
.messageId(suggestion.getCommandMessageId())
.build();
SuggestionReminderModel model = SuggestionReminderModel
.builder()
.serverId(serverId)
.serverUser(ServerUser.fromAUserInAServer(suggestion.getSuggester()))
.suggestionCreationDate(suggestion.getCreated())
.suggestionId(suggestionId.getId())
.suggestionMessage(suggestionServerChannelMessage)
.suggestionCommandMessage(commandServerChannelMessage)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_REMINDER_TEMPLATE_KEY, model, serverId);
log.info("Reminding about suggestion {} in server {}.", suggestionId.getId(), serverId);
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, serverId);
return FutureUtils.toSingleFutureGeneric(completableFutures);
}
@Override
public void cancelSuggestionReminder(Suggestion suggestion) {
if(suggestion.getSuggestionReminderJobTriggerKey() != null) {
log.info("Cancelling reminder for suggestion {} in server {}.", suggestion.getSuggestionId().getId(), suggestion.getSuggestionId().getServerId());
schedulerService.stopTrigger(suggestion.getSuggestionReminderJobTriggerKey());
}
}
@Transactional
public void deleteSuggestion(Long suggestionId, Long serverId) {
suggestionManagementService.deleteSuggestion(serverId, suggestionId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
cancelSuggestionReminder(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="tables/tables.xml" relativeToChangelogFile="true"/>
<include file="seedData/data.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="suggestionReminderJob.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-reminder-job-insert">
<insert tableName="scheduler_job">
<column name="name" value="suggestionReminderJob"/>
<column name="group_name" value="suggestion"/>
<column name="clazz" value="dev.sheldan.abstracto.suggestion.job.SuggestionReminderJob"/>
<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-addTriggerColumn">
<addColumn tableName="suggestion" >
<column name="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

@@ -8,4 +8,5 @@
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.0-suggestion/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.12/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.13/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -2,6 +2,14 @@ abstracto.featureFlags.suggestion.featureName=suggestion
abstracto.featureFlags.suggestion.enabled=false
abstracto.postTargets.suggestions.name=suggestions
abstracto.postTargets.suggestionReminder.name=suggestionReminder
abstracto.feature.suggestion.removalMaxAge=3600
abstracto.feature.suggestion.removalDays=2
abstracto.feature.suggestion.removalDays=2
abstracto.featureModes.suggestionReminder.featureName=suggestion
abstracto.featureModes.suggestionReminder.mode=suggestionReminder
abstracto.featureModes.suggestionReminder.enabled=false
abstracto.systemConfigs.suggestionReminderDays.name=suggestionReminderDays
abstracto.systemConfigs.suggestionReminderDays.longValue=7

View File

@@ -11,9 +11,10 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureMode;
import dev.sheldan.abstracto.suggestion.config.SuggestionPostTarget;
import dev.sheldan.abstracto.suggestion.exception.SuggestionNotFoundException;
import dev.sheldan.abstracto.suggestion.exception.UnSuggestNotPossibleException;
import dev.sheldan.abstracto.suggestion.model.database.Suggestion;
import dev.sheldan.abstracto.suggestion.model.database.SuggestionState;
import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog;
@@ -76,6 +77,9 @@ public class SuggestionServiceBeanTest {
@Mock
private ServerManagementService serverManagementService;
@Mock
private FeatureModeService featureModeService;
@Mock
private UserService userService;
@@ -125,10 +129,11 @@ public class SuggestionServiceBeanTest {
@Test
public void testCreateSuggestion() {
when(member.getGuild()).thenReturn(guild);
when(guild.getId()).thenReturn("5");
when(guild.getIdLong()).thenReturn(SERVER_ID);
String text = "text";
Message message = Mockito.mock(Message.class);
Message commandMessage = Mockito.mock(Message.class);
when(featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, SERVER_ID, SuggestionFeatureMode.SUGGESTION_REMINDER)).thenReturn(false);
testUnit.persistSuggestionInDatabase(member, text, message, SUGGESTION_ID, commandMessage);
verify(suggestionManagementService, times(1)).createSuggestion(member, text, message, SUGGESTION_ID, commandMessage);
}

View File

@@ -2,7 +2,9 @@ package dev.sheldan.abstracto.suggestion.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.suggestion.service.SuggestionService;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@@ -18,11 +20,21 @@ public class SuggestionFeatureConfig implements FeatureConfig {
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(SuggestionPostTarget.SUGGESTION);
return Arrays.asList(SuggestionPostTarget.SUGGESTION, SuggestionPostTarget.SUGGESTION_REMINDER);
}
@Override
public List<String> getRequiredEmotes() {
return Arrays.asList("suggestionYes", "suggestionNo");
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(SuggestionFeatureMode.SUGGESTION_REMINDER);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(SuggestionService.SUGGESTION_REMINDER_DAYS_CONFIG_KEY);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.suggestion.config;
import dev.sheldan.abstracto.core.config.FeatureMode;
import lombok.Getter;
@Getter
public enum SuggestionFeatureMode implements FeatureMode {
SUGGESTION_REMINDER("suggestionReminder");
private final String key;
SuggestionFeatureMode(String key) {
this.key = key;
}
}

View File

@@ -5,7 +5,7 @@ import lombok.Getter;
@Getter
public enum SuggestionPostTarget implements PostTargetEnum {
SUGGESTION("suggestions");
SUGGESTION("suggestions"), SUGGESTION_REMINDER("suggestionReminder");
private String key;

View File

@@ -66,4 +66,7 @@ public class Suggestion implements Serializable {
@Column(name = "command_message_id")
private Long commandMessageId;
@Column(name = "job_trigger_key")
private String suggestionReminderJobTriggerKey;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.suggestion.model.template;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@Builder
public class SuggestionReminderModel {
private Long serverId;
private Long suggestionId;
private Instant suggestionCreationDate;
private ServerChannelMessage suggestionMessage;
private ServerChannelMessage suggestionCommandMessage;
private ServerUser serverUser;
}

View File

@@ -1,15 +1,20 @@
package dev.sheldan.abstracto.suggestion.service;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.suggestion.model.database.Suggestion;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import java.util.concurrent.CompletableFuture;
public interface SuggestionService {
String SUGGESTION_REMINDER_DAYS_CONFIG_KEY = "suggestionReminderDays";
CompletableFuture<Void> createSuggestionMessage(Message commandMessage, String text);
CompletableFuture<Void> acceptSuggestion(Long suggestionId, Message commandMessage, String text);
CompletableFuture<Void> vetoSuggestion(Long suggestionId, Message commandMessage, String text);
CompletableFuture<Void> rejectSuggestion(Long suggestionId, Message commandMessage, String text);
CompletableFuture<Void> removeSuggestion(Long suggestionId, Member member);
void cleanUpSuggestions();
CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId);
void cancelSuggestionReminder(Suggestion suggestion);
}