SEBSERV-162 create exam from template and tests
This commit is contained in:
parent
a589fd8ad4
commit
25265fdb2b
19 changed files with 721 additions and 128 deletions
|
@ -84,13 +84,15 @@ public final class Constants {
|
|||
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
public static final String TIME_ZONE_OFFSET_TAIL_FORMAT = "|ZZ";
|
||||
|
||||
//public static final String DEFAULT_DISPLAY_DATE_TIME_FORMAT = "MM-dd-yyyy HH:mm:ss";
|
||||
public static final String DEFAULT_DISPLAY_DATE_FORMAT = "MM-dd-yyyy";
|
||||
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
|
||||
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
|
||||
|
||||
public static final DateTimeFormatter STANDARD_DATE_TIME_FORMATTER = DateTimeFormat
|
||||
.forPattern(DEFAULT_DATE_TIME_FORMAT)
|
||||
.withZoneUTC();
|
||||
public static final DateTimeFormatter STANDARD_DATE_FORMATTER = DateTimeFormat
|
||||
.forPattern(DEFAULT_DATE_FORMAT)
|
||||
.withZoneUTC();
|
||||
|
||||
public static final String XML_VERSION_HEADER =
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>";
|
||||
|
|
|
@ -136,6 +136,29 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
this.configStatus = postParams.getEnum(Domain.CONFIGURATION_NODE.ATTR_STATUS, ConfigurationStatus.class);
|
||||
}
|
||||
|
||||
public ExamConfigurationMap(
|
||||
final Long institutionId,
|
||||
final Long examId,
|
||||
final Long configurationNodeId,
|
||||
final String userNames) {
|
||||
|
||||
this.id = null;
|
||||
this.institutionId = institutionId;
|
||||
this.examId = examId;
|
||||
this.examName = null;
|
||||
this.examDescription = null;
|
||||
this.examStartTime = null;
|
||||
this.examType = null;
|
||||
this.configurationNodeId = configurationNodeId;
|
||||
this.userNames = userNames;
|
||||
this.encryptSecret = null;
|
||||
this.confirmEncryptSecret = null;
|
||||
|
||||
this.configName = null;
|
||||
this.configDescription = null;
|
||||
this.configStatus = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType entityType() {
|
||||
return EntityType.EXAM_CONFIGURATION_MAP;
|
||||
|
|
|
@ -712,4 +712,12 @@ public final class Utils {
|
|||
return false; // Either timeout or unreachable or failed DNS lookup.
|
||||
}
|
||||
}
|
||||
|
||||
public static String replaceAll(final String template, final Map<String, String> values) {
|
||||
String result = template;
|
||||
for (final Map.Entry<String, String> e : values.entrySet()) {
|
||||
result = result.replace(e.getKey(), e.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
|
|||
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.ExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
|
@ -41,6 +42,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType;
|
|||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
|
@ -58,6 +60,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
|
||||
|
@ -104,6 +107,10 @@ public class ExamForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.exam.form.quizurl");
|
||||
private static final LocTextKey FORM_LMSSETUP_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.form.lmssetup");
|
||||
private static final LocTextKey FORM_EXAM_TEMPLATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.form.examTemplate");
|
||||
private static final LocTextKey FORM_EXAM_TEMPLATE_ERROR =
|
||||
new LocTextKey("sebserver.exam.form.examTemplate.error");
|
||||
|
||||
private final static LocTextKey CONSISTENCY_MESSAGE_TITLE =
|
||||
new LocTextKey("sebserver.exam.consistency.title");
|
||||
|
@ -329,6 +336,17 @@ public class ExamForm implements TemplateComposer {
|
|||
.withInputSpan(4)
|
||||
.withEmptyCellSeparation(false))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.EXAM.ATTR_EXAM_TEMPLATE_ID,
|
||||
FORM_EXAM_TEMPLATE_TEXT_KEY,
|
||||
(exam.examTemplateId == null) ? null : String.valueOf(exam.examTemplateId),
|
||||
this.resourceService::examTemplateResources)
|
||||
.withSelectionListener(form -> this.processTemplateSelection(form, formContext))
|
||||
.withLabelSpan(2)
|
||||
.withInputSpan(4)
|
||||
.withEmptyCellSpan(2)
|
||||
.mandatory(!readonly))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
FORM_TYPE_TEXT_KEY,
|
||||
|
@ -448,6 +466,28 @@ public class ExamForm implements TemplateComposer {
|
|||
}
|
||||
}
|
||||
|
||||
private void processTemplateSelection(final Form form, final PageContext context) {
|
||||
try {
|
||||
final String templateId = form.getFieldValue(Domain.EXAM.ATTR_EXAM_TEMPLATE_ID);
|
||||
if (StringUtils.isNotBlank(templateId)) {
|
||||
final ExamTemplate examTemplate = this.pageService.getRestService().getBuilder(GetExamTemplate.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, templateId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
form.setFieldValue(Domain.EXAM.ATTR_TYPE, examTemplate.examType.name());
|
||||
form.setFieldValue(
|
||||
Domain.EXAM.ATTR_SUPPORTER,
|
||||
StringUtils.join(examTemplate.supporter, Constants.LIST_SEPARATOR));
|
||||
} else {
|
||||
form.setFieldValue(Domain.EXAM.ATTR_TYPE, Exam.ExamType.UNDEFINED.name());
|
||||
form.setFieldValue(Domain.EXAM.ATTR_SUPPORTER, null);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
context.notifyError(FORM_EXAM_TEMPLATE_ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private PageAction importExam(
|
||||
final PageAction action,
|
||||
final FormHandle<Exam> formHandle,
|
||||
|
|
|
@ -12,8 +12,6 @@ import java.util.List;
|
|||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -55,8 +53,6 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
|||
@GuiProfile
|
||||
public class ExamTemplateForm implements TemplateComposer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamTemplateForm.class);
|
||||
|
||||
public static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examtemplate.form.title");
|
||||
public static final LocTextKey TITLE_NEW_TEXT_KEY =
|
||||
|
|
|
@ -75,6 +75,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
|||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateNames;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExams;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames;
|
||||
|
@ -786,4 +787,16 @@ public class ResourceService {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Tuple<String>> examTemplateResources() {
|
||||
return Stream.concat(
|
||||
Stream.of(new EntityName("", EntityType.EXAM_TEMPLATE, "")),
|
||||
this.restService.getBuilder(GetExamTemplateNames.class)
|
||||
.call()
|
||||
.onError(error -> log.warn("Failed to get exam template names: {}", error.getMessage()))
|
||||
.getOr(Collections.emptyList())
|
||||
.stream())
|
||||
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.gui.service.remote.webservice.api.exam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.EntityName;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetExamTemplateNames extends RestCall<List<EntityName>> {
|
||||
|
||||
public GetExamTemplateNames() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_NAMES,
|
||||
EntityType.EXAM_TEMPLATE,
|
||||
new TypeReference<List<EntityName>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_TEMPLATE_ENDPOINT + API.NAMES_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,8 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -55,6 +57,26 @@ public interface AdditionalAttributesDAO {
|
|||
String name,
|
||||
String value);
|
||||
|
||||
/** Use this to save an additional attributes for a specific entity.
|
||||
* If an additional attribute with specified name already exists for the specified entity
|
||||
* this updates just the value for this additional attribute. Otherwise create a new instance
|
||||
* of additional attribute with the given data
|
||||
*
|
||||
* @param type the entity type
|
||||
* @param entityId the entity identifier (primary key)
|
||||
* @param attributes Map of attributes to save for */
|
||||
default Result<Collection<AdditionalAttributeRecord>> saveAdditionalAttributes(
|
||||
final EntityType type,
|
||||
final Long entityId,
|
||||
final Map<String, String> attributes) {
|
||||
|
||||
return Result.tryCatch(() -> attributes.entrySet()
|
||||
.stream()
|
||||
.map(attr -> saveAdditionalAttribute(type, entityId, attr.getKey(), attr.getValue()))
|
||||
.flatMap(Result::onErrorLogAndSkip)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/** Use this to delete an additional attribute by identifier (primary-key)
|
||||
*
|
||||
* @param id identifier (primary-key) */
|
||||
|
|
|
@ -91,11 +91,17 @@ public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO {
|
|||
final String value) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"value cannot be null. Use delete to delete an additional attribute");
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Save additional attribute. Type: {}, entity: {}, name: {}, value: {}",
|
||||
type, entityId, name, value);
|
||||
}
|
||||
|
||||
final Optional<Long> id = this.additionalAttributeRecordMapper
|
||||
.selectIdsByExample()
|
||||
.where(
|
||||
|
@ -150,26 +156,43 @@ public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO {
|
|||
@Override
|
||||
@Transactional
|
||||
public void delete(final EntityType type, final Long entityId, final String name) {
|
||||
this.additionalAttributeRecordMapper
|
||||
.deleteByExample()
|
||||
.where(
|
||||
AdditionalAttributeRecordDynamicSqlSupport.entityType,
|
||||
SqlBuilder.isEqualTo(type.name()))
|
||||
.and(
|
||||
AdditionalAttributeRecordDynamicSqlSupport.entityId,
|
||||
SqlBuilder.isEqualTo(entityId))
|
||||
.and(
|
||||
AdditionalAttributeRecordDynamicSqlSupport.name,
|
||||
SqlBuilder.isEqualTo(name))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
try {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Delete additional attribute. Type: {}, entity: {}, name: {}",
|
||||
type, entityId, name);
|
||||
}
|
||||
|
||||
this.additionalAttributeRecordMapper
|
||||
.deleteByExample()
|
||||
.where(
|
||||
AdditionalAttributeRecordDynamicSqlSupport.entityType,
|
||||
SqlBuilder.isEqualTo(type.name()))
|
||||
.and(
|
||||
AdditionalAttributeRecordDynamicSqlSupport.entityId,
|
||||
SqlBuilder.isEqualTo(entityId))
|
||||
.and(
|
||||
AdditionalAttributeRecordDynamicSqlSupport.name,
|
||||
SqlBuilder.isEqualTo(name))
|
||||
.build()
|
||||
.execute();
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to delete additional attribute: Type: {}, entity: {}, name: {}",
|
||||
type, entityId, name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteAll(final EntityType type, final Long entityId) {
|
||||
try {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Delete all additional attributes. Type: {}, entity: {}",
|
||||
type, entityId);
|
||||
}
|
||||
|
||||
this.additionalAttributeRecordMapper
|
||||
.deleteByExample()
|
||||
.where(
|
||||
|
@ -181,7 +204,7 @@ public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO {
|
|||
.build()
|
||||
.execute();
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to delete all additional attributes for: {} cause: {}", entityId, e.getMessage());
|
||||
log.error("Failed to delete all additional attributes for: {} cause: {}", entityId, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -329,7 +329,8 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
|
|||
final Long count = this.examTemplateRecordMapper
|
||||
.countByExample()
|
||||
.where(ExamTemplateRecordDynamicSqlSupport.name, isEqualTo(examTemplate.name))
|
||||
.and(ExamTemplateRecordDynamicSqlSupport.id, isNotEqualToWhenPresent(examTemplate.institutionId))
|
||||
.and(ExamTemplateRecordDynamicSqlSupport.institutionId, isEqualTo(examTemplate.institutionId))
|
||||
.and(ExamTemplateRecordDynamicSqlSupport.id, isNotEqualToWhenPresent(examTemplate.id))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
|
|
|
@ -16,23 +16,17 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringServi
|
|||
|
||||
public interface ExamAdminService {
|
||||
|
||||
/** Adds a default indicator that is defined by configuration to a given exam.
|
||||
/** Saves additional attributes for the exam that are specific to a type of LMS
|
||||
*
|
||||
* @param exam The Exam to add the default indicator
|
||||
* @return Result refer to the Exam with added default indicator or to an error if happened */
|
||||
Result<Exam> addDefaultIndicator(Exam exam);
|
||||
|
||||
/** Saves additional attributes for a specified Exam on creation or on update.
|
||||
*
|
||||
* @param exam The Exam to add the default indicator
|
||||
* @return Result refer */
|
||||
Result<Exam> saveAdditionalAttributes(Exam exam);
|
||||
* @param exam The Exam to add the LMS specific attributes
|
||||
* @return Result refer to the created exam or to an error when happened */
|
||||
Result<Exam> saveLMSAttributes(Exam exam);
|
||||
|
||||
/** Applies all additional SEB restriction attributes that are defined by the
|
||||
* type of the LMS of a given Exam to this given Exam.
|
||||
*
|
||||
* @param exam the Exam to apply all additional SEB restriction attributes
|
||||
* @return the Exam */
|
||||
* @return Result refer to the created exam or to an error when happened */
|
||||
Result<Exam> applyAdditionalSEBRestrictions(Exam exam);
|
||||
|
||||
/** Indicates whether a specific exam is been restricted with SEB restriction feature on the LMS or not.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* 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/.
|
||||
|
@ -8,6 +8,41 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
public interface ExamTemplateService {
|
||||
|
||||
String VAR_START_DATE = "__startDate__";
|
||||
String VAR_CURRENT_DATE = "__currentDate__";
|
||||
String VAR_EXAM_NAME = "__examName__";
|
||||
String VAR_EXAM_TEMPLATE_NAME = "__examTemplateName__";
|
||||
|
||||
String DEFAULT_EXAM_CONFIG_NAME_TEMPLATE = VAR_START_DATE + " " + VAR_EXAM_NAME;
|
||||
String DEFAULT_EXAM_CONFIG_DESC_TEMPLATE =
|
||||
"This has automatically been created from the exam template: "
|
||||
+ VAR_EXAM_TEMPLATE_NAME + " at: "
|
||||
+ VAR_CURRENT_DATE;
|
||||
|
||||
/** Adds the indicators that are defined by a exam template or
|
||||
* a default indicator that is defined by configuration to a given exam.
|
||||
*
|
||||
* @param exam The Exam to add the default indicator
|
||||
* @return Result refer to the Exam with added default indicator or to an error if happened */
|
||||
Result<Exam> addDefinedIndicators(Exam exam);
|
||||
|
||||
/** Initializes additional attributes for a specified Exam on creation.
|
||||
*
|
||||
* @param exam The Exam to add the default indicator
|
||||
* @return Result refer to the created exam or to an error when happened */
|
||||
Result<Exam> initAdditionalAttributes(Exam exam);
|
||||
|
||||
/** Initializes a pre defined exam configuration. The configuration template to create a exam configuration
|
||||
* is defined by a given linked exam template. This is used to create the exam configuration and automatically
|
||||
* link it to the newly created exam
|
||||
*
|
||||
* @param exam The Exam to create and add new exam configuration
|
||||
* @return Result refer to the created exam or to an error when happened */
|
||||
Result<Exam> initExamConfiguration(Exam exam);
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -21,19 +20,13 @@ import org.apache.commons.lang3.BooleanUtils;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
|
||||
|
@ -47,7 +40,6 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
|||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
|
@ -64,80 +56,40 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
private static final Logger log = LoggerFactory.getLogger(ExamAdminServiceImpl.class);
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
private final IndicatorDAO indicatorDAO;
|
||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final JSONMapper jsonMapper;
|
||||
private final Cryptor cryptor;
|
||||
private final ExamProctoringServiceFactory examProctoringServiceFactory;
|
||||
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||
|
||||
private final String defaultIndicatorName;
|
||||
private final String defaultIndicatorType;
|
||||
private final String defaultIndicatorColor;
|
||||
private final String defaultIndicatorThresholds;
|
||||
|
||||
protected ExamAdminServiceImpl(
|
||||
final ExamDAO examDAO,
|
||||
final IndicatorDAO indicatorDAO,
|
||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||
final LmsAPIService lmsAPIService,
|
||||
final JSONMapper jsonMapper,
|
||||
final Cryptor cryptor,
|
||||
final ExamProctoringServiceFactory examProctoringServiceFactory,
|
||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||
@Value("${sebserver.webservice.api.exam.indicator.name:Ping}") final String defaultIndicatorName,
|
||||
@Value("${sebserver.webservice.api.exam.indicator.type:LAST_PING}") final String defaultIndicatorType,
|
||||
@Value("${sebserver.webservice.api.exam.indicator.color:b4b4b4}") final String defaultIndicatorColor,
|
||||
@Value("${sebserver.webservice.api.exam.indicator.thresholds:[{\"value\":2000.0,\"color\":\"22b14c\"},{\"value\":5000.0,\"color\":\"ff7e00\"},{\"value\":10000.0,\"color\":\"ed1c24\"}]}") final String defaultIndicatorThresholds) {
|
||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.indicatorDAO = indicatorDAO;
|
||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.cryptor = cryptor;
|
||||
this.examProctoringServiceFactory = examProctoringServiceFactory;
|
||||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||
|
||||
this.defaultIndicatorName = defaultIndicatorName;
|
||||
this.defaultIndicatorType = defaultIndicatorType;
|
||||
this.defaultIndicatorColor = defaultIndicatorColor;
|
||||
this.defaultIndicatorThresholds = defaultIndicatorThresholds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> addDefaultIndicator(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final Collection<Indicator.Threshold> thresholds = this.jsonMapper.readValue(
|
||||
this.defaultIndicatorThresholds,
|
||||
new TypeReference<Collection<Indicator.Threshold>>() {
|
||||
});
|
||||
|
||||
final Indicator indicator = new Indicator(
|
||||
null,
|
||||
exam.id,
|
||||
this.defaultIndicatorName,
|
||||
IndicatorType.valueOf(this.defaultIndicatorType),
|
||||
this.defaultIndicatorColor,
|
||||
null,
|
||||
null,
|
||||
thresholds);
|
||||
|
||||
this.indicatorDAO.createNew(indicator)
|
||||
.getOrThrow();
|
||||
|
||||
return this.examDAO
|
||||
.byPK(exam.id)
|
||||
.getOrThrow();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyAdditionalSEBRestrictions(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply additional SEB restrictions for exam: {}",
|
||||
exam.externalId);
|
||||
}
|
||||
|
||||
final LmsSetup lmsSetup = this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (lmsSetup.lmsType == LmsType.OPEN_EDX) {
|
||||
|
@ -161,30 +113,10 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> saveAdditionalAttributes(final Exam exam) {
|
||||
public Result<Exam> saveLMSAttributes(final Exam exam) {
|
||||
return saveAdditionalAttributesForMoodleExams(exam);
|
||||
}
|
||||
|
||||
private Result<Exam> saveAdditionalAttributesForMoodleExams(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
final LmsAPITemplate lmsTemplate = this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (lmsTemplate.lmsSetup().lmsType == LmsType.MOODLE) {
|
||||
lmsTemplate.getQuiz(exam.externalId)
|
||||
.flatMap(quizData -> this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
quizData.name))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Boolean> isRestricted(final Exam exam) {
|
||||
if (exam == null) {
|
||||
|
@ -386,4 +318,24 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
}
|
||||
}
|
||||
|
||||
private Result<Exam> saveAdditionalAttributesForMoodleExams(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
final LmsAPITemplate lmsTemplate = this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (lmsTemplate.lmsSetup().lmsType == LmsType.MOODLE) {
|
||||
lmsTemplate.getQuiz(exam.externalId)
|
||||
.flatMap(quizData -> this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
quizData.name))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* 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.exam.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
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.ExamTemplateDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
public class ExamTemplateServiceImpl implements ExamTemplateService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamTemplateServiceImpl.class);
|
||||
|
||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final ExamTemplateDAO examTemplateDAO;
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final IndicatorDAO indicatorDAO;
|
||||
private final JSONMapper jsonMapper;
|
||||
|
||||
private final String defaultIndicatorName;
|
||||
private final String defaultIndicatorType;
|
||||
private final String defaultIndicatorColor;
|
||||
private final String defaultIndicatorThresholds;
|
||||
private final String defaultExamConfigNameTemplate;
|
||||
private final String defaultExamConfigDescTemplate;
|
||||
|
||||
public ExamTemplateServiceImpl(
|
||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||
final ExamAdminService examAdminService,
|
||||
final ExamTemplateDAO examTemplateDAO,
|
||||
final ConfigurationNodeDAO configurationNodeDAO,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final IndicatorDAO indicatorDAO,
|
||||
final JSONMapper jsonMapper,
|
||||
|
||||
@Value("${sebserver.webservice.api.exam.indicator.name:Ping}") final String defaultIndicatorName,
|
||||
@Value("${sebserver.webservice.api.exam.indicator.type:LAST_PING}") final String defaultIndicatorType,
|
||||
@Value("${sebserver.webservice.api.exam.indicator.color:b4b4b4}") final String defaultIndicatorColor,
|
||||
@Value("${sebserver.webservice.api.exam.indicator.thresholds:[{\"value\":2000.0,\"color\":\"22b14c\"},{\"value\":5000.0,\"color\":\"ff7e00\"},{\"value\":10000.0,\"color\":\"ed1c24\"}]}") final String defaultIndicatorThresholds,
|
||||
@Value("${sebserver.webservice.configtemplate.examconfig.default.name:") final String defaultExamConfigNameTemplate,
|
||||
@Value("${sebserver.webservice.configtemplate.examconfig.default.description:}") final String defaultExamConfigDescTemplate) {
|
||||
|
||||
this.examTemplateDAO = examTemplateDAO;
|
||||
this.configurationNodeDAO = configurationNodeDAO;
|
||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||
this.examAdminService = examAdminService;
|
||||
this.indicatorDAO = indicatorDAO;
|
||||
this.jsonMapper = jsonMapper;
|
||||
|
||||
this.defaultIndicatorName = defaultIndicatorName;
|
||||
this.defaultIndicatorType = defaultIndicatorType;
|
||||
this.defaultIndicatorColor = defaultIndicatorColor;
|
||||
this.defaultIndicatorThresholds = defaultIndicatorThresholds;
|
||||
|
||||
this.defaultExamConfigNameTemplate = (StringUtils.isNotBlank(defaultExamConfigDescTemplate))
|
||||
? defaultExamConfigNameTemplate
|
||||
: DEFAULT_EXAM_CONFIG_NAME_TEMPLATE;
|
||||
this.defaultExamConfigDescTemplate = (StringUtils.isNotBlank(defaultExamConfigDescTemplate))
|
||||
? defaultExamConfigDescTemplate
|
||||
: DEFAULT_EXAM_CONFIG_DESC_TEMPLATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> addDefinedIndicators(final Exam exam) {
|
||||
if (exam.examTemplateId != null) {
|
||||
return addIndicatorsFromTemplate(exam);
|
||||
} else {
|
||||
return addDefaultIndicator(exam);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> initAdditionalAttributes(final Exam exam) {
|
||||
return this.examAdminService.saveLMSAttributes(exam)
|
||||
.map(_exam -> {
|
||||
|
||||
if (exam.examTemplateId != null) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Init exam: {} with additional attributes form exam template: {}",
|
||||
exam.externalId,
|
||||
exam.examTemplateId);
|
||||
}
|
||||
|
||||
final ExamTemplate examTemplate = this.examTemplateDAO
|
||||
.byPK(exam.examTemplateId)
|
||||
.onError(error -> log.warn("No exam template found for id: {}",
|
||||
exam.examTemplateId,
|
||||
error.getMessage()))
|
||||
.getOr(null);
|
||||
|
||||
if (examTemplate == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
if (examTemplate.examAttributes != null && !examTemplate.examAttributes.isEmpty()) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttributes(
|
||||
EntityType.EXAM,
|
||||
exam.getId(),
|
||||
examTemplate.examAttributes);
|
||||
}
|
||||
}
|
||||
return _exam;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> initExamConfiguration(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (exam.examTemplateId != null) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Init exam: {} from template: {}", exam.externalId, exam.examTemplateId);
|
||||
}
|
||||
|
||||
final ExamTemplate examTemplate = this.examTemplateDAO
|
||||
.byPK(exam.examTemplateId)
|
||||
.onError(error -> log.warn("No exam template found for id: {}",
|
||||
exam.examTemplateId,
|
||||
error.getMessage()))
|
||||
.getOr(null);
|
||||
|
||||
if (examTemplate == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
if (examTemplate.configTemplateId != null) {
|
||||
|
||||
// create new exam configuration for the exam
|
||||
final ConfigurationNode examConfig = this.configurationNodeDAO
|
||||
.createNew(new ConfigurationNode(
|
||||
null,
|
||||
exam.institutionId,
|
||||
examTemplate.configTemplateId,
|
||||
replaceVars(this.defaultExamConfigNameTemplate, exam, examTemplate),
|
||||
replaceVars(this.defaultExamConfigDescTemplate, exam, examTemplate),
|
||||
ConfigurationType.EXAM_CONFIG,
|
||||
exam.owner,
|
||||
ConfigurationStatus.CONSTRUCTION))
|
||||
.onError(error -> log.error(
|
||||
"Failed to create exam configuration for exam: {} from template: {}",
|
||||
exam,
|
||||
examTemplate,
|
||||
error))
|
||||
.getOrThrow();
|
||||
|
||||
// map the exam configuration to the exam
|
||||
this.examConfigurationMapDAO.createNew(new ExamConfigurationMap(
|
||||
exam.institutionId,
|
||||
exam.id,
|
||||
examConfig.id,
|
||||
null))
|
||||
.onError(error -> log.error(
|
||||
"Failed to create exam configuration mapping for exam: {} for exam config: {}",
|
||||
exam,
|
||||
examConfig,
|
||||
error))
|
||||
.getOrThrow();
|
||||
|
||||
}
|
||||
} else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Not exam template defined for exam: {}", exam.externalId);
|
||||
}
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
private Result<Exam> addIndicatorsFromTemplate(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (exam.examTemplateId != null) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Init exam: {} from template: {}", exam.externalId, exam.examTemplateId);
|
||||
}
|
||||
|
||||
final ExamTemplate examTemplate = this.examTemplateDAO
|
||||
.byPK(exam.examTemplateId)
|
||||
.onError(error -> log.warn("No exam template found for id: {}",
|
||||
exam.examTemplateId,
|
||||
error.getMessage()))
|
||||
.getOr(null);
|
||||
|
||||
if (examTemplate == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
examTemplate.indicatorTemplates
|
||||
.forEach(it -> createIndicatorFromTemplate(it, exam));
|
||||
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
private void createIndicatorFromTemplate(final IndicatorTemplate template, final Exam exam) {
|
||||
try {
|
||||
|
||||
this.indicatorDAO.createNew(
|
||||
new Indicator(
|
||||
null,
|
||||
exam.id,
|
||||
template.name,
|
||||
template.type,
|
||||
template.defaultColor,
|
||||
template.defaultIcon,
|
||||
template.tags,
|
||||
template.thresholds))
|
||||
.getOrThrow();
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to automatically create indicator from template: {} for exam: {}",
|
||||
template,
|
||||
exam,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private Result<Exam> addDefaultIndicator(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Init default indicator for exam: {}", exam.externalId);
|
||||
}
|
||||
|
||||
final Collection<Indicator.Threshold> thresholds = this.jsonMapper.readValue(
|
||||
this.defaultIndicatorThresholds,
|
||||
new TypeReference<Collection<Indicator.Threshold>>() {
|
||||
});
|
||||
|
||||
this.indicatorDAO.createNew(
|
||||
new Indicator(
|
||||
null,
|
||||
exam.id,
|
||||
this.defaultIndicatorName,
|
||||
IndicatorType.valueOf(this.defaultIndicatorType),
|
||||
this.defaultIndicatorColor,
|
||||
null,
|
||||
null,
|
||||
thresholds))
|
||||
.getOrThrow();
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
private String replaceVars(final String template, final Exam exam, final ExamTemplate examTemplate) {
|
||||
final String currentDate = DateTime.now(DateTimeZone.UTC).toString(Constants.STANDARD_DATE_FORMATTER);
|
||||
final Map<String, String> vars = new HashMap<>();
|
||||
vars.put(VAR_CURRENT_DATE, currentDate);
|
||||
vars.put(
|
||||
VAR_START_DATE,
|
||||
(exam.startTime != null)
|
||||
? exam.startTime.toString(Constants.STANDARD_DATE_FORMATTER)
|
||||
: currentDate);
|
||||
vars.put(VAR_EXAM_NAME, exam.name);
|
||||
vars.put(VAR_EXAM_TEMPLATE_NAME, examTemplate.name);
|
||||
|
||||
return Utils.replaceAll(template, vars);
|
||||
}
|
||||
|
||||
}
|
|
@ -70,6 +70,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
|
||||
|
@ -86,6 +87,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
private final ExamDAO examDAO;
|
||||
private final UserDAO userDAO;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final ExamTemplateService examTemplateService;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final ExamConfigService sebExamConfigService;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
@ -101,6 +103,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
final LmsAPIService lmsAPIService,
|
||||
final UserDAO userDAO,
|
||||
final ExamAdminService examAdminService,
|
||||
final ExamTemplateService examTemplateService,
|
||||
final ExamConfigService sebExamConfigService,
|
||||
final ExamSessionService examSessionService,
|
||||
final SEBRestrictionService sebRestrictionService) {
|
||||
|
@ -115,6 +118,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
this.examDAO = examDAO;
|
||||
this.userDAO = userDAO;
|
||||
this.examAdminService = examAdminService;
|
||||
this.examTemplateService = examTemplateService;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.sebExamConfigService = sebExamConfigService;
|
||||
this.examSessionService = examSessionService;
|
||||
|
@ -459,9 +463,10 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
|
||||
@Override
|
||||
protected Result<Exam> notifyCreated(final Exam entity) {
|
||||
return this.examAdminService
|
||||
.addDefaultIndicator(entity)
|
||||
.flatMap(this.examAdminService::saveAdditionalAttributes)
|
||||
return this.examTemplateService
|
||||
.addDefinedIndicators(entity)
|
||||
.flatMap(this.examTemplateService::initAdditionalAttributes)
|
||||
.flatMap(this.examTemplateService::initExamConfiguration)
|
||||
.flatMap(this.examAdminService::applyAdditionalSEBRestrictions);
|
||||
}
|
||||
|
||||
|
@ -470,7 +475,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
return Result.tryCatch(() -> {
|
||||
this.examSessionService.flushCache(entity);
|
||||
return entity;
|
||||
}).flatMap(this.examAdminService::saveAdditionalAttributes);
|
||||
}).flatMap(this.examAdminService::saveLMSAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -133,15 +133,15 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
|
|||
EntityType.EXAM_TEMPLATE,
|
||||
institutionId);
|
||||
|
||||
final ExamTemplate examTemplate = super.entityDAO
|
||||
return super.entityDAO
|
||||
.byModelId(parentModelId)
|
||||
.map(t -> t.indicatorTemplates
|
||||
.stream()
|
||||
.filter(i -> modelId.equals(i.getModelId()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ResourceNotFoundException(EntityType.INDICATOR, parentModelId)))
|
||||
.getOrThrow();
|
||||
|
||||
return examTemplate.indicatorTemplates
|
||||
.stream()
|
||||
.filter(i -> modelId.equals(i.getModelId()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ResourceNotFoundException(EntityType.INDICATOR, parentModelId));
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -220,7 +220,15 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
|
|||
.stream()
|
||||
.map(i -> {
|
||||
if (modelId.equals(i.getModelId())) {
|
||||
return modifyData;
|
||||
return new IndicatorTemplate(
|
||||
modifyData.id,
|
||||
modifyData.examTemplateId,
|
||||
modifyData.name,
|
||||
(modifyData.type != null) ? modifyData.type : i.type,
|
||||
(modifyData.defaultColor != null) ? modifyData.defaultColor : i.defaultColor,
|
||||
(modifyData.defaultIcon != null) ? modifyData.defaultIcon : i.defaultIcon,
|
||||
(modifyData.tags != null) ? modifyData.tags : i.tags,
|
||||
(modifyData.thresholds != null) ? modifyData.thresholds : i.thresholds);
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
|
|
|
@ -71,3 +71,7 @@ sebserver.webservice.proctoring.resetBroadcastOnLeav=true
|
|||
sebserver.webservice.proctoring.zoom.enableWaitingRoom=false
|
||||
sebserver.webservice.proctoring.zoom.sendRejoinForCollectingRoom=true
|
||||
|
||||
sebserver.webservice.configtemplate.examconfig.default.name=__startDate__ __examName__
|
||||
sebserver.webservice.configtemplate.examconfig.default.description=This has automatically been created from the exam template: __examTemplateName__ at: __currentDate__
|
||||
|
||||
|
||||
|
|
|
@ -485,6 +485,9 @@ sebserver.exam.form.type=Exam Type
|
|||
sebserver.exam.form.type.tooltip=The type of the exam.<br/><br/>This has only descriptive character for now and can be used to categorise exams within a type
|
||||
sebserver.exam.form.supporter=Exam Supporter
|
||||
sebserver.exam.form.supporter.tooltip=A list of users that are allowed to support this exam<br/><br/>To add a user in edit mode click into the field on the right-hand side and start typing the first letters of the username.<br/>A filtered choice will drop down. Select a specific username in the dropdown list to add the user to the list.<br/>To remove a user from the list, just double-click the username on the list.
|
||||
sebserver.exam.form.examTemplate=Exam Template
|
||||
sebserver.exam.form.examTemplate.tooltip=Select an exam template to automatically create the exam, exam configuration and indicators defined by the template.
|
||||
sebserver.exam.form.examTemplate.error=Failed to set type and supporter form template.<br/>Please make sure you have selected the right type and supporter or tray again.
|
||||
|
||||
sebserver.exam.form.export.config.popup.title=Export Connection Configuration for Starting the Exam
|
||||
sebserver.exam.form.export.config.name=Name
|
||||
|
|
|
@ -102,6 +102,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicatorTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ExportExamConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMapping;
|
||||
|
@ -2602,7 +2603,9 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
new GetIndicatorTemplatePage(),
|
||||
new NewIndicatorTemplate(),
|
||||
new SaveIndicatorTemplate(),
|
||||
new GetIndicatorTemplate());
|
||||
new DeleteIndicatorTemplate(),
|
||||
new GetIndicatorTemplate(),
|
||||
new GetExamConfigNodeNames());
|
||||
|
||||
Page<ExamTemplate> examTemplatePage = restService
|
||||
.getBuilder(GetExamTemplatePage.class)
|
||||
|
@ -2653,11 +2656,11 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
assertEquals(configTemplate.id, templateFromList.configTemplateId);
|
||||
|
||||
// create new indicator template
|
||||
final MultiValueMap<String, String> thresholds = new LinkedMultiValueMap<>();
|
||||
MultiValueMap<String, String> thresholds = new LinkedMultiValueMap<>();
|
||||
thresholds.add(Domain.THRESHOLD.REFERENCE_NAME, "1|000001");
|
||||
thresholds.add(Domain.THRESHOLD.REFERENCE_NAME, "2|000002");
|
||||
thresholds.add(Domain.THRESHOLD.REFERENCE_NAME, "3|000003");
|
||||
final IndicatorTemplate indicatorTemplate = restService
|
||||
IndicatorTemplate indicatorTemplate = restService
|
||||
.getBuilder(NewIndicatorTemplate.class)
|
||||
.withFormParam(IndicatorTemplate.ATTR_EXAM_TEMPLATE_ID, examTemplate.getModelId())
|
||||
.withFormParam(Domain.INDICATOR.ATTR_NAME, "Errors")
|
||||
|
@ -2683,11 +2686,118 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
assertFalse(indicatorList.isEmpty());
|
||||
assertTrue(indicatorList.content.size() == 1);
|
||||
|
||||
// TODO save exam template
|
||||
// get exam config template for use
|
||||
final List<EntityName> configTemplateNames = restService.getBuilder(GetExamConfigNodeNames.class)
|
||||
.withQueryParam(ConfigurationNode.FILTER_ATTR_TYPE, ConfigurationType.TEMPLATE.name())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
// TODO edit indicator template
|
||||
assertNotNull(configTemplateNames);
|
||||
assertFalse(configTemplateNames.isEmpty());
|
||||
final EntityName configTemplateName = configTemplateNames.get(0);
|
||||
|
||||
// TODO remove indicator template
|
||||
// edit/save exam template
|
||||
ExamTemplate savedTemplate = restService
|
||||
.getBuilder(SaveExamTemplate.class)
|
||||
.withBody(new ExamTemplate(
|
||||
examTemplate.id,
|
||||
examTemplate.institutionId,
|
||||
examTemplate.name,
|
||||
"New Description",
|
||||
null,
|
||||
null,
|
||||
Long.parseLong(configTemplateName.modelId), // assosiate with given config template
|
||||
null,
|
||||
null,
|
||||
null))
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
assertNotNull(savedTemplate);
|
||||
assertEquals("New Description", savedTemplate.description);
|
||||
assertNotNull(savedTemplate.configTemplateId);
|
||||
|
||||
// edit/save indicator template
|
||||
IndicatorTemplate savedIndicatorTemplate = restService
|
||||
.getBuilder(SaveIndicatorTemplate.class)
|
||||
.withBody(new IndicatorTemplate(
|
||||
indicatorTemplate.id,
|
||||
indicatorTemplate.examTemplateId,
|
||||
"New Errors",
|
||||
indicatorTemplate.type,
|
||||
null, null, null, null))
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
assertNotNull(savedIndicatorTemplate);
|
||||
assertEquals("New Errors", savedIndicatorTemplate.name);
|
||||
|
||||
savedTemplate = restService
|
||||
.getBuilder(GetExamTemplate.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, savedTemplate.getModelId())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
assertNotNull(savedTemplate);
|
||||
assertNotNull(savedTemplate.indicatorTemplates);
|
||||
assertFalse(savedTemplate.indicatorTemplates.isEmpty());
|
||||
|
||||
savedIndicatorTemplate = savedTemplate.indicatorTemplates.iterator().next();
|
||||
assertNotNull(savedIndicatorTemplate);
|
||||
assertEquals("New Errors", savedIndicatorTemplate.name);
|
||||
|
||||
// create/remove indicator template
|
||||
thresholds = new LinkedMultiValueMap<>();
|
||||
thresholds.add(Domain.THRESHOLD.REFERENCE_NAME, "1|000001");
|
||||
thresholds.add(Domain.THRESHOLD.REFERENCE_NAME, "2|000002");
|
||||
thresholds.add(Domain.THRESHOLD.REFERENCE_NAME, "3|000003");
|
||||
indicatorTemplate = restService
|
||||
.getBuilder(NewIndicatorTemplate.class)
|
||||
.withFormParam(IndicatorTemplate.ATTR_EXAM_TEMPLATE_ID, examTemplate.getModelId())
|
||||
.withFormParam(Domain.INDICATOR.ATTR_NAME, "Errors")
|
||||
.withFormParam(Domain.INDICATOR.ATTR_TYPE, IndicatorType.ERROR_COUNT.name)
|
||||
.withFormParam(Domain.INDICATOR.ATTR_COLOR, "000001")
|
||||
.withFormParams(thresholds)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
savedTemplate = restService
|
||||
.getBuilder(GetExamTemplate.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, savedTemplate.getModelId())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
assertNotNull(savedTemplate);
|
||||
assertNotNull(savedTemplate.indicatorTemplates);
|
||||
assertFalse(savedTemplate.indicatorTemplates.isEmpty());
|
||||
assertTrue(savedTemplate.indicatorTemplates.size() == 2);
|
||||
final Iterator<IndicatorTemplate> iterator = savedTemplate.indicatorTemplates.iterator();
|
||||
final IndicatorTemplate next1 = iterator.next();
|
||||
final IndicatorTemplate next2 = iterator.next();
|
||||
assertEquals("New Errors", next1.name);
|
||||
assertEquals("Errors", next2.name);
|
||||
|
||||
final EntityKey entityKey = restService
|
||||
.getBuilder(DeleteIndicatorTemplate.class)
|
||||
.withURIVariable(API.PARAM_PARENT_MODEL_ID, savedTemplate.getModelId())
|
||||
.withURIVariable(API.PARAM_MODEL_ID, next2.getModelId())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
assertNotNull(entityKey);
|
||||
savedTemplate = restService
|
||||
.getBuilder(GetExamTemplate.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, savedTemplate.getModelId())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
assertNotNull(savedTemplate);
|
||||
assertNotNull(savedTemplate.indicatorTemplates);
|
||||
assertFalse(savedTemplate.indicatorTemplates.isEmpty());
|
||||
assertTrue(savedTemplate.indicatorTemplates.size() == 1);
|
||||
assertEquals("New Errors", savedTemplate.indicatorTemplates.iterator().next().name);
|
||||
|
||||
// TODO create exam from template
|
||||
|
||||
// TODO delete exam template
|
||||
|
||||
|
|
Loading…
Reference in a new issue