SEBSERV-212 prevent double-creation of exam for a quiz on the same
institution. Do also not forward and load the existing one. This seems to cause some trouble when be done sometimes.
This commit is contained in:
parent
eb7042acf6
commit
086bc5ef3b
7 changed files with 73 additions and 32 deletions
|
@ -12,6 +12,7 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.joda.time.DateTime;
|
||||
|
@ -100,6 +101,8 @@ public class QuizLookupList implements TemplateComposer {
|
|||
new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
|
||||
private final static LocTextKey TEXT_KEY_CONFIRM_EXISTING =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.import.existing.confirm");
|
||||
private final static LocTextKey TEXT_KEY_EXISTING =
|
||||
new LocTextKey("sebserver.quizdiscovery.quiz.import.existing");
|
||||
|
||||
private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX =
|
||||
"sebserver.quizdiscovery.quiz.details.additional.";
|
||||
|
@ -147,6 +150,7 @@ public class QuizLookupList implements TemplateComposer {
|
|||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
final Long institutionId = currentUser.get().institutionId;
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = this.widgetFactory.defaultPageLayout(
|
||||
|
@ -242,10 +246,10 @@ public class QuizLookupList implements TemplateComposer {
|
|||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT)
|
||||
.withConfirm(importQuizConfirm(table, restService))
|
||||
.withConfirm(importQuizConfirm(institutionId, table, restService))
|
||||
.withSelect(
|
||||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
||||
action -> this.importQuizData(action, table),
|
||||
action -> this.importQuizData(institutionId, action, table, restService),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.publishIf(() -> examGrant.im(), false);
|
||||
}
|
||||
|
@ -257,6 +261,7 @@ public class QuizLookupList implements TemplateComposer {
|
|||
}
|
||||
|
||||
private Function<PageAction, LocTextKey> importQuizConfirm(
|
||||
final Long institutionId,
|
||||
final EntityTable<QuizData> table,
|
||||
final RestService restService) {
|
||||
|
||||
|
@ -264,12 +269,15 @@ public class QuizLookupList implements TemplateComposer {
|
|||
action.getSingleSelection();
|
||||
final QuizData selectedROWData = table.getSingleSelectedROWData();
|
||||
|
||||
final Collection<EntityKey> existingImports = restService.getBuilder(CheckExamImported.class)
|
||||
final Collection<Long> existingImports = restService.getBuilder(CheckExamImported.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, selectedROWData.id)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.map(key -> Long.valueOf(key.modelId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (existingImports != null && !existingImports.isEmpty()) {
|
||||
if (existingImports != null && !existingImports.contains(institutionId)) {
|
||||
return TEXT_KEY_CONFIRM_EXISTING;
|
||||
} else {
|
||||
return null;
|
||||
|
@ -278,8 +286,10 @@ public class QuizLookupList implements TemplateComposer {
|
|||
}
|
||||
|
||||
private PageAction importQuizData(
|
||||
final Long institutionId,
|
||||
final PageAction action,
|
||||
final EntityTable<QuizData> table) {
|
||||
final EntityTable<QuizData> table,
|
||||
final RestService restService) {
|
||||
|
||||
action.getSingleSelection();
|
||||
final QuizData selectedROWData = table.getSingleSelectedROWData();
|
||||
|
@ -291,6 +301,18 @@ public class QuizLookupList implements TemplateComposer {
|
|||
}
|
||||
}
|
||||
|
||||
final Collection<Long> existingImports = restService.getBuilder(CheckExamImported.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, selectedROWData.id)
|
||||
.call()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.map(key -> Long.valueOf(key.modelId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (existingImports.contains(institutionId)) {
|
||||
throw new PageMessageException(TEXT_KEY_EXISTING);
|
||||
}
|
||||
|
||||
return action
|
||||
.withEntityKey(action.getSingleSelection())
|
||||
.withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
|
||||
|
|
|
@ -232,7 +232,7 @@ public class UserAccountList implements TemplateComposer {
|
|||
.newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY)
|
||||
.withExec(this.pageService.activationToggleActionFunction(table, EMPTY_SELECTION_TEXT_KEY))
|
||||
.withConfirm(this.pageService.confirmDeactivation(table))
|
||||
.publishIf(() -> userGrant.m(), false);
|
||||
.publishIf(() -> userGrant.im(), false);
|
||||
}
|
||||
|
||||
private PageAction editAction(final PageAction pageAction) {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.webservice.servicelayer.dao;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
|
||||
public class DuplicateResourceException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 2935680103812281185L;
|
||||
|
||||
/** The entity key of the resource that was requested */
|
||||
public final EntityKey entityKey;
|
||||
|
||||
public DuplicateResourceException(final EntityType entityType, final String modelId) {
|
||||
super("Resource " + entityType + " with ID: " + modelId + " already exists");
|
||||
this.entityKey = new EntityKey(modelId, entityType);
|
||||
}
|
||||
|
||||
public DuplicateResourceException(final EntityType entityType, final String modelId, final Throwable cause) {
|
||||
super("Resource " + entityType + " with ID: " + modelId + " not found", cause);
|
||||
this.entityKey = new EntityKey(modelId, entityType);
|
||||
}
|
||||
|
||||
}
|
|
@ -44,7 +44,11 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
* happened */
|
||||
Result<Collection<Long>> allIdsOfInstitution(Long institutionId);
|
||||
|
||||
Result<Collection<Long>> allByQuizId(String quizId);
|
||||
/** Get all institution ids for that a specified exam for given quiz id already exists
|
||||
*
|
||||
* @param quizId The quiz or external identifier of the exam (LMS)
|
||||
* @return Result refer to a collection of primary keys of the institutions or to an error when happened */
|
||||
Result<Collection<Long>> allInstitutionIdsByQuizId(String quizId);
|
||||
|
||||
/** Updates the exam status for specified exam
|
||||
*
|
||||
|
|
|
@ -59,6 +59,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDyn
|
|||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DuplicateResourceException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||
|
@ -139,7 +140,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<Long>> allByQuizId(final String quizId) {
|
||||
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examRecordMapper.selectByExample()
|
||||
.where(
|
||||
|
@ -151,7 +152,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(rec -> rec.getId())
|
||||
.map(rec -> rec.getInstitutionId())
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
@ -331,23 +332,9 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
// used to save instead of create a new one
|
||||
if (records != null && records.size() > 0) {
|
||||
final ExamRecord examRecord = records.get(0);
|
||||
// if the same institution tries to import an exam that already exists
|
||||
// open the existing. otherwise create new one if requested
|
||||
// if the same institution tries to import an exam that already exists throw an error
|
||||
if (exam.institutionId.equals(examRecord.getInstitutionId())) {
|
||||
final ExamRecord newRecord = new ExamRecord(
|
||||
examRecord.getId(),
|
||||
null, null, null, null, null,
|
||||
(exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(),
|
||||
null, // quitPassword
|
||||
null, // browser keys
|
||||
null, // status
|
||||
null, // lmsSebRestriction (deprecated)
|
||||
null, // updating
|
||||
null, // lastUpdate
|
||||
BooleanUtils.toIntegerObject(exam.active));
|
||||
|
||||
this.examRecordMapper.updateByPrimaryKeySelective(newRecord);
|
||||
return this.examRecordMapper.selectByPrimaryKey(examRecord.getId());
|
||||
throw new DuplicateResourceException(EntityType.EXAM, exam.externalId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -228,10 +228,10 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||
|
||||
checkReadPrivilege(institutionId);
|
||||
return this.examDAO.allByQuizId(modelId)
|
||||
return this.examDAO.allInstitutionIdsByQuizId(modelId)
|
||||
.map(ids -> ids
|
||||
.stream()
|
||||
.map(id -> new EntityKey(id, EntityType.EXAM))
|
||||
.map(id -> new EntityKey(id, EntityType.INSTITUTION))
|
||||
.collect(Collectors.toList()))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
@ -256,10 +256,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
.checkExamConsistency(modelId)
|
||||
.getOrThrow();
|
||||
|
||||
if (includeRestriction) {
|
||||
// TODO include seb restriction check and status
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -380,6 +380,7 @@ sebserver.quizdiscovery.action.import=Import as Exam
|
|||
sebserver.quizdiscovery.quiz.import.out.dated=The Selected LMS exam is already finished and can't be imported
|
||||
sebserver.quizdiscovery.action.details=Show LMS Exam Details
|
||||
sebserver.quizdiscovery.quiz.import.existing.confirm=This course was already imported and importing it twice may lead to<br/> unexpected behavior within automated SEB restriction on LMS.<br/><br/> Do you want to import this course as exam anyway?
|
||||
sebserver.quizdiscovery.quiz.import.existing=This course was already imported as an exam.<br/> You will find it in the Exam section.
|
||||
|
||||
sebserver.quizdiscovery.quiz.details.title=LMS Exam Details
|
||||
sebserver.quizdiscovery.quiz.details.institution=Institution
|
||||
|
|
Loading…
Reference in a new issue