[AB-307] enabling dependent features and including them into the setup wizard

this comes with the usual limitation, that dependent features must be referenced via importing
This commit is contained in:
Sheldan
2021-11-28 13:19:17 +01:00
parent c10296251c
commit f2aa7035aa
5 changed files with 103 additions and 35 deletions

View File

@@ -13,18 +13,22 @@ import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel;
import dev.sheldan.abstracto.core.models.template.commands.FeatureSwitchModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
public class DisableFeature extends AbstractConditionableCommand {
@@ -43,26 +47,46 @@ public class DisableFeature extends AbstractConditionableCommand {
@Autowired
private ServerManagementService serverManagementService;
private static final String DISABLE_FEATURE_ALL_FEATURES_RESPONSE_TEMPLATE_KEY = "disableFeature_all_features_response";
private static final String DISABLE_FEATURE_DEPENDENCIES_RESPONSE_TEMPLATE_KEY = "disableFeature_feature_dependencies_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
if(commandContext.getParameters().getParameters().isEmpty()) {
EnableModel model = (EnableModel) ContextConverter.fromCommandContext(commandContext, EnableModel.class);
if (commandContext.getParameters().getParameters().isEmpty()) {
FeatureSwitchModel model = (FeatureSwitchModel) ContextConverter.fromCommandContext(commandContext, FeatureSwitchModel.class);
model.setFeatures(featureConfigService.getAllFeatures());
String response = templateService.renderTemplate("disable_features_response", model, commandContext.getGuild().getIdLong());
return channelService.sendTextToChannel(response, commandContext.getChannel())
.thenApply(aVoid -> CommandResult.fromSuccess());
MessageToSend messageToSend = templateService.renderEmbedTemplate(DISABLE_FEATURE_ALL_FEATURES_RESPONSE_TEMPLATE_KEY, model, commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(message -> CommandResult.fromIgnored());
} else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(flagKey);
featureFlagService.disableFeature(feature, commandContext.getGuild().getIdLong());
if(feature.getDependantFeatures() != null) {
List<FeatureConfig> featureDependencies = new ArrayList<>();
if (feature.getDependantFeatures() != null) {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
feature.getDependantFeatures().forEach(featureDisplay ->
featureFlagService.disableFeature(featureDisplay, server)
feature.getDependantFeatures().forEach(featureDisplay -> {
if (featureFlagService.isFeatureEnabled(featureDisplay, server)) {
featureFlagService.disableFeature(featureDisplay, server);
featureDependencies.add(featureDisplay);
}
}
);
}
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
if (featureDependencies.isEmpty()) {
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
} else {
List<String> additionalFeatures = featureDependencies
.stream()
.map(featureDef -> featureDef.getFeature().getKey()).
collect(Collectors.toList());
FeatureSwitchModel model = (FeatureSwitchModel) ContextConverter.fromCommandContext(commandContext, FeatureSwitchModel.class);
model.setFeatures(additionalFeatures);
MessageToSend messageToSend = templateService.renderEmbedTemplate(DISABLE_FEATURE_DEPENDENCIES_RESPONSE_TEMPLATE_KEY,
model, commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(message -> CommandResult.fromIgnored());
}
}
}

View File

@@ -14,19 +14,23 @@ import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel;
import dev.sheldan.abstracto.core.models.template.commands.FeatureSwitchModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
@@ -47,32 +51,52 @@ public class EnableFeature extends AbstractConditionableCommand {
@Autowired
private ServerManagementService serverManagementService;
private static final String ENABLE_FEATURE_DEPENDENCIES_RESPONSE_TEMPLATE_KEY = "enableFeature_feature_dependencies_response";
private static final String ENABLE_FEATURE_ALL_FEATURES_RESPONSE = "enableFeature_all_features_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
if(commandContext.getParameters().getParameters().isEmpty()) {
EnableModel model = (EnableModel) ContextConverter.fromCommandContext(commandContext, EnableModel.class);
FeatureSwitchModel model = (FeatureSwitchModel) ContextConverter.fromCommandContext(commandContext, FeatureSwitchModel.class);
model.setFeatures(featureConfigService.getAllFeatures());
String response = templateService.renderTemplate("enable_features_response", model, commandContext.getGuild().getIdLong());
return channelService.sendTextToChannel(response, commandContext.getChannel())
.thenApply(message -> CommandResult.fromSuccess());
MessageToSend messageToSend = templateService.renderEmbedTemplate(ENABLE_FEATURE_ALL_FEATURES_RESPONSE, model, commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(message -> CommandResult.fromIgnored());
} else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(flagKey);
String featureKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(featureKey);
AServer server = serverManagementService.loadServer(commandContext.getUserInitiatedContext().getGuild().getIdLong());
FeatureValidationResult featureSetup = featureConfigService.validateFeatureSetup(feature, server);
if(Boolean.FALSE.equals(featureSetup.getValidationResult())) {
log.info("Feature {} has failed the setup validation. Notifying user.", flagKey);
log.info("Feature {} has failed the setup validation. Notifying user.", featureKey);
channelService.sendTextToChannelNotAsync(templateService.renderTemplatable(featureSetup, commandContext.getGuild().getIdLong()),
commandContext.getChannel());
}
featureFlagService.enableFeature(feature, server);
List<FeatureConfig> featureDependencies = new ArrayList<>();
if(feature.getRequiredFeatures() != null) {
feature.getRequiredFeatures().forEach(featureDisplay -> {
log.info("Also enabling required feature {}.", featureDisplay.getFeature().getKey());
featureFlagService.enableFeature(featureDisplay, server);
if(!featureFlagService.isFeatureEnabled(featureDisplay, server)) {
featureFlagService.enableFeature(featureDisplay, server);
featureDependencies.add(featureDisplay);
}
});
}
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
if(featureDependencies.isEmpty()) {
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
} else {
List<String> additionalFeatures = featureDependencies
.stream()
.map(featureDef -> featureDef.getFeature().getKey()).
collect(Collectors.toList());
FeatureSwitchModel model = (FeatureSwitchModel) ContextConverter.fromCommandContext(commandContext, FeatureSwitchModel.class);
model.setFeatures(additionalFeatures);
MessageToSend messageToSend = templateService.renderEmbedTemplate(ENABLE_FEATURE_DEPENDENCIES_RESPONSE_TEMPLATE_KEY,
model, commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(message -> CommandResult.fromIgnored());
}
}
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.core.commands.config;
package dev.sheldan.abstracto.core.commands.config.features;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
@@ -8,6 +8,7 @@ import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.FeatureNotFoundException;

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.interactive.*;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
@@ -14,9 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@Component
@@ -61,8 +60,13 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
log.info("Performing setup of feature {} for user {} in channel {} in server {}.",
featureConfig.getFeature().getKey(), user.getUserId(), user.getChannelId(), user.getGuildId());
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelFromServerOptional(user.getGuildId(), user.getChannelId());
if(textChannelInGuild.isPresent()) {
List<String> requiredSystemConfigKeys = featureConfig.getRequiredSystemConfigKeys();
if (textChannelInGuild.isPresent()) {
Set<String> requiredSystemConfigKeys = new HashSet<>();
Set<PostTargetEnum> requiredPostTargets = new HashSet<>();
Set<SetupStep> customSetupSteps = new HashSet<>();
collectRequiredFeatureSteps(featureConfig, requiredSystemConfigKeys, requiredPostTargets, customSetupSteps, new HashSet<>());
List<SetupExecution> steps = new ArrayList<>();
requiredSystemConfigKeys.forEach(s -> {
log.debug("Feature requires system config key {}.", s);
@@ -73,7 +77,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
.build();
steps.add(execution);
});
featureConfig.getRequiredPostTargets().forEach(postTargetEnum -> {
requiredPostTargets.forEach(postTargetEnum -> {
log.debug("Feature requires post target {}.", postTargetEnum.getKey());
SetupExecution execution = SetupExecution
.builder()
@@ -82,7 +86,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
.build();
steps.add(execution);
});
featureConfig.getCustomSetupSteps().forEach(setupStep -> {
customSetupSteps.forEach(setupStep -> {
log.debug("Feature requires custom setup step {}.", setupStep.getClass().getName());
SetupExecution execution = SetupExecution
.builder()
@@ -94,7 +98,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
for (int i = 0; i < steps.size(); i++) {
SetupExecution setupExecution = steps.get(i);
setupExecution.getParameter().setPreviousMessageId(initialMessageId);
if(i < steps.size() - 1) {
if (i < steps.size() - 1) {
setupExecution.setNextStep(steps.get(i + 1));
}
}
@@ -117,7 +121,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
@Override
public CompletableFuture<Void> executeFeatureSetup(FeatureConfig featureConfig, List<SetupExecution> steps, AServerChannelUserId user, List<DelayedActionConfig> delayedActionConfigs) {
if(!steps.isEmpty()) {
if (!steps.isEmpty()) {
SetupExecution nextStep = steps.get(0);
return executeStep(user, nextStep, delayedActionConfigs, featureConfig);
} else {
@@ -130,10 +134,10 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
private CompletableFuture<Void> executeStep(AServerChannelUserId aUserInAServer, SetupExecution execution, List<DelayedActionConfig> delayedActionConfigs, FeatureConfig featureConfig) {
log.debug("Executing step {} in server {} in channel {} for user {}.", execution.getStep().getClass(), aUserInAServer.getGuildId(), aUserInAServer.getChannelId(), aUserInAServer.getUserId());
return execution.getStep().execute(aUserInAServer, execution.getParameter()).thenAccept(setpResult -> {
if(setpResult.getResult().equals(SetupStepResultType.SUCCESS)) {
if (setpResult.getResult().equals(SetupStepResultType.SUCCESS)) {
log.info("Step {} in server {} has been executed successfully. Proceeding.", execution.getStep().getClass(), aUserInAServer.getGuildId());
delayedActionConfigs.addAll(setpResult.getDelayedActionConfigList());
if(execution.getNextStep() != null) {
if (execution.getNextStep() != null) {
log.debug("Executing next step {}.", execution.getNextStep().getStep().getClass());
executeStep(aUserInAServer, execution.getNextStep(), delayedActionConfigs, featureConfig);
} else {
@@ -156,7 +160,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
public void showExceptionMessage(Throwable throwable, AServerChannelUserId aServerChannelUserId) {
Optional<TextChannel> channelOptional = channelService.getTextChannelFromServerOptional(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
memberService.getMemberInServerAsync(aServerChannelUserId.getGuildId(), aServerChannelUserId.getUserId()).thenAccept(member ->
channelOptional.ifPresent(textChannel -> exceptionService.reportExceptionToChannel(throwable, textChannel, member))
channelOptional.ifPresent(textChannel -> exceptionService.reportExceptionToChannel(throwable, textChannel, member))
).exceptionally(innserThrowable -> {
log.error("Failed to report exception message for exception {} for user {} in channel {} in server {}.", throwable, aServerChannelUserId.getUserId(), aServerChannelUserId.getChannelId(), aServerChannelUserId.getGuildId(), innserThrowable);
return null;
@@ -178,7 +182,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
log.debug("Notifying user {} in channel {} in server {} about completion of setup for feature {}.",
aServerChannelUserId.getUserId(), aServerChannelUserId.getChannelId(), aServerChannelUserId.getGuildId(), featureConfig.getFeature().getKey());
String templateKey;
if(result.getResult().equals(SetupStepResultType.CANCELLED)) {
if (result.getResult().equals(SetupStepResultType.CANCELLED)) {
templateKey = FEATURE_SETUP_CANCELLATION_NOTIFICATION_TEMPLATE;
} else {
templateKey = FEATURE_SETUP_COMPLETION_NOTIFICATION_TEMPLATE;
@@ -202,4 +206,19 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
aServerChannelUserId.getUserId(), aServerChannelUserId.getChannelId(), aServerChannelUserId.getGuildId(), featureConfig.getFeature().getKey());
notifyUserWithTemplate(aServerChannelUserId, featureConfig, FEATURE_SETUP_CANCELLATION_NOTIFICATION_TEMPLATE);
}
private void collectRequiredFeatureSteps(FeatureConfig featureConfig, Set<String> requiredSystemConfigKeys,
Set<PostTargetEnum> requiredPostTargets, Set<SetupStep> customSetupSteps,
Set<String> coveredFeatures) {
if (coveredFeatures.contains(featureConfig.getFeature().getKey())) {
return;
}
coveredFeatures.add(featureConfig.getFeature().getKey());
requiredSystemConfigKeys.addAll(featureConfig.getRequiredSystemConfigKeys());
requiredPostTargets.addAll(featureConfig.getRequiredPostTargets());
customSetupSteps.addAll(featureConfig.getCustomSetupSteps());
featureConfig.getRequiredFeatures()
.forEach(requiredFeature -> collectRequiredFeatureSteps(requiredFeature, requiredSystemConfigKeys, requiredPostTargets, customSetupSteps, coveredFeatures));
}
}

View File

@@ -10,6 +10,6 @@ import java.util.List;
@Getter
@Setter
@SuperBuilder
public class EnableModel extends UserInitiatedServerContext {
public class FeatureSwitchModel extends UserInitiatedServerContext {
private List<String> features;
}