new seb restrcition strategy and minor fixes

This commit is contained in:
anhefti 2020-03-30 11:03:26 +02:00
parent 09eb17a630
commit b6466d8f1d
19 changed files with 207 additions and 85 deletions

View file

@ -117,6 +117,7 @@ public final class API {
public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config"; public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency"; public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction"; public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction";
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";

View file

@ -48,7 +48,7 @@ public final class Exam implements GrantEntity {
null, null,
null, null,
ExamStatus.FINISHED, ExamStatus.FINISHED,
Boolean.FALSE, // Boolean.FALSE,
null, null,
Boolean.FALSE, Boolean.FALSE,
null); null);
@ -117,8 +117,8 @@ public final class Exam implements GrantEntity {
@JsonProperty(EXAM.ATTR_STATUS) @JsonProperty(EXAM.ATTR_STATUS)
public final ExamStatus status; public final ExamStatus status;
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION) // @JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION)
public final Boolean lmsSebRestriction; // public final Boolean lmsSebRestriction;
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) @JsonProperty(EXAM.ATTR_BROWSER_KEYS)
public final String browserExamKeys; public final String browserExamKeys;
@ -145,7 +145,7 @@ public final class Exam implements GrantEntity {
@JsonProperty(EXAM.ATTR_OWNER) final String owner, @JsonProperty(EXAM.ATTR_OWNER) final String owner,
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter, @JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
@JsonProperty(EXAM.ATTR_STATUS) final ExamStatus status, @JsonProperty(EXAM.ATTR_STATUS) final ExamStatus status,
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION) final Boolean lmsSebRestriction, // @JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION) final Boolean lmsSebRestriction,
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys, @JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys,
@JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active, @JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active,
@JsonProperty(EXAM.ATTR_LASTUPDATE) final String lastUpdate) { @JsonProperty(EXAM.ATTR_LASTUPDATE) final String lastUpdate) {
@ -163,7 +163,7 @@ public final class Exam implements GrantEntity {
this.quitPassword = quitPassword; this.quitPassword = quitPassword;
this.owner = owner; this.owner = owner;
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime); this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
this.lmsSebRestriction = (lmsSebRestriction != null) ? lmsSebRestriction : Boolean.FALSE; // this.lmsSebRestriction = (lmsSebRestriction != null) ? lmsSebRestriction : Boolean.FALSE;
this.browserExamKeys = browserExamKeys; this.browserExamKeys = browserExamKeys;
this.active = (active != null) ? active : Boolean.TRUE; this.active = (active != null) ? active : Boolean.TRUE;
this.lastUpdate = lastUpdate; this.lastUpdate = lastUpdate;
@ -191,7 +191,7 @@ public final class Exam implements GrantEntity {
EXAM.ATTR_STATUS, EXAM.ATTR_STATUS,
ExamStatus.class, ExamStatus.class,
getStatusFromDate(this.startTime, this.endTime)); getStatusFromDate(this.startTime, this.endTime));
this.lmsSebRestriction = mapper.getBoolean(EXAM.ATTR_LMS_SEB_RESTRICTION); // this.lmsSebRestriction = mapper.getBoolean(EXAM.ATTR_LMS_SEB_RESTRICTION);
this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS); this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS);
this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE); this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE);
this.supporter = mapper.getStringSet(EXAM.ATTR_SUPPORTER); this.supporter = mapper.getStringSet(EXAM.ATTR_SUPPORTER);
@ -216,7 +216,7 @@ public final class Exam implements GrantEntity {
this.quitPassword = null; this.quitPassword = null;
this.owner = null; this.owner = null;
this.status = (status != null) ? status : getStatusFromDate(this.startTime, this.endTime); this.status = (status != null) ? status : getStatusFromDate(this.startTime, this.endTime);
this.lmsSebRestriction = null; // this.lmsSebRestriction = null;
this.browserExamKeys = null; this.browserExamKeys = null;
this.active = null; this.active = null;
this.supporter = null; this.supporter = null;
@ -314,9 +314,9 @@ public final class Exam implements GrantEntity {
return this.status; return this.status;
} }
public Boolean getLmsSebRestriction() { // public Boolean getLmsSebRestriction() {
return this.lmsSebRestriction; // return this.lmsSebRestriction;
} // }
public String getBrowserExamKeys() { public String getBrowserExamKeys() {
return this.browserExamKeys; return this.browserExamKeys;
@ -357,8 +357,8 @@ public final class Exam implements GrantEntity {
builder.append(this.supporter); builder.append(this.supporter);
builder.append(", status="); builder.append(", status=");
builder.append(this.status); builder.append(this.status);
builder.append(", lmsSebRestriction="); // builder.append(", lmsSebRestriction=");
builder.append(this.lmsSebRestriction); // builder.append(this.lmsSebRestriction);
builder.append(", browserExamKeys="); builder.append(", browserExamKeys=");
builder.append(this.browserExamKeys); builder.append(this.browserExamKeys);
builder.append(", active="); builder.append(", active=");

View file

@ -39,13 +39,13 @@ public final class LmsSetup implements GrantEntity, Activatable {
public enum Features { public enum Features {
COURSE_API, COURSE_API,
SEA_RESTRICTION, SEB_RESTRICTION,
COURSE_STRUCTURE_API, COURSE_STRUCTURE_API,
} }
public enum LmsType { public enum LmsType {
MOCKUP(Features.COURSE_API), MOCKUP(Features.COURSE_API),
OPEN_EDX(Features.COURSE_API, Features.SEA_RESTRICTION), OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
MOODLE(Features.COURSE_API); MOODLE(Features.COURSE_API);
public final EnumSet<Features> features; public final EnumSet<Features> features;

View file

@ -636,4 +636,16 @@ public final class Utils {
.toString(); .toString();
} }
public static String truncateText(final String text, final int toChars) {
if (text == null || toChars < 3) {
return text;
}
if (text.length() <= toChars) {
return text;
}
return StringUtils.truncate(text, toChars - 3) + "...";
}
} }

View file

@ -65,6 +65,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigDownload; import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigDownload;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; 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.CheckExamConsistency;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSebRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator; 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.GetExam;
@ -222,7 +223,7 @@ public class ExamForm implements TemplateComposer {
.call() .call()
.getOr(Collections.emptyList()); .getOr(Collections.emptyList());
if (warnings != null && !warnings.isEmpty()) { if (warnings != null && !warnings.isEmpty()) {
showConsistencyChecks(warnings, formContext.getParent()); showConsistencyChecks(warnings, formContext.getParent());
} }
} }
@ -234,7 +235,7 @@ public class ExamForm implements TemplateComposer {
formContext.getParent(), formContext.getParent(),
titleKey); titleKey);
if (warnings != null && !warnings.isEmpty()) { if (warnings != null && !warnings.isEmpty()) {
GridData gridData = (GridData) content.getLayoutData(); final GridData gridData = (GridData) content.getLayoutData();
gridData.verticalIndent = 10; gridData.verticalIndent = 10;
} }
@ -248,6 +249,12 @@ public class ExamForm implements TemplateComposer {
|| examStatus == ExamStatus.RUNNING || examStatus == ExamStatus.RUNNING
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN); && currentUser.get().hasRole(UserRole.EXAM_ADMIN);
final boolean sebRestrictionAvailable = testSebRestrictionAPI(exam); final boolean sebRestrictionAvailable = testSebRestrictionAPI(exam);
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
.getBuilder(CheckSebRestriction.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.call()
.onError(e -> log.error("Unexpected error while trying to verify seb restriction settings: ", e))
.getOr(false);
// The Exam form // The Exam form
final FormHandle<Exam> formHandle = this.pageService.formBuilder( final FormHandle<Exam> formHandle = this.pageService.formBuilder(
@ -399,12 +406,14 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_ENABLE_SEB_RESTRICTION) .newAction(ActionDefinition.EXAM_ENABLE_SEB_RESTRICTION)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(action -> ExamSebRestrictionSettings.setSebRestriction(action, true, this.restService)) .withExec(action -> ExamSebRestrictionSettings.setSebRestriction(action, true, this.restService))
.publishIf(() -> sebRestrictionAvailable && readonly && BooleanUtils.isFalse(exam.lmsSebRestriction)) .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
&& BooleanUtils.isFalse(isRestricted))
.newAction(ActionDefinition.EXAM_DISABLE_SEB_RESTRICTION) .newAction(ActionDefinition.EXAM_DISABLE_SEB_RESTRICTION)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(action -> ExamSebRestrictionSettings.setSebRestriction(action, false, this.restService)) .withExec(action -> ExamSebRestrictionSettings.setSebRestriction(action, false, this.restService))
.publishIf(() -> sebRestrictionAvailable && readonly && BooleanUtils.isTrue(exam.lmsSebRestriction)); .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
&& BooleanUtils.isTrue(isRestricted));
// additional data in read-only view // additional data in read-only view
if (readonly && !importFromQuizData) { if (readonly && !importFromQuizData) {
@ -413,8 +422,7 @@ public class ExamForm implements TemplateComposer {
this.widgetFactory.addFormSubContextHeader( this.widgetFactory.addFormSubContextHeader(
content, content,
CONFIG_LIST_TITLE_KEY, CONFIG_LIST_TITLE_KEY,
CONFIG_LIST_TITLE_TOOLTIP_KEY CONFIG_LIST_TITLE_TOOLTIP_KEY);
);
final EntityTable<ExamConfigurationMap> configurationTable = final EntityTable<ExamConfigurationMap> configurationTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigMappingsPage.class)) this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigMappingsPage.class))
@ -506,8 +514,7 @@ public class ExamForm implements TemplateComposer {
this.widgetFactory.addFormSubContextHeader( this.widgetFactory.addFormSubContextHeader(
content, content,
INDICATOR_LIST_TITLE_KEY, INDICATOR_LIST_TITLE_KEY,
INDICATOR_LIST_TITLE_TOOLTIP_KEY INDICATOR_LIST_TITLE_TOOLTIP_KEY);
);
final EntityTable<Indicator> indicatorTable = final EntityTable<Indicator> indicatorTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetIndicatorPage.class)) this.pageService.entityTableBuilder(this.restService.getRestCall(GetIndicatorPage.class))
@ -597,7 +604,7 @@ public class ExamForm implements TemplateComposer {
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId)) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId))
.call() .call()
.onError(t -> log.error("Failed to check SEB restriction API: ", t)) .onError(t -> log.error("Failed to check SEB restriction API: ", t))
.map(lmsSetup -> lmsSetup.lmsType.features.contains(Features.SEA_RESTRICTION)) .map(lmsSetup -> lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION))
.getOr(false); .getOr(false);
} }

View file

@ -114,11 +114,18 @@ public class RegisterPage implements TemplateComposer {
@Override @Override
public void compose(final PageContext pageContext) { public void compose(final PageContext pageContext) {
final Composite outer = new Composite(pageContext.getParent(), SWT.NONE);
final GridLayout outerLayout = new GridLayout();
outerLayout.marginLeft = 50;
outerLayout.marginRight = 50;
outer.setLayout(outerLayout);
outer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Composite parent = PageService.createManagedVScrolledComposite( final Composite parent = PageService.createManagedVScrolledComposite(
pageContext.getParent(), outer,
scrolledComposite -> { scrolledComposite -> {
final Composite result = new Composite(scrolledComposite, SWT.NONE); final Composite result = new Composite(scrolledComposite, SWT.NONE);
result.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); result.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true));
final GridLayout contentOutlaying = new GridLayout(); final GridLayout contentOutlaying = new GridLayout();
contentOutlaying.marginHeight = 0; contentOutlaying.marginHeight = 0;
contentOutlaying.marginWidth = 0; contentOutlaying.marginWidth = 0;
@ -147,7 +154,6 @@ public class RegisterPage implements TemplateComposer {
.sorted(ResourceService.RESOURCE_COMPARATOR) .sorted(ResourceService.RESOURCE_COMPARATOR)
.collect(Collectors.toList()); .collect(Collectors.toList());
this.widgetFactory.labelLocalizedTitle(parent, TITLE_TEXT_KEY); this.widgetFactory.labelLocalizedTitle(parent, TITLE_TEXT_KEY);
// The UserAccount form // The UserAccount form

View file

@ -198,6 +198,9 @@ public final class PageAction {
PageAction.this.getName(), PageAction.this.getName(),
e.getMessage(), e.getMessage(),
Utils.getErrorCauseMessage(e)); Utils.getErrorCauseMessage(e));
PageAction.this.pageContext.notifyError(
PageContext.UNEXPECTED_ERROR_KEY,
e);
return Result.ofError(e); return Result.ofError(e);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to execute action: {} | error: {} | cause: {}", log.error("Failed to execute action: {} | error: {} | cause: {}",

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 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 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.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class CheckSebRestriction extends RestCall<Boolean> {
public CheckSebRestriction() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.EXAM_SEB_RESTRICTION,
new TypeReference<Boolean>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT);
}
}

View file

@ -19,6 +19,7 @@ import org.eclipse.swt.widgets.Listener;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils;
public final class SingleSelection extends Combo implements Selection { public final class SingleSelection extends Combo implements Selection {
@ -41,7 +42,7 @@ public final class SingleSelection extends Combo implements Selection {
this.valueMapping.clear(); this.valueMapping.clear();
this.keyMapping.clear(); this.keyMapping.clear();
this.valueMapping.addAll(mapping.stream() this.valueMapping.addAll(mapping.stream()
.map(t -> t._2) .map(t -> Utils.truncateText(t._2, 100))
.collect(Collectors.toList())); .collect(Collectors.toList()));
this.keyMapping.addAll(mapping.stream() this.keyMapping.addAll(mapping.stream()
.map(t -> t._1) .map(t -> t._1)

View file

@ -8,6 +8,44 @@
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.rap.rwt.widgets.WidgetUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DateTime;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@ -19,29 +57,6 @@ import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.DefaultPageLayout; import ch.ethz.seb.sebserver.gui.service.page.impl.DefaultPageLayout;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.rap.rwt.widgets.WidgetUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY;
@Lazy @Lazy
@Service @Service
@ -151,7 +166,7 @@ public class WidgetFactory {
TEXT_H1("h1"), TEXT_H1("h1"),
TEXT_H2("h2"), TEXT_H2("h2"),
TEXT_H3("h3"), TEXT_H3("h3"),
SUBTITLE( "subtitle"), SUBTITLE("subtitle"),
IMAGE_BUTTON("imageButton"), IMAGE_BUTTON("imageButton"),
TEXT_ACTION("action"), TEXT_ACTION("action"),
TEXT_READONLY("readonlyText"), TEXT_READONLY("readonlyText"),
@ -179,7 +194,9 @@ public class WidgetFactory {
LIST_NAVIGATION("list-nav"), LIST_NAVIGATION("list-nav"),
PLAIN_PWD("pwdplain"), PLAIN_PWD("pwdplain"),
COLOR_BOX("colorbox") COLOR_BOX("colorbox"),
REGISTER_FORM("register")
; ;
@ -228,8 +245,8 @@ public class WidgetFactory {
labelLocalizedTitle.setLayoutData(gridData); labelLocalizedTitle.setLayoutData(gridData);
// sub title if defined in language properties // sub title if defined in language properties
LocTextKey subTitleTextKey = new LocTextKey(title.name + SUB_TITLE_TExT_SUFFIX); final LocTextKey subTitleTextKey = new LocTextKey(title.name + SUB_TITLE_TExT_SUFFIX);
if (i18nSupport.hasText(subTitleTextKey)) { if (this.i18nSupport.hasText(subTitleTextKey)) {
final GridData gridDataSub = new GridData(SWT.FILL, SWT.FILL, true, false); final GridData gridDataSub = new GridData(SWT.FILL, SWT.FILL, true, false);
final Label labelLocalizedSubTitle = labelLocalized( final Label labelLocalizedSubTitle = labelLocalized(
defaultPageLayout, defaultPageLayout,
@ -241,20 +258,21 @@ public class WidgetFactory {
return defaultPageLayout; return defaultPageLayout;
} }
public void addFormSubContextHeader(final Composite parent, final LocTextKey titleTextKey, final LocTextKey tooltipTextKey) { public void addFormSubContextHeader(final Composite parent, final LocTextKey titleTextKey,
GridData gridData = new GridData(SWT.FILL, SWT.BOTTOM, true, false); final LocTextKey tooltipTextKey) {
final GridData gridData = new GridData(SWT.FILL, SWT.BOTTOM, true, false);
gridData.horizontalIndent = 8; gridData.horizontalIndent = 8;
gridData.verticalIndent = 10; gridData.verticalIndent = 10;
Label subContextLabel = labelLocalized( final Label subContextLabel = labelLocalized(
parent, parent,
CustomVariant.TEXT_H3, CustomVariant.TEXT_H3,
titleTextKey, titleTextKey,
tooltipTextKey); tooltipTextKey);
subContextLabel.setLayoutData(gridData); subContextLabel.setLayoutData(gridData);
GridData gridData1 = new GridData(SWT.FILL, SWT.BOTTOM, true, false); final GridData gridData1 = new GridData(SWT.FILL, SWT.BOTTOM, true, false);
gridData1.heightHint = 5; gridData1.heightHint = 5;
gridData1.horizontalIndent = 8; gridData1.horizontalIndent = 8;
Label subContextSeparator = labelSeparator(parent); final Label subContextSeparator = labelSeparator(parent);
subContextSeparator.setLayoutData(gridData1); subContextSeparator.setLayoutData(gridData1);
} }
@ -289,7 +307,7 @@ public class WidgetFactory {
public Composite createWarningPanel(final Composite parent) { public Composite createWarningPanel(final Composite parent) {
final Composite composite = new Composite(parent, SWT.NONE); final Composite composite = new Composite(parent, SWT.NONE);
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
composite.setLayoutData(gridData); composite.setLayoutData(gridData);
final GridLayout gridLayout = new GridLayout(1, true); final GridLayout gridLayout = new GridLayout(1, true);
gridLayout.marginWidth = 20; gridLayout.marginWidth = 20;
@ -379,7 +397,7 @@ public class WidgetFactory {
public Label labelLocalizedTitle(final Composite content, final LocTextKey locTextKey) { public Label labelLocalizedTitle(final Composite content, final LocTextKey locTextKey) {
final Label labelLocalized = labelLocalized(content, CustomVariant.TEXT_H1, locTextKey); final Label labelLocalized = labelLocalized(content, CustomVariant.TEXT_H1, locTextKey);
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
labelLocalized.setLayoutData(gridData); labelLocalized.setLayoutData(gridData);
return labelLocalized; return labelLocalized;
} }

View file

@ -107,7 +107,8 @@ public class ExamDAOImpl implements ExamDAO {
.execute() .execute()
: this.examRecordMapper.selectByExample() : this.examRecordMapper.selectByExample()
.build() .build()
.execute()).flatMap(this::toDomainModel); .execute())
.flatMap(this::toDomainModel);
} }
@Override @Override
@ -210,9 +211,7 @@ public class ExamDAOImpl implements ExamDAO {
(exam.status != null) (exam.status != null)
? exam.status.name() ? exam.status.name()
: null, : null,
(exam.lmsSebRestriction != null) 1, // seb restriction (deprecated)
? BooleanUtils.toIntegerObject(exam.lmsSebRestriction)
: null,
null, // updating null, // updating
null, // lastUpdate null, // lastUpdate
null // active null // active
@ -259,6 +258,10 @@ 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 another institution tries to import an exam that already exists
if (!exam.institutionId.equals(examRecord.getInstitutionId())) {
throw new IllegalStateException("Exam cannot be imported twice from different institutions");
}
final ExamRecord newRecord = new ExamRecord( final ExamRecord newRecord = new ExamRecord(
examRecord.getId(), examRecord.getId(),
null, null, null, null, null, null, null, null, null, null,
@ -266,7 +269,7 @@ public class ExamDAOImpl implements ExamDAO {
null, // quitPassword null, // quitPassword
null, // browser keys null, // browser keys
null, // status null, // status
null, // lmsSebRestriction null, // lmsSebRestriction (deprecated)
null, // updating null, // updating
null, // lastUpdate null, // lastUpdate
BooleanUtils.toIntegerObject(exam.active)); BooleanUtils.toIntegerObject(exam.active));
@ -288,7 +291,7 @@ public class ExamDAOImpl implements ExamDAO {
null, // quitPassword null, // quitPassword
null, // browser keys null, // browser keys
(exam.status != null) ? exam.status.name() : ExamStatus.UP_COMING.name(), (exam.status != null) ? exam.status.name() : ExamStatus.UP_COMING.name(),
BooleanUtils.toInteger(exam.lmsSebRestriction), 1, // seb restriction (deprecated)
BooleanUtils.toInteger(false), BooleanUtils.toInteger(false),
null, // lastUpdate null, // lastUpdate
BooleanUtils.toInteger(true)); BooleanUtils.toInteger(true));
@ -752,7 +755,7 @@ public class ExamDAOImpl implements ExamDAO {
record.getOwner(), record.getOwner(),
supporter, supporter,
status, status,
BooleanUtils.toBooleanObject((quizData != null) ? record.getLmsSebRestriction() : null), // BooleanUtils.toBooleanObject((quizData != null) ? record.getLmsSebRestriction() : null),
record.getBrowserKeys(), record.getBrowserKeys(),
BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : null), BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : null),
record.getLastupdate()); record.getLastupdate());

View file

@ -17,13 +17,15 @@ public interface ExamAdminService {
* *
* @param exam The Exam to add the default indicator * @param exam The Exam to add the default indicator
* @return the Exam with added default indicator */ * @return the Exam with added default indicator */
Result<Exam> addDefaultIndicator(final Exam exam); Result<Exam> addDefaultIndicator(Exam exam);
/** Applies all additional SEB restriction attributes that are defined by the /** Applies all additional SEB restriction attributes that are defined by the
* type of the LMS of a given Exam to this given Exam. * type of the LMS of a given Exam to this given Exam.
* *
* @param exam the Exam to apply all additional SEB restriction attributes * @param exam the Exam to apply all additional SEB restriction attributes
* @return the Exam */ * @return the Exam */
Result<Exam> applyAdditionalSEBRestrictions(final Exam exam); Result<Exam> applyAdditionalSEBRestrictions(Exam exam);
Result<Boolean> isRestricted(Exam exam);
} }

View file

@ -127,4 +127,15 @@ public class ExamAdminServiceImpl implements ExamAdminService {
}); });
} }
@Override
public Result<Boolean> isRestricted(final Exam exam) {
if (exam == null) {
return Result.of(false);
}
return this.lmsAPIService
.getLmsAPITemplate(exam.lmsSetupId)
.map(lmsAPI -> !lmsAPI.getSebClientRestriction(exam).hasError());
}
} }

View file

@ -121,7 +121,6 @@ public class SebRestrictionServiceImpl implements SebRestrictionService {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
exam.supporter, exam.supporter,
exam.status, exam.status,
exam.lmsSebRestriction,
(browserExamKeys != null && !browserExamKeys.isEmpty()) (browserExamKeys != null && !browserExamKeys.isEmpty())
? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR) ? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR)
: StringUtils.EMPTY, : StringUtils.EMPTY,
@ -157,7 +156,7 @@ public class SebRestrictionServiceImpl implements SebRestrictionService {
public Result<Exam> applySebClientRestriction(final Exam exam) { public Result<Exam> applySebClientRestriction(final Exam exam) {
if (!this.lmsAPIService if (!this.lmsAPIService
.getLmsSetup(exam.lmsSetupId) .getLmsSetup(exam.lmsSetupId)
.getOrThrow().lmsType.features.contains(Features.SEA_RESTRICTION)) { .getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
return Result.of(exam); return Result.of(exam);
} }

View file

@ -14,7 +14,6 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -31,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
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.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
@ -46,19 +46,22 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
private final ExamConfigurationMapDAO examConfigurationMapDAO; private final ExamConfigurationMapDAO examConfigurationMapDAO;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final ExamUpdateHandler examUpdateHandler; private final ExamUpdateHandler examUpdateHandler;
private final ExamAdminService examAdminService;
protected ExamConfigUpdateServiceImpl( protected ExamConfigUpdateServiceImpl(
final ExamDAO examDAO, final ExamDAO examDAO,
final ConfigurationDAO configurationDAO, final ConfigurationDAO configurationDAO,
final ExamConfigurationMapDAO examConfigurationMapDAO, final ExamConfigurationMapDAO examConfigurationMapDAO,
final ExamSessionService examSessionService, final ExamSessionService examSessionService,
final ExamUpdateHandler examUpdateHandler) { final ExamUpdateHandler examUpdateHandler,
final ExamAdminService examAdminService) {
this.examDAO = examDAO; this.examDAO = examDAO;
this.configurationDAO = configurationDAO; this.configurationDAO = configurationDAO;
this.examConfigurationMapDAO = examConfigurationMapDAO; this.examConfigurationMapDAO = examConfigurationMapDAO;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.examUpdateHandler = examUpdateHandler; this.examUpdateHandler = examUpdateHandler;
this.examAdminService = examAdminService;
} }
// processing: // processing:
@ -127,7 +130,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key) // generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
for (final Exam exam : exams) { for (final Exam exam : exams) {
if (exam.getStatus() == ExamStatus.RUNNING && BooleanUtils.isTrue(exam.lmsSebRestriction)) { if (exam.getStatus() == ExamStatus.RUNNING || this.examAdminService.isRestricted(exam).getOr(false)) {
this.examUpdateHandler this.examUpdateHandler
.getSebRestrictionService() .getSebRestrictionService()
.applySebClientRestriction(exam) .applySebClientRestriction(exam)
@ -197,7 +201,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
.getOrThrow(); .getOrThrow();
// update seb client restriction if the feature is activated for the exam // update seb client restriction if the feature is activated for the exam
if (exam.lmsSebRestriction) { if (this.examAdminService.isRestricted(exam).getOr(false)) {
this.examUpdateHandler this.examUpdateHandler
.getSebRestrictionService() .getSebRestrictionService()
.applySebClientRestriction(exam) .applySebClientRestriction(exam)

View file

@ -125,7 +125,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
// if SEB restriction is not available no consistency violation message is added // if SEB restriction is not available no consistency violation message is added
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId) final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
.getOr(null); .getOr(null);
if (lmsSetup != null && lmsSetup.lmsType.features.contains(Features.SEA_RESTRICTION)) { if (lmsSetup != null && lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
this.lmsAPIService.getLmsAPITemplate(exam.lmsSetupId) this.lmsAPIService.getLmsAPITemplate(exam.lmsSetupId)
.map(t -> { .map(t -> {
if (t.testCourseRestrictionAPI().isOk()) { if (t.testCourseRestrictionAPI().isOk()) {

View file

@ -22,7 +22,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; import javax.validation.Valid;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -227,6 +226,24 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
.getOrThrow(); .getOrThrow();
} }
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Boolean checkSebRestriction(
@PathVariable final Long modelId,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
checkReadPrivilege(institutionId);
return this.examDAO.byPK(modelId)
.flatMap(this.examAdminService::isRestricted)
.getOrThrow();
}
// **************************************************************************** // ****************************************************************************
// **** SEB Restriction // **** SEB Restriction
@ -266,7 +283,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
return this.entityDAO.byPK(examId) return this.entityDAO.byPK(examId)
.flatMap(this.authorization::checkModify) .flatMap(this.authorization::checkModify)
.flatMap(exam -> this.sebRestrictionService.saveSebRestrictionToExam(exam, sebRestriction)) .flatMap(exam -> this.sebRestrictionService.saveSebRestrictionToExam(exam, sebRestriction))
.flatMap(exam -> BooleanUtils.isTrue(exam.lmsSebRestriction) .flatMap(exam -> this.examAdminService.isRestricted(exam).getOrThrow()
? this.applySebRestriction(exam, true) ? this.applySebRestriction(exam, true)
: Result.of(exam)) : Result.of(exam))
.getOrThrow(); .getOrThrow();
@ -397,7 +414,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId) final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
.getOrThrow(); .getOrThrow();
if (!lmsSetup.lmsType.features.contains(Features.SEA_RESTRICTION)) { if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
return Result.ofError(new UnsupportedOperationException( return Result.ofError(new UnsupportedOperationException(
"SEB Restriction feature not available for LMS type: " + lmsSetup.lmsType)); "SEB Restriction feature not available for LMS type: " + lmsSetup.lmsType));
} }
@ -405,7 +422,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
if (restrict) { if (restrict) {
if (!this.lmsAPIService if (!this.lmsAPIService
.getLmsSetup(exam.lmsSetupId) .getLmsSetup(exam.lmsSetupId)
.getOrThrow().lmsType.features.contains(Features.SEA_RESTRICTION)) { .getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
return Result.ofError(new APIMessageException( return Result.ofError(new APIMessageException(
APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT

View file

@ -809,7 +809,6 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
null, null, null, null,
Utils.immutableCollectionOf(userId), Utils.immutableCollectionOf(userId),
ExamStatus.RUNNING, ExamStatus.RUNNING,
true,
null, null,
true, true,
null); null);

View file

@ -65,7 +65,6 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
exam.owner, exam.owner,
Arrays.asList("user5"), Arrays.asList("user5"),
null, null,
true,
null, null,
true, true,
null)) null))
@ -97,7 +96,6 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
exam.owner, exam.owner,
Arrays.asList("user2"), Arrays.asList("user2"),
null, null,
true,
null, null,
true, true,
null)) null))