SEBSERV-260 Delete exam config
This commit is contained in:
parent
ecc5398147
commit
34fe5ba43f
8 changed files with 172 additions and 4 deletions
|
@ -548,6 +548,11 @@ public enum ActionDefinition {
|
|||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
||||
ActionCategory.FORM),
|
||||
|
||||
SEB_EXAM_CONFIG_DELETE(
|
||||
new LocTextKey("sebserver.examconfig.action.delete"),
|
||||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
|
||||
ActionCategory.FORM),
|
||||
SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY(
|
||||
new LocTextKey("sebserver.overall.action.modify.cancel"),
|
||||
ImageIcon.CANCEL,
|
||||
|
|
|
@ -21,9 +21,12 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
|
||||
|
@ -32,6 +35,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.Configuration
|
|||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.content.exam.ExamList;
|
||||
|
@ -46,9 +50,11 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.DeleteExamConfiguration;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportConfigKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
|
||||
|
@ -106,6 +112,16 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.error.unexpected");
|
||||
static final LocTextKey FORM_IMPORT_ERROR_FILE_SELECTION =
|
||||
new LocTextKey("sebserver.examconfig.message.error.file");
|
||||
static final LocTextKey CONFIRM_DELETE =
|
||||
new LocTextKey("sebserver.examconfig.message.confirm.delete");
|
||||
static final LocTextKey DELETE_CONFIRM_TITLE =
|
||||
new LocTextKey("sebserver.dialog.confirm.title");
|
||||
private final static LocTextKey DELETE_ERROR_CONSISTENCY =
|
||||
new LocTextKey("sebserver.examconfig.message.consistency.error");
|
||||
private final static LocTextKey DELETE_ERROR_DEPENDENCY =
|
||||
new LocTextKey("sebserver.examconfig.message.delete.partialerror");
|
||||
private final static LocTextKey DELETE_CONFIRM =
|
||||
new LocTextKey("sebserver.examconfig.message.delete.confirm");
|
||||
|
||||
private final PageService pageService;
|
||||
private final RestService restService;
|
||||
|
@ -217,9 +233,14 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_DELETE)
|
||||
.withExec(this::deleteConfiguration)
|
||||
.publishIf(() -> writeGrant && examConfig.status != ConfigurationStatus.IN_USE && isReadonly)
|
||||
|
||||
.newAction((!modifyGrant || examConfig.status == ConfigurationStatus.IN_USE)
|
||||
? ActionDefinition.SEB_EXAM_CONFIG_VIEW
|
||||
: ActionDefinition.SEB_EXAM_CONFIG_MODIFY)
|
||||
|
@ -320,6 +341,51 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
}
|
||||
}
|
||||
|
||||
private PageAction deleteConfiguration(final PageAction action) {
|
||||
final ConfigurationNode configNode = this.restService
|
||||
.getBuilder(GetExamConfigNode.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Result<EntityProcessingReport> call = this.restService
|
||||
.getBuilder(DeleteExamConfiguration.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||
.call();
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
|
||||
if (call.hasError()) {
|
||||
final Exception error = call.getError();
|
||||
if (error instanceof RestCallError) {
|
||||
final APIMessage message = ((RestCallError) error)
|
||||
.getAPIMessages()
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (message != null && ErrorMessage.INTEGRITY_VALIDATION.isOf(message)) {
|
||||
pageContext.publishPageMessage(new PageMessageException(DELETE_ERROR_CONSISTENCY));
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final EntityProcessingReport report = call.getOrThrow();
|
||||
final String configName = configNode.toName().name;
|
||||
if (report.getErrors().isEmpty()) {
|
||||
pageContext.publishPageMessage(DELETE_CONFIRM_TITLE, new LocTextKey(DELETE_CONFIRM.name, configName));
|
||||
} else {
|
||||
pageContext.publishPageMessage(
|
||||
DELETE_CONFIRM_TITLE,
|
||||
new LocTextKey(DELETE_ERROR_DEPENDENCY.name, configName,
|
||||
report.getErrors().iterator().next().getErrorMessage().systemMessage));
|
||||
}
|
||||
|
||||
return this.pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_LIST)
|
||||
.create();
|
||||
}
|
||||
|
||||
private PageAction showExamAction(final EntityTable<ExamConfigurationMap> table) {
|
||||
return this.pageService.pageActionBuilder(table.getPageContext())
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
|
|
|
@ -123,7 +123,9 @@ public class ExamDeletePopup {
|
|||
|
||||
try {
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final Exam examToDelete = this.pageService.getRestService().getBuilder(GetExam.class)
|
||||
final Exam examToDelete = this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class DeleteExamConfiguration extends RestCall<EntityProcessingReport> {
|
||||
|
||||
public DeleteExamConfiguration() {
|
||||
super(new TypeKey<>(
|
||||
CallType.DELETE,
|
||||
EntityType.CONFIGURATION_NODE,
|
||||
new TypeReference<EntityProcessingReport>() {
|
||||
}),
|
||||
HttpMethod.DELETE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.CONFIGURATION_NODE_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -50,7 +50,7 @@ public interface ExamConfigurationMapDAO extends
|
|||
Result<Long> getUserConfigurationNodeId(final Long examId, final String userId);
|
||||
|
||||
/** Get a list of all ConfigurationNode identifiers of configurations that currently are attached to a given Exam
|
||||
*
|
||||
*
|
||||
* @param examId the Exam identifier
|
||||
* @return Result refers to a list of ConfigurationNode identifiers or refer to an error if happened */
|
||||
Result<Collection<Long>> getConfigurationNodeIds(Long examId);
|
||||
|
@ -67,4 +67,6 @@ public interface ExamConfigurationMapDAO extends
|
|||
* @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */
|
||||
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
|
||||
|
||||
Result<Boolean> checkForDeletion(Long configurationNodeId);
|
||||
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
|
@ -372,6 +373,34 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.flatMap(this::getExamIdsForConfigNodeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Boolean> checkForDeletion(final Long configurationNodeId) {
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample()
|
||||
.where(
|
||||
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
|
||||
isEqualTo(configurationNodeId))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.filter(rec -> !isExamFinished(rec.getExamId()))
|
||||
.findFirst()
|
||||
.isEmpty());
|
||||
}
|
||||
|
||||
private boolean isExamFinished(final Long examId) {
|
||||
try {
|
||||
return this.examRecordMapper.countByExample()
|
||||
.where(ExamRecordDynamicSqlSupport.id, isEqualTo(examId))
|
||||
.and(ExamRecordDynamicSqlSupport.status, isEqualTo(ExamStatus.FINISHED.name()))
|
||||
.build()
|
||||
.execute() >= 1;
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to check exam status for exam: {}", examId, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Result<ExamConfigurationMapRecord> recordById(final Long id) {
|
||||
return Result.tryCatch(() -> {
|
||||
final ExamConfigurationMapRecord record = this.examConfigurationMapRecordMapper
|
||||
|
|
|
@ -37,6 +37,8 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
|
||||
|
@ -63,6 +65,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServe
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
||||
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.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.OrientationDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
|
@ -80,6 +83,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ConfigurationDAO configurationDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final ViewDAO viewDAO;
|
||||
private final OrientationDAO orientationDAO;
|
||||
private final ExamConfigService sebExamConfigService;
|
||||
|
@ -90,6 +94,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
final BulkActionService bulkActionService,
|
||||
final ConfigurationNodeDAO entityDAO,
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ConfigurationDAO configurationDAO,
|
||||
|
@ -107,6 +112,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
|
||||
this.configurationDAO = configurationDAO;
|
||||
this.configurationNodeDAO = entityDAO;
|
||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.viewDAO = viewDAO;
|
||||
this.orientationDAO = orientationDAO;
|
||||
this.sebExamConfigService = sebExamConfigService;
|
||||
|
@ -508,6 +514,19 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ConfigurationNode> validForDelete(final ConfigurationNode entity) {
|
||||
return Result.tryCatch(() -> {
|
||||
if (!this.examConfigurationMapDAO.checkForDeletion(entity.id).getOr(false)) {
|
||||
throw new APIMessageException(
|
||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.of("Exam configuration has references to at least one upcoming or running exam."));
|
||||
}
|
||||
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ConfigurationNode> notifyCreated(final ConfigurationNode entity) {
|
||||
return super.notifyCreated(entity)
|
||||
|
|
|
@ -805,11 +805,11 @@ sebserver.examconfig.list.actions=
|
|||
sebserver.examconfig.list.empty=There is currently no Exam configuration available. Please create a new one
|
||||
sebserver.examconfig.info.pleaseSelect=At first please select an Exam Configuration from the list
|
||||
sebserver.examconfig.list.action.no.modify.privilege=No Access: An Exam Configuration from other institution cannot be modified.
|
||||
|
||||
sebserver.examconfig.action.list.new=Add Exam Configuration
|
||||
sebserver.examconfig.action.list.view=View Exam Configuration
|
||||
|
||||
sebserver.examconfig.action.list.modify.properties=Edit Exam Configuration
|
||||
sebserver.examconfig.action.delete=Delete Exam Configuration
|
||||
sebserver.examconfig.action.modify=Edit SEB Settings
|
||||
sebserver.examconfig.action.view=View SEB Settings
|
||||
sebserver.examconfig.action.modify.properties=Edit Exam Configuration
|
||||
|
@ -838,6 +838,11 @@ sebserver.examconfig.action.import.auto-publish=Publish
|
|||
sebserver.examconfig.action.import.auto-publish.tooltip=Try to automatically publish the imported changes
|
||||
sebserver.examconfig.action.state-change.confirm=This configuration is already attached to an exam.<br/>Please note that changing an attached configuration will take effect on the exam when the configuration changes are saved<br/><br/>Are you sure to change this configuration to an editable state?
|
||||
sebserver.examconfig.message.error.file=Please select a valid SEB Exam Configuration File
|
||||
sebserver.examconfig.message.confirm.delete=This will completely delete the exam configuration.<br/><br/>Are you sure you want to delete this exam configuration?
|
||||
sebserver.examconfig.message.consistency.error=The exam configuration cannot be deleted since it is used by at least one running or upcoming exam.<br/>Please remove the exam configuration from running and upcoming exams first.
|
||||
sebserver.examconfig.message.delete.confirm=The exam configuration ({0}) was successfully deleted.
|
||||
sebserver.examconfig.message.delete.partialerror=The exam configuration ({0}) was deleted but there where some dependency errors:<br/><br/>{1}
|
||||
|
||||
|
||||
sebserver.examconfig.form.title.new=Add Exam Configuration
|
||||
sebserver.examconfig.form.title=Exam Configuration
|
||||
|
|
Loading…
Reference in a new issue