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.Collection;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -100,6 +101,8 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
|
new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
|
||||||
private final static LocTextKey TEXT_KEY_CONFIRM_EXISTING =
|
private final static LocTextKey TEXT_KEY_CONFIRM_EXISTING =
|
||||||
new LocTextKey("sebserver.quizdiscovery.quiz.import.existing.confirm");
|
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 =
|
private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX =
|
||||||
"sebserver.quizdiscovery.quiz.details.additional.";
|
"sebserver.quizdiscovery.quiz.details.additional.";
|
||||||
|
@ -147,6 +150,7 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||||
final RestService restService = this.resourceService.getRestService();
|
final RestService restService = this.resourceService.getRestService();
|
||||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||||
|
final Long institutionId = currentUser.get().institutionId;
|
||||||
|
|
||||||
// content page layout with title
|
// content page layout with title
|
||||||
final Composite content = this.widgetFactory.defaultPageLayout(
|
final Composite content = this.widgetFactory.defaultPageLayout(
|
||||||
|
@ -242,10 +246,10 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
.publish(false)
|
.publish(false)
|
||||||
|
|
||||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT)
|
.newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT)
|
||||||
.withConfirm(importQuizConfirm(table, restService))
|
.withConfirm(importQuizConfirm(institutionId, table, restService))
|
||||||
.withSelect(
|
.withSelect(
|
||||||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
||||||
action -> this.importQuizData(action, table),
|
action -> this.importQuizData(institutionId, action, table, restService),
|
||||||
EMPTY_SELECTION_TEXT)
|
EMPTY_SELECTION_TEXT)
|
||||||
.publishIf(() -> examGrant.im(), false);
|
.publishIf(() -> examGrant.im(), false);
|
||||||
}
|
}
|
||||||
|
@ -257,6 +261,7 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function<PageAction, LocTextKey> importQuizConfirm(
|
private Function<PageAction, LocTextKey> importQuizConfirm(
|
||||||
|
final Long institutionId,
|
||||||
final EntityTable<QuizData> table,
|
final EntityTable<QuizData> table,
|
||||||
final RestService restService) {
|
final RestService restService) {
|
||||||
|
|
||||||
|
@ -264,12 +269,15 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
action.getSingleSelection();
|
action.getSingleSelection();
|
||||||
final QuizData selectedROWData = table.getSingleSelectedROWData();
|
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)
|
.withURIVariable(API.PARAM_MODEL_ID, selectedROWData.id)
|
||||||
.call()
|
.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;
|
return TEXT_KEY_CONFIRM_EXISTING;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -278,8 +286,10 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageAction importQuizData(
|
private PageAction importQuizData(
|
||||||
|
final Long institutionId,
|
||||||
final PageAction action,
|
final PageAction action,
|
||||||
final EntityTable<QuizData> table) {
|
final EntityTable<QuizData> table,
|
||||||
|
final RestService restService) {
|
||||||
|
|
||||||
action.getSingleSelection();
|
action.getSingleSelection();
|
||||||
final QuizData selectedROWData = table.getSingleSelectedROWData();
|
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
|
return action
|
||||||
.withEntityKey(action.getSingleSelection())
|
.withEntityKey(action.getSingleSelection())
|
||||||
.withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
|
.withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
|
||||||
|
|
|
@ -232,7 +232,7 @@ public class UserAccountList implements TemplateComposer {
|
||||||
.newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY)
|
.newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY)
|
||||||
.withExec(this.pageService.activationToggleActionFunction(table, EMPTY_SELECTION_TEXT_KEY))
|
.withExec(this.pageService.activationToggleActionFunction(table, EMPTY_SELECTION_TEXT_KEY))
|
||||||
.withConfirm(this.pageService.confirmDeactivation(table))
|
.withConfirm(this.pageService.confirmDeactivation(table))
|
||||||
.publishIf(() -> userGrant.m(), false);
|
.publishIf(() -> userGrant.im(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageAction editAction(final PageAction pageAction) {
|
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 */
|
* happened */
|
||||||
Result<Collection<Long>> allIdsOfInstitution(Long institutionId);
|
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
|
/** 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.AdditionalAttributeRecord;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
|
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.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.ExamDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||||
|
@ -139,7 +140,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Collection<Long>> allByQuizId(final String quizId) {
|
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
return this.examRecordMapper.selectByExample()
|
return this.examRecordMapper.selectByExample()
|
||||||
.where(
|
.where(
|
||||||
|
@ -151,7 +152,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
.build()
|
.build()
|
||||||
.execute()
|
.execute()
|
||||||
.stream()
|
.stream()
|
||||||
.map(rec -> rec.getId())
|
.map(rec -> rec.getInstitutionId())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -331,23 +332,9 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
// used to save instead of create a new one
|
// used to save instead of create a new one
|
||||||
if (records != null && records.size() > 0) {
|
if (records != null && records.size() > 0) {
|
||||||
final ExamRecord examRecord = records.get(0);
|
final ExamRecord examRecord = records.get(0);
|
||||||
// if the same institution tries to import an exam that already exists
|
// if the same institution tries to import an exam that already exists throw an error
|
||||||
// open the existing. otherwise create new one if requested
|
|
||||||
if (exam.institutionId.equals(examRecord.getInstitutionId())) {
|
if (exam.institutionId.equals(examRecord.getInstitutionId())) {
|
||||||
final ExamRecord newRecord = new ExamRecord(
|
throw new DuplicateResourceException(EntityType.EXAM, exam.externalId);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -228,10 +228,10 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||||
|
|
||||||
checkReadPrivilege(institutionId);
|
checkReadPrivilege(institutionId);
|
||||||
return this.examDAO.allByQuizId(modelId)
|
return this.examDAO.allInstitutionIdsByQuizId(modelId)
|
||||||
.map(ids -> ids
|
.map(ids -> ids
|
||||||
.stream()
|
.stream()
|
||||||
.map(id -> new EntityKey(id, EntityType.EXAM))
|
.map(id -> new EntityKey(id, EntityType.INSTITUTION))
|
||||||
.collect(Collectors.toList()))
|
.collect(Collectors.toList()))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
@ -256,10 +256,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
.checkExamConsistency(modelId)
|
.checkExamConsistency(modelId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
if (includeRestriction) {
|
|
||||||
// TODO include seb restriction check and status
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
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.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.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.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.title=LMS Exam Details
|
||||||
sebserver.quizdiscovery.quiz.details.institution=Institution
|
sebserver.quizdiscovery.quiz.details.institution=Institution
|
||||||
|
|
Loading…
Reference in a new issue