This commit is contained in:
anhefti 2019-04-03 17:01:55 +02:00
parent 96b6f6efc7
commit 8278f3f43d
11 changed files with 115 additions and 77 deletions

View file

@ -8,13 +8,10 @@
package ch.ethz.seb.sebserver.gbl.api;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -38,23 +35,9 @@ public class POSTMapper {
public POSTMapper(final MultiValueMap<String, String> params) {
super();
this.params = new LinkedMultiValueMap<>();
if (params != null) {
for (final Map.Entry<String, List<String>> entry : params.entrySet()) {
this.params.put(
entry.getKey(),
entry.getValue()
.stream()
.map(encoded -> {
try {
return URLDecoder.decode(encoded, "UTF-8");
} catch (final UnsupportedEncodingException e) {
return encoded;
}
})
.collect(Collectors.toList()));
}
}
this.params = params != null
? new LinkedMultiValueMap<>(params)
: new LinkedMultiValueMap<>();
}
public String getString(final String name) {

View file

@ -14,6 +14,7 @@ import java.util.Collections;
import javax.validation.constraints.NotNull;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -27,15 +28,15 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
public final class Exam implements GrantEntity, Activatable {
public static final String ATTR_STATUS = "examStatus";
public static final String FILTER_ATTR_TYPE = "type";
public static final String FILTER_ATTR_FROM = "from";
// public enum ExamStatus {
// ON_CREATION,
// READY,
// RUNNING,
// FINISHED
// }
public enum ExamStatus {
UP_COMING,
RUNNING,
FINISHED
}
public enum ExamType {
UNDEFINED,
@ -82,7 +83,6 @@ public final class Exam implements GrantEntity, Activatable {
public final String quitPassword;
@JsonProperty(EXAM.ATTR_OWNER)
@NotNull
public final String owner;
@JsonProperty(EXAM.ATTR_SUPPORTER)
@ -221,6 +221,24 @@ public final class Exam implements GrantEntity, Activatable {
return this.quitPassword;
}
@JsonIgnore
public ExamStatus getStatus() {
if (this.startTime == null) {
return ExamStatus.UP_COMING;
}
final DateTime now = DateTime.now(DateTimeZone.UTC);
if (this.startTime.isBefore(now)) {
if (this.endTime == null || this.endTime.isAfter(now)) {
return ExamStatus.RUNNING;
} else {
return ExamStatus.FINISHED;
}
}
return ExamStatus.UP_COMING;
}
@Override
public boolean isActive() {
return this.active;

View file

@ -23,6 +23,7 @@ import ch.ethz.seb.sebserver.gbl.api.API;
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.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@ -40,10 +41,7 @@ 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.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageUtils;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ActivateExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
@ -128,8 +126,9 @@ public class ExamForm implements TemplateComposer {
final BooleanSupplier isNew = () -> importFromQuizData;
final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean();
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
final boolean writeGrant = userGrantCheck.w();
final boolean modifyGrant = userGrantCheck.m();
final ExamStatus examStatus = exam.getStatus();
final boolean editable = examStatus == ExamStatus.UP_COMING;
// The Exam form
final FormHandle<Exam> formHandle = this.pageService.formBuilder(
@ -191,6 +190,11 @@ public class ExamForm implements TemplateComposer {
"sebserver.exam.form.type",
String.valueOf(exam.type),
this.resourceService::examTypeResources))
.addField(FormBuilder.text(
Exam.ATTR_STATUS,
"sebserver.exam.form.status",
i18nSupport.getText(new LocTextKey("sebserver.exam.status." + examStatus.name())))
.readonly(true))
.addField(FormBuilder.multiComboSelection(
Domain.EXAM.ATTR_SUPPORTER,
"sebserver.exam.form.supporter",
@ -210,7 +214,7 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_MODIFY)
.withEntityKey(entityKey)
.publishIf(() -> modifyGrant && readonly)
.publishIf(() -> modifyGrant && readonly && editable)
.newAction(ActionDefinition.EXAM_SAVE)
.withExec(formHandle::processFormSave)
@ -221,18 +225,18 @@ public class ExamForm implements TemplateComposer {
.withEntityKey(entityKey)
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData))
.withExec(this::cancelModify)
.publishIf(() -> !readonly)
.publishIf(() -> !readonly);
.newAction(ActionDefinition.EXAM_DEACTIVATE)
.withEntityKey(entityKey)
.withSimpleRestCall(restService, DeactivateExam.class)
.withConfirm(PageUtils.confirmDeactivation(exam, restService))
.publishIf(() -> writeGrant && readonly && exam.isActive())
.newAction(ActionDefinition.EXAM_ACTIVATE)
.withEntityKey(entityKey)
.withSimpleRestCall(restService, ActivateExam.class)
.publishIf(() -> writeGrant && readonly && !exam.isActive());
// .newAction(ActionDefinition.EXAM_DEACTIVATE)
// .withEntityKey(entityKey)
// .withSimpleRestCall(restService, DeactivateExam.class)
// .withConfirm(PageUtils.confirmDeactivation(exam, restService))
// .publishIf(() -> writeGrant && readonly && exam.isActive())
//
// .newAction(ActionDefinition.EXAM_ACTIVATE)
// .withEntityKey(entityKey)
// .withSimpleRestCall(restService, ActivateExam.class)
// .publishIf(() -> writeGrant && readonly && !exam.isActive());
// additional data in read-only view
if (readonly) {
@ -274,17 +278,17 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_INDICATOR_NEW)
.withParentEntityKey(entityKey)
.publishIf(() -> modifyGrant)
.publishIf(() -> modifyGrant && editable)
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.withSelect(indicatorTable::getSelection, PageAction::applySingleSelection, emptySelectionTextKey)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent())
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable)
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
.withEntityKey(entityKey)
.withSelect(indicatorTable::getSelection, this::deleteSelectedIndicator, emptySelectionTextKey)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent());
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable);
// TODO List of attached SEB Configurations

View file

@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gui.content;
import java.util.function.Function;
import org.eclipse.swt.widgets.Composite;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@ -27,6 +29,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.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
@ -60,6 +63,8 @@ public class QuizDiscoveryList implements TemplateComposer {
new LocTextKey("sebserver.quizdiscovery.list.column.name");
private final static LocTextKey DETAILS_TITLE_TEXT_KEY =
new LocTextKey("sebserver.quizdiscovery.quiz.details.title");
private final static LocTextKey NO_IMPORT_OF_OUT_DATED_QUIZ =
new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
// filter attribute models
private final TableFilterAttribute lmsFilter;
@ -174,6 +179,13 @@ public class QuizDiscoveryList implements TemplateComposer {
private PageAction importQuizData(final PageAction action, final EntityTable<QuizData> table) {
final QuizData selectedROWData = table.getSelectedROWData();
if (selectedROWData.endTime != null) {
final DateTime now = DateTime.now(DateTimeZone.UTC);
if (selectedROWData.endTime.isBefore(now)) {
throw new PageMessageException(NO_IMPORT_OF_OUT_DATED_QUIZ);
}
}
return action
.withEntityKey(action.getSingleSelection())
.withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
@ -196,7 +208,8 @@ public class QuizDiscoveryList implements TemplateComposer {
}
private void createDetailsForm(final QuizData quizData, final PageContext pc) {
this.pageService.formBuilder(pc, 4)
this.pageService.formBuilder(pc, 3)
.withEmptyCellSeparation(false)
.readonly(true)
.addField(FormBuilder.singleSelection(
QuizData.QUIZ_ATTR_LMS_SETUP_ID,

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.gui.form;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
@ -266,11 +268,11 @@ public final class Form implements FormBinding {
final String[] nameValue = StringUtils.split(split[i], Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR);
buffer.append(nameValue[0])
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(nameValue[1]);
.append(URLEncoder.encode(nameValue[1], StandardCharsets.UTF_8));
} else {
buffer.append(name)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(split[i]);
.append(URLEncoder.encode(split[i], StandardCharsets.UTF_8));
}
}
}

View file

@ -130,7 +130,7 @@ public class ModalInputDialog<T> extends Dialog {
shell.pack();
final Rectangle bounds = shell.getBounds();
final Rectangle bounds2 = super.getParent().getDisplay().getBounds();
bounds.width = 400;
//bounds.width = bounds.width;
bounds.x = (bounds2.width - bounds.width) / 2;
bounds.y = (bounds2.height - bounds.height) / 2;
shell.setBounds(bounds);

View file

@ -15,6 +15,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
@ -199,7 +200,7 @@ public class ExamDAOImpl implements ExamDAO {
null,
(exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(),
null,
BooleanUtils.toInteger(false));
BooleanUtils.toInteger(true));
this.examRecordMapper.insert(examRecord);
return examRecord;
@ -353,29 +354,35 @@ public class ExamDAOImpl implements ExamDAO {
private Result<Collection<Exam>> toDomainModel(final Long lmsSetupId, final Collection<ExamRecord> records) {
return Result.tryCatch(() -> {
final HashMap<String, ExamRecord> recordMapping = records
.stream()
.reduce(new HashMap<String, ExamRecord>(),
(map, record) -> Utils.mapPut(map, record.getExternalId(), record),
(map1, map2) -> Utils.mapPutAll(map1, map2));
return this.lmsAPIService
// map records
final Map<String, ExamRecord> recordMapping = records
.stream()
.collect(Collectors.toMap(r -> r.getExternalId(), Function.identity()));
// get and map quizzes
final Map<String, QuizData> quizzes = this.lmsAPIService
.getLmsAPITemplate(lmsSetupId)
.map(template -> template.getQuizzes(recordMapping.keySet()))
.getOrThrow()
.stream()
.map(result -> result.flatMap(quiz -> toDomainModel(recordMapping, quiz)).getOrThrow())
.flatMap(Result::skipOnError)
.collect(Collectors.toMap(q -> q.id, Function.identity()));
// collect Exam's
return recordMapping.entrySet()
.stream()
.map(entry -> toDomainModel(entry.getValue(), quizzes.get(entry.getKey())).getOrThrow())
.collect(Collectors.toList());
});
}
private Result<Exam> toDomainModel(
final HashMap<String, ExamRecord> recordMapping,
final ExamRecord record,
final QuizData quizData) {
return Result.tryCatch(() -> {
final ExamRecord record = recordMapping.get(quizData.id);
final Collection<String> supporter = (StringUtils.isNoneBlank(record.getSupporter()))
? Arrays.asList(StringUtils.split(record.getSupporter(), Constants.LIST_SEPARATOR_CHAR))
: null;
@ -384,17 +391,17 @@ public class ExamDAOImpl implements ExamDAO {
record.getId(),
record.getInstitutionId(),
record.getLmsSetupId(),
quizData.id,
quizData.name,
quizData.description,
quizData.startTime,
quizData.endTime,
quizData.startURL,
record.getExternalId(),
(quizData != null) ? quizData.name : Constants.EMPTY_NOTE,
(quizData != null) ? quizData.description : Constants.EMPTY_NOTE,
(quizData != null) ? quizData.startTime : null,
(quizData != null) ? quizData.endTime : null,
(quizData != null) ? quizData.startURL : Constants.EMPTY_NOTE,
ExamType.valueOf(record.getType()),
record.getQuitPassword(),
record.getOwner(),
supporter,
BooleanUtils.toBooleanObject(record.getActive()));
BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : 0));
});
}

View file

@ -16,7 +16,6 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
@ -64,12 +63,12 @@ public interface LmsAPIService {
public static Predicate<QuizData> quizFilterFunction(final FilterMap filterMap) {
final String name = filterMap.getQuizName();
final DateTime from = filterMap.getQuizFromTime();
final DateTime now = DateTime.now(DateTimeZone.UTC);
//final DateTime now = DateTime.now(DateTimeZone.UTC);
return q -> {
final boolean nameFilter = StringUtils.isBlank(name) || (q.name != null && q.name.contains(name));
final boolean startTimeFilter = (from == null) || (q.startTime != null && q.startTime.isAfter(from));
final boolean endTimeFilter = (now == null) || (q.endTime != null && q.endTime.isAfter(now));
return nameFilter && startTimeFilter && endTimeFilter;
// final boolean endTimeFilter = (now == null) || (q.endTime != null && q.endTime.isAfter(now));
return nameFilter && startTimeFilter /* && endTimeFilter */;
};
}

View file

@ -11,11 +11,13 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -93,7 +95,7 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
this.knownTokenAccessPaths.addAll(Arrays.asList(alternativeTokenRequestPaths));
}
this.allQuizzesSupplier = asyncService.createMemoizingCircuitBreaker(allQuizzesSupplier(lmsSetup));
this.allQuizzesSupplier = asyncService.createMemoizingCircuitBreaker(allQuizzesSupplier());
}
@Override
@ -143,8 +145,13 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
@Override
public Collection<Result<QuizData>> getQuizzes(final Set<String> ids) {
// TODO Auto-generated method stub
return null;
// TODO this can be improved in the future
return getQuizzes(new FilterMap())
.getOrElse(() -> Collections.emptyList())
.stream()
.filter(quiz -> ids.contains(quiz.id))
.map(quiz -> Result.of(quiz))
.collect(Collectors.toList());
}
private Result<LmsSetup> initRestTemplateAndRequestAccessToken() {
@ -209,7 +216,7 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
return template;
}
private Supplier<List<QuizData>> allQuizzesSupplier(final LmsSetup lmsSetup) {
private Supplier<List<QuizData>> allQuizzesSupplier() {
return () -> {
return initRestTemplateAndRequestAccessToken()
.map(this::collectAllQuizzes)

View file

@ -210,6 +210,7 @@ sebserver.quizdiscovery.info.pleaseSelect=Please Select a Quiz first
sebserver.quizdiscovery.action.list=Quiz Discovery
sebserver.quizdiscovery.action.import=Import as Exam
sebserver.quizdiscovery.quiz.import.out.dated=The Selected Quiz is in the past and can't be imported
sebserver.quizdiscovery.action.details=Show Details
sebserver.quizdiscovery.quiz.details.title=Quiz Details
@ -262,6 +263,10 @@ sebserver.exam.type.MANAGED=Managed
sebserver.exam.type.BYOD=Bring Your Own Device
sebserver.exam.type.VDI=VDI (Virtual Desktop Infrastructure)
sebserver.exam.status.UP_COMING=Up Coming
sebserver.exam.status.RUNNING=Running
sebserver.exam.status.FINISHED=Finished
sebserver.exam.indicator.list.actions=Selected Indicator
sebserver.exam.indicator.list.title=Indicators
sebserver.exam.indicator.list.column.type=Type

View file

@ -59,7 +59,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester {
});
assertNotNull(quizzes);
assertTrue(quizzes.content.size() == 5);
assertTrue(quizzes.content.size() == 7);
// for the inactive LmsSetup we should'nt get any quizzes
quizzes = new RestAPITestHelper()
@ -110,7 +110,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester {
});
assertNotNull(quizzes);
assertTrue(quizzes.content.size() == 5);
assertTrue(quizzes.content.size() == 7);
}
@Test