From f191d59495c603d5cd3c13eb0ecaa940cb2433b0 Mon Sep 17 00:00:00 2001 From: Nadim Ritter Date: Wed, 7 Feb 2024 09:31:26 +0100 Subject: [PATCH] SEBSERV-398 - add bulk delete for exam config --- .../gui/content/action/ActionDefinition.java | 6 + .../SEBExamConfigBatchDeletePopup.java | 132 ++++++++++++++++++ .../content/configs/SEBExamConfigList.java | 19 ++- .../examconfig/GetExamConfigNodesByIds.java | 32 +++++ .../bulkaction/impl/ExamConfigDelete.java | 82 +++++++++++ src/main/resources/messages.properties | 3 + 6 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchDeletePopup.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodesByIds.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigDelete.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index b0416cb0..eca07f00 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -715,6 +715,12 @@ public enum ActionDefinition { PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST, ActionCategory.SEB_EXAM_CONFIG_LIST), + SEB_EXAM_CONFIG_BULK_DELETE( + new LocTextKey("sebserver.examconfig.list.batch.action.delete"), + ImageIcon.DELETE, + PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST, + ActionCategory.SEB_EXAM_CONFIG_LIST), + SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST( new LocTextKey("sebserver.examconfig.action.list.modify.properties"), ImageIcon.EDIT, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchDeletePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchDeletePopup.java new file mode 100644 index 00000000..bb8ca940 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchDeletePopup.java @@ -0,0 +1,132 @@ +package ch.ethz.seb.sebserver.gui.content.configs; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.BatchAction; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; +import ch.ethz.seb.sebserver.gui.service.ResourceService; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.AbstractBatchActionWizard; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageService; +import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNodesByIds; +import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; +import org.apache.tomcat.util.buf.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +@Lazy +@Component +@GuiProfile +public class SEBExamConfigBatchDeletePopup extends AbstractBatchActionWizard { + + private static final Logger log = LoggerFactory.getLogger(SEBExamConfigBatchDeletePopup.class); + + private final static LocTextKey FORM_TITLE = new LocTextKey("sebserver.examconfig.list.batch.delete.title"); + private final static LocTextKey FORM_INFO = new LocTextKey("sebserver.examconfig.list.batch.delete.info"); + private final static LocTextKey ACTION_DO_DELETE = new LocTextKey("sebserver.examconfig.list.batch.action.delete"); + + protected SEBExamConfigBatchDeletePopup( + final PageService pageService, + final ServerPushService serverPushService) { + + super(pageService, serverPushService); + } + + @Override + protected LocTextKey getTitle() { + return FORM_TITLE; + } + + @Override + protected LocTextKey getBatchActionInfo() { + return FORM_INFO; + } + + @Override + protected LocTextKey getBatchActionTitle() { + return ACTION_DO_DELETE; + } + + @Override + protected BatchActionType getBatchActionType() { + return BatchActionType.EXAM_CONFIG_DELETE; + } + + @Override + protected Supplier createResultPageSupplier(PageContext pageContext, FormHandle formHandle) { + return () -> pageContext; + } + + @Override + protected void extendBatchActionRequest(PageContext pageContext, RestCall.RestCallBuilder batchActionRequestBuilder) { + // Nothing to do here + } + + @Override + protected FormBuilder buildSpecificFormFields(PageContext formContext, FormBuilder formHead, boolean readonly) { + return formHead; + } + + @Override + protected void processUpdateListAction(PageContext formContext) { + this.pageService.executePageAction(this.pageService.pageActionBuilder(formContext) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_LIST) + .create()); + } + + @Override + protected void applySelectionList( + final PageContext formContext, + final Set multiSelection) { + + final ResourceService resourceService = this.pageService.getResourceService(); + + final String ids = StringUtils.join( + multiSelection.stream().map(EntityKey::getModelId).collect(Collectors.toList()), + Constants.LIST_SEPARATOR_CHAR); + + final RestService restService = this.pageService.getRestService(); + final List selected = new ArrayList<>(restService.getBuilder(GetExamConfigNodesByIds.class) + .withQueryParam(API.PARAM_MODEL_ID_LIST, ids) + .call() + .getOr(Collections.emptyList())); + + selected.sort((examConfig1, examConfig2) -> examConfig1.name.compareTo(examConfig2.name)); + + this.pageService.staticListTableBuilder(selected, EntityType.CONFIGURATION_NODE) + .withPaging(10) + .withColumn(new ColumnDefinition<>( + Domain.CONFIGURATION_NODE.ATTR_NAME, + SEBExamConfigList.NAME_TEXT_KEY, + ConfigurationNode::getName)) + + .withColumn(new ColumnDefinition<>( + Domain.CONFIGURATION_NODE.ATTR_STATUS, + SEBExamConfigList.STATUS_TEXT_KEY, + resourceService::localizedExamConfigStatusName) + ) + + .compose(formContext); + } +} \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java index 674f7c6f..9225b93a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigList.java @@ -53,11 +53,11 @@ public class SEBExamConfigList implements TemplateComposer { new LocTextKey("sebserver.examconfig.list.title"); private static final LocTextKey INSTITUTION_TEXT_KEY = new LocTextKey("sebserver.examconfig.list.column.institution"); - private static final LocTextKey NAME_TEXT_KEY = + public static final LocTextKey NAME_TEXT_KEY = new LocTextKey("sebserver.examconfig.list.column.name"); private static final LocTextKey DESCRIPTION_TEXT_KEY = new LocTextKey("sebserver.examconfig.list.column.description"); - private static final LocTextKey STATUS_TEXT_KEY = + public static final LocTextKey STATUS_TEXT_KEY = new LocTextKey("sebserver.examconfig.list.column.status"); private static final LocTextKey TEMPLATE_TEXT_KEY = new LocTextKey("sebserver.examconfig.list.column.template"); @@ -81,6 +81,8 @@ public class SEBExamConfigList implements TemplateComposer { private final SEBExamConfigCreationPopup sebExamConfigCreationPopup; private final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup; private final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup; + private final SEBExamConfigBatchDeletePopup sebExamConfigBatchDeletePopup; + private final CurrentUser currentUser; private final ResourceService resourceService; private final int pageSize; @@ -91,6 +93,7 @@ public class SEBExamConfigList implements TemplateComposer { final SEBExamConfigCreationPopup sebExamConfigCreationPopup, final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup, final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup, + final SEBExamConfigBatchDeletePopup sebExamConfigBatchDeletePopup, @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; @@ -98,6 +101,7 @@ public class SEBExamConfigList implements TemplateComposer { this.sebExamConfigCreationPopup = sebExamConfigCreationPopup; this.sebExamConfigBatchStateChangePopup = sebExamConfigBatchStateChangePopup; this.sebExamConfigBatchResetToTemplatePopup = sebExamConfigBatchResetToTemplatePopup; + this.sebExamConfigBatchDeletePopup = sebExamConfigBatchDeletePopup; this.currentUser = pageService.getCurrentUser(); this.resourceService = pageService.getResourceService(); this.pageSize = pageSize; @@ -188,7 +192,8 @@ public class SEBExamConfigList implements TemplateComposer { ActionDefinition.SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST, ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST, ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE, - ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE)) + ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE, + ActionDefinition.SEB_EXAM_CONFIG_BULK_DELETE)) .compose(pageContext.copyOf(content)); @@ -248,6 +253,14 @@ public class SEBExamConfigList implements TemplateComposer { .noEventPropagation() .publishIf(() -> !isLight && examConfigGrant.im(), false) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_DELETE) + .withSelect( + configTable::getMultiSelection, + this.sebExamConfigBatchDeletePopup.popupCreationFunction(pageContext), + EMPTY_SELECTION_TEXT_KEY) + .noEventPropagation() + .publishIf(() -> !isLight && examConfigGrant.im(), false) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG) .withSelect( configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodesByIds.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodesByIds.java new file mode 100644 index 00000000..2569f357 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodesByIds.java @@ -0,0 +1,32 @@ +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import com.fasterxml.jackson.core.type.TypeReference; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +@Lazy +@Component +@GuiProfile +public class GetExamConfigNodesByIds extends RestCall> { + + public GetExamConfigNodesByIds() { + super(new TypeKey<>( + CallType.GET_LIST, + EntityType.CONFIGURATION_NODE, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.CONFIGURATION_NODE_ENDPOINT + API.LIST_PATH_SEGMENT + ); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigDelete.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigDelete.java new file mode 100644 index 00000000..4d213e70 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigDelete.java @@ -0,0 +1,82 @@ +package ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl; + +import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType; +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; +import ch.ethz.seb.sebserver.gbl.model.BatchAction; +import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.stream.Collectors; + +@Lazy +@Component +@WebServiceProfile +public class ExamConfigDelete implements BatchActionExec { + + private final ConfigurationNodeDAO configurationNodeDAO; + private final AuthorizationService authorizationService; + private final ExamConfigurationMapDAO examConfigurationMapDAO; + + public ExamConfigDelete( + final ExamConfigService sebExamConfigService, + final ConfigurationNodeDAO configurationNodeDAO, + final AuthorizationService authorizationService, + final ExamConfigurationMapDAO examConfigurationMapDAO) { + + this.configurationNodeDAO = configurationNodeDAO; + this.authorizationService = authorizationService; + this.examConfigurationMapDAO = examConfigurationMapDAO; + } + + @Override + public BatchActionType actionType() { + return BatchActionType.EXAM_CONFIG_DELETE; + } + + @Override + public APIMessage checkConsistency(final Map actionAttributes) { + // no additional check here + return null; + } + + @Override + public Result doSingleAction(final String modelId, final BatchAction batchAction) { + return this.configurationNodeDAO + .byModelId(modelId) + .flatMap(examConfig -> this.authorizationService.check(PrivilegeType.MODIFY, examConfig)) + .flatMap(examConfig -> checkDeletionRequirements(examConfig)) + .flatMap(examConfig -> this.configurationNodeDAO.delete(new HashSet<>(Arrays.asList(new EntityKey( + modelId, + EntityType.CONFIGURATION_NODE)))) + ) + .map(res -> res.stream().collect(Collectors.toList()).get(0)); + } + + private Result checkDeletionRequirements(ConfigurationNode examConfig){ + Result isNotActive = this.examConfigurationMapDAO.checkNoActiveExamReferences(examConfig.id); + if(!isNotActive.getOrThrow()){ + return Result.ofError(new APIMessageException( + APIMessage.ErrorMessage.INTEGRITY_VALIDATION + .of("Exam Configuration has active Exam references") + )); + } + + return Result.of(examConfig); + } + +} \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 16744fcc..ada81177 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1097,6 +1097,9 @@ sebserver.examconfig.list.batch.action.status=Target Status sebserver.examconfig.list.action.reset=Reset To Template Settings sebserver.examconfig.list.batch.reset.title=Reset To Template Settings sebserver.examconfig.list.batch.action.reset=Reset All +sebserver.examconfig.list.batch.action.delete=Delete +sebserver.examconfig.list.batch.delete.title=Delete Selected Exam Configurations +sebserver.examconfig.list.batch.delete.info=Please Note:
    This deletes the exam configuration in SEB Server
     sebserver.examconfig.list.batch.action.reset.info=This action process a reset of the SEB settings to its template settings for all selected configurations.
Please note this will be done only for those that has an origin template and are not in state "Used".
Please note also that this action tries to directly publish the changes and if not possible (active SEB client connections) it will report an error even if the settings has been reseted. sebserver.examconfig.action.list.modify.properties=Edit Exam Configuration