SEBSERV-29 SEBSERV-30 Indicator, ColorSelector, edx integration
This commit is contained in:
parent
584a900529
commit
100bf77bd4
38 changed files with 896 additions and 268 deletions
5
pom.xml
5
pom.xml
|
@ -289,6 +289,11 @@
|
|||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptonode.jncryptor</groupId>
|
||||
<artifactId>jncryptor</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
|
|
|
@ -28,15 +28,23 @@ public final class Constants {
|
|||
public static final String FORM_URL_ENCODED_SEPARATOR = "&";
|
||||
public static final String FORM_URL_ENCODED_NAME_VALUE_SEPARATOR = "=";
|
||||
|
||||
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
public static final String DEFAULT_DISPLAY_DATE_FORMAT = "MM-dd-yyy HH:mm";
|
||||
public static final String TIME_ZONE_OFFSET_TAIL_FORMAT = "|ZZ";
|
||||
|
||||
public static final DateTimeFormatter STANDARD_DATE_TIME_FORMATTER = DateTimeFormat
|
||||
.forPattern(DEFAULT_DATE_TIME_FORMAT)
|
||||
.withZoneUTC();
|
||||
|
||||
/** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */
|
||||
// TODO check if this works with DEFAULT_DATE_TIME_FORMAT
|
||||
@Deprecated
|
||||
public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat
|
||||
.forPattern("yyyy-MM-dd HH:mm:ss")
|
||||
.withZoneUTC();
|
||||
|
||||
/** Date-Time formatter with milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss.S */
|
||||
@Deprecated
|
||||
public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_MILLIS = DateTimeFormat
|
||||
.forPattern("yyyy-MM-dd HH:mm:ss.S")
|
||||
.withZoneUTC();
|
||||
|
|
|
@ -20,10 +20,10 @@ public final class API {
|
|||
public static final String PARAM_INSTITUTION_ID = "institutionId";
|
||||
public static final String PARAM_MODEL_ID = "modelId";
|
||||
public static final String PARAM_MODEL_ID_LIST = "modelIds";
|
||||
public static final String PARAM_PARENT_MODEL_ID = "parentModelId";
|
||||
public static final String PARAM_ENTITY_TYPE = "entityType";
|
||||
public static final String PARAM_BULK_ACTION_TYPE = "bulkActionType";
|
||||
public static final String PARAM_LMS_SETUP_ID = "lmsSetupId";
|
||||
public static final String PARAM_EXAM_ID = "examId";
|
||||
|
||||
public static final String INSTITUTION_VAR_PATH_SEGMENT = "/{" + PARAM_INSTITUTION_ID + "}";
|
||||
public static final String MODEL_ID_VAR_PATH_SEGMENT = "/{" + PARAM_MODEL_ID + "}";
|
||||
|
@ -54,7 +54,7 @@ public final class API {
|
|||
|
||||
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
||||
|
||||
public static final String EXAM_INDICATOR_ENDPOINT = "/exam/indicator";
|
||||
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
||||
|
||||
public static final String USER_ACTIVITY_LOG_ENDPOINT = "/useractivity";
|
||||
|
||||
|
@ -75,4 +75,20 @@ public final class API {
|
|||
public static final String PATH_VAR_ACTIVE = MODEL_ID_VAR_PATH_SEGMENT + ACTIVE_PATH_SEGMENT;
|
||||
public static final String PATH_VAR_INACTIVE = MODEL_ID_VAR_PATH_SEGMENT + INACTIVE_PATH_SEGMENT;
|
||||
|
||||
// *************************
|
||||
// ** Exam API
|
||||
// *************************
|
||||
|
||||
public static final String EXAM_API_PARAM_EXAM_ID = "examId";
|
||||
|
||||
public static final String EXAM_API_SEB_CONNECTION_TOKEN = "seb-connection-token";
|
||||
|
||||
public static final String EXAM_API_HANDSHAKE_ENDPOINT = "/handshake";
|
||||
|
||||
public static final String EXAM_API_CONFIGURATION_REQUEST_ENDPOINT = "/examconfig";
|
||||
|
||||
public static final String EXAM_API_PING_ENDPOINT = "/sebping";
|
||||
|
||||
public static final String EXAM_API_EVENT_ENDPOINT = "/sebevent";
|
||||
|
||||
}
|
||||
|
|
|
@ -48,12 +48,12 @@ public final class Indicator implements GrantEntity {
|
|||
public final Long examId;
|
||||
|
||||
@JsonProperty(INDICATOR.ATTR_NAME)
|
||||
@NotNull
|
||||
@NotNull(message = "indicator:name:notNull")
|
||||
@Size(min = 3, max = 255, message = "indicator:name:size:{min}:{max}:${validatedValue}")
|
||||
public final String name;
|
||||
|
||||
@JsonProperty(INDICATOR.ATTR_TYPE)
|
||||
@NotNull
|
||||
@NotNull(message = "indicator:type:notNull")
|
||||
public final IndicatorType type;
|
||||
|
||||
@JsonProperty(INDICATOR.ATTR_COLOR)
|
||||
|
@ -62,12 +62,14 @@ public final class Indicator implements GrantEntity {
|
|||
@JsonProperty(THRESHOLD.REFERENCE_NAME)
|
||||
public final List<Threshold> thresholds;
|
||||
|
||||
public final String examOwner;
|
||||
|
||||
@JsonCreator
|
||||
public Indicator(
|
||||
@JsonProperty(INDICATOR.ATTR_ID) final Long id,
|
||||
@JsonProperty(EXAM.ATTR_INSTITUTION_ID) final Long institutionId,
|
||||
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
|
||||
@JsonProperty(INDICATOR.ATTR_EXAM_ID) final Long examId,
|
||||
@JsonProperty(EXAM.ATTR_OWNER) final String examOwner,
|
||||
@JsonProperty(INDICATOR.ATTR_NAME) final String name,
|
||||
@JsonProperty(INDICATOR.ATTR_TYPE) final IndicatorType type,
|
||||
@JsonProperty(INDICATOR.ATTR_COLOR) final String defaultColor,
|
||||
|
@ -76,6 +78,7 @@ public final class Indicator implements GrantEntity {
|
|||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
this.examId = examId;
|
||||
this.examOwner = examOwner;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.defaultColor = defaultColor;
|
||||
|
@ -86,6 +89,7 @@ public final class Indicator implements GrantEntity {
|
|||
this.id = null;
|
||||
this.institutionId = exam.institutionId;
|
||||
this.examId = exam.id;
|
||||
this.examOwner = exam.owner;
|
||||
this.name = postParams.getString(Domain.INDICATOR.ATTR_NAME);
|
||||
this.type = postParams.getEnum(Domain.INDICATOR.ATTR_TYPE, IndicatorType.class);
|
||||
this.defaultColor = postParams.getString(Domain.INDICATOR.ATTR_COLOR);
|
||||
|
@ -119,7 +123,7 @@ public final class Indicator implements GrantEntity {
|
|||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return null;
|
||||
return this.examOwner;
|
||||
}
|
||||
|
||||
public Long getExamId() {
|
||||
|
@ -145,14 +149,18 @@ public final class Indicator implements GrantEntity {
|
|||
+ this.defaultColor + ", thresholds=" + this.thresholds + "]";
|
||||
}
|
||||
|
||||
public static Indicator createNew(final Long institutionId, final Exam exam) {
|
||||
return new Indicator(null, institutionId, exam.id, exam.owner, null, null, null, null);
|
||||
}
|
||||
|
||||
public static final class Threshold {
|
||||
|
||||
@JsonProperty(THRESHOLD.ATTR_ID)
|
||||
public final Long id;
|
||||
|
||||
@JsonProperty(THRESHOLD.ATTR_INDICATOR_ID)
|
||||
@NotNull
|
||||
public final Long indicatorId;
|
||||
// @JsonProperty(THRESHOLD.ATTR_ID)
|
||||
// public final Long id;
|
||||
//
|
||||
// @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID)
|
||||
// @NotNull
|
||||
// public final Long indicatorId;
|
||||
|
||||
@JsonProperty(THRESHOLD.ATTR_VALUE)
|
||||
@NotNull
|
||||
|
@ -163,24 +171,24 @@ public final class Indicator implements GrantEntity {
|
|||
|
||||
@JsonCreator
|
||||
public Threshold(
|
||||
@JsonProperty(THRESHOLD.ATTR_ID) final Long id,
|
||||
@JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) final Long indicatorId,
|
||||
// @JsonProperty(THRESHOLD.ATTR_ID) final Long id,
|
||||
// @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) final Long indicatorId,
|
||||
@JsonProperty(THRESHOLD.ATTR_VALUE) final Double value,
|
||||
@JsonProperty(THRESHOLD.ATTR_COLOR) final String color) {
|
||||
|
||||
this.id = id;
|
||||
this.indicatorId = indicatorId;
|
||||
// this.id = id;
|
||||
// this.indicatorId = indicatorId;
|
||||
this.value = value;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public Long getIndicatorId() {
|
||||
return this.indicatorId;
|
||||
}
|
||||
// public Long getId() {
|
||||
// return this.id;
|
||||
// }
|
||||
//
|
||||
// public Long getIndicatorId() {
|
||||
// return this.indicatorId;
|
||||
// }
|
||||
|
||||
public Double getValue() {
|
||||
return this.value;
|
||||
|
@ -190,12 +198,12 @@ public final class Indicator implements GrantEntity {
|
|||
return this.color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Threshold [id=" + this.id + ", indicatorId=" + this.indicatorId + ", value=" + this.value
|
||||
+ ", color=" + this.color
|
||||
+ "]";
|
||||
}
|
||||
// @Override
|
||||
// public String toString() {
|
||||
// return "Threshold [id=" + this.id + ", indicatorId=" + this.indicatorId + ", value=" + this.value
|
||||
// + ", color=" + this.color
|
||||
// + "]";
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -105,12 +105,16 @@ public final class QuizData implements GrantEntity {
|
|||
this.lmsType = lmsType;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.startTime = LocalDateTime
|
||||
.parse(startTime, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS)
|
||||
.toDateTime(DateTimeZone.UTC);
|
||||
this.endTime = LocalDateTime
|
||||
.parse(endTime, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS)
|
||||
.toDateTime(DateTimeZone.UTC);
|
||||
this.startTime = (startTime != null)
|
||||
? LocalDateTime
|
||||
.parse(startTime, Constants.STANDARD_DATE_TIME_FORMATTER)
|
||||
.toDateTime(DateTimeZone.UTC)
|
||||
: null;
|
||||
this.endTime = (endTime != null)
|
||||
? LocalDateTime
|
||||
.parse(endTime, Constants.STANDARD_DATE_TIME_FORMATTER)
|
||||
.toDateTime(DateTimeZone.UTC)
|
||||
: null;
|
||||
this.startURL = startURL;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gbl.model.seb;
|
||||
|
||||
public class PingResponse {
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.gbl.model.seb;
|
||||
|
||||
public class RunningExams {
|
||||
// TODO
|
||||
}
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.content;
|
|||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -36,6 +37,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.PageUtils;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||
|
@ -43,7 +45,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
|
@ -71,6 +72,8 @@ public class ExamForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.exam.indicator.list.column.name");
|
||||
private final static LocTextKey thresholdColumnKey =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.thresholds");
|
||||
private final static LocTextKey emptySelectionTextKey =
|
||||
new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
|
||||
|
||||
protected ExamForm(
|
||||
final PageFormService pageFormService,
|
||||
|
@ -124,11 +127,6 @@ public class ExamForm implements TemplateComposer {
|
|||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
|
||||
final boolean writeGrant = userGrantCheck.w();
|
||||
final boolean modifyGrant = userGrantCheck.m();
|
||||
final boolean institutionActive = restService.getBuilder(GetInstitution.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.getInstitutionId()))
|
||||
.call()
|
||||
.map(inst -> inst.active)
|
||||
.getOr(false);
|
||||
|
||||
// The Exam form
|
||||
final FormHandle<Exam> formHandle = this.pageFormService.getBuilder(
|
||||
|
@ -140,7 +138,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.putStaticValue(
|
||||
Domain.EXAM.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(exam.getInstitutionId()))
|
||||
.putStaticValueIf(isNew,
|
||||
.putStaticValue(
|
||||
Domain.EXAM.ATTR_OWNER,
|
||||
user.uuid)
|
||||
.putStaticValueIf(isNotNew,
|
||||
|
@ -192,11 +190,48 @@ public class ExamForm implements TemplateComposer {
|
|||
"sebserver.exam.form.type",
|
||||
String.valueOf(exam.type),
|
||||
this.resourceService::examTypeResources))
|
||||
.addField(FormBuilder.multiComboSelection(
|
||||
Domain.EXAM.ATTR_SUPPORTER,
|
||||
"sebserver.exam.form.supporter",
|
||||
StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
|
||||
this.resourceService::examSupporterResources)
|
||||
.withCondition(isNotNew))
|
||||
|
||||
.buildFor(importFromQuizData
|
||||
? restService.getRestCall(ImportAsExam.class)
|
||||
: restService.getRestCall(SaveExam.class));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
formContext.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && readonly)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_SAVE)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.publishIf(() -> !readonly && modifyGrant)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_CANCEL_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData))
|
||||
.withExec(ExamForm::cancelModify)
|
||||
.withConfirm("sebserver.overall.action.modify.cancel.confirm")
|
||||
.publishIf(() -> !readonly)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_DEACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(restService::activation)
|
||||
.withConfirm(PageUtils.confirmDeactivation(exam, restService))
|
||||
.publishIf(() -> writeGrant && readonly && exam.isActive())
|
||||
|
||||
.createAction(ActionDefinition.EXAM_ACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(restService::activation)
|
||||
.publishIf(() -> writeGrant && readonly && !exam.isActive());
|
||||
|
||||
// additional data in read-only view
|
||||
if (readonly) {
|
||||
|
||||
// List of Indicators
|
||||
|
@ -227,24 +262,25 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
.compose(content);
|
||||
|
||||
formContext.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_INDICATOR_NEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||
.withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent())
|
||||
|
||||
.createAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
||||
.withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent());
|
||||
|
||||
// TODO List of attached SEB Configurations
|
||||
|
||||
}
|
||||
|
||||
// propagate content actions to action-pane
|
||||
formContext.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_SAVE)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.publishIf(() -> !readonly && modifyGrant)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_CANCEL_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData))
|
||||
.withExec(ExamForm::cancelModify)
|
||||
.withConfirm("sebserver.overall.action.modify.cancel.confirm")
|
||||
.publishIf(() -> !readonly);
|
||||
}
|
||||
|
||||
private Result<Exam> getExistingExam(final EntityKey entityKey, final RestService restService) {
|
||||
|
|
|
@ -10,11 +10,7 @@ package ch.ethz.seb.sebserver.gui.content;
|
|||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -32,7 +28,6 @@ import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
|||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
|
||||
|
@ -136,9 +131,9 @@ public class ExamList implements TemplateComposer {
|
|||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
|
||||
pageContext.clearEntityKeys()
|
||||
|
||||
.createAction(ActionDefinition.TEST_ACTION)
|
||||
.withExec(this::testModalInput)
|
||||
.publish()
|
||||
// .createAction(ActionDefinition.TEST_ACTION)
|
||||
// .withExec(this::testModalInput)
|
||||
// .publish()
|
||||
|
||||
.createAction(ActionDefinition.EXAM_IMPORT)
|
||||
.publishIf(userGrant::im)
|
||||
|
@ -172,29 +167,29 @@ public class ExamList implements TemplateComposer {
|
|||
.getText("sebserver.exam.type." + exam.type.name());
|
||||
}
|
||||
|
||||
private Action testModalInput(final Action action) {
|
||||
final ModalInputDialog<String> dialog = new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.widgetFactory);
|
||||
|
||||
dialog.open(
|
||||
"Test Input Dialog",
|
||||
action.pageContext(),
|
||||
value -> {
|
||||
System.out.println("********************** value: " + value);
|
||||
},
|
||||
pc -> {
|
||||
final Composite parent = pc.getParent();
|
||||
final Label label = new Label(parent, SWT.NONE);
|
||||
label.setText("Please Enter:");
|
||||
label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
final Text text = new Text(parent, SWT.LEFT | SWT.BORDER);
|
||||
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
return () -> text.getText();
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
// private Action testModalInput(final Action action) {
|
||||
// final ModalInputDialog<String> dialog = new ModalInputDialog<>(
|
||||
// action.pageContext().getParent().getShell(),
|
||||
// this.widgetFactory);
|
||||
//
|
||||
// dialog.open(
|
||||
// "Test Input Dialog",
|
||||
// action.pageContext(),
|
||||
// value -> {
|
||||
// System.out.println("********************** value: " + value);
|
||||
// },
|
||||
// pc -> {
|
||||
// final Composite parent = pc.getParent();
|
||||
// final Label label = new Label(parent, SWT.NONE);
|
||||
// label.setText("Please Enter:");
|
||||
// label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
//
|
||||
// final Text text = new Text(parent, SWT.LEFT | SWT.BORDER);
|
||||
// text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
// return () -> text.getText();
|
||||
// });
|
||||
//
|
||||
// return action;
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -8,20 +8,47 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
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;
|
||||
|
||||
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.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.form.PageFormService;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class IndicatorForm implements TemplateComposer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(IndicatorForm.class);
|
||||
|
||||
private final static LocTextKey listTitleKey =
|
||||
new LocTextKey("sebserver.exam.indicator.thresholds.list.title");
|
||||
|
||||
private final PageFormService pageFormService;
|
||||
private final ResourceService resourceService;
|
||||
|
||||
|
@ -35,7 +62,97 @@ public class IndicatorForm implements TemplateComposer {
|
|||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
// TODO Auto-generated method stub
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
final UserInfo user = currentUser.get();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
final boolean isReadonly = pageContext.isReadonly();
|
||||
|
||||
final Exam exam = restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.get(pageContext::notifyError);
|
||||
|
||||
// get data or create new. Handle error if happen
|
||||
final Indicator indicator = (isNew)
|
||||
? Indicator.createNew(exam.getInstitutionId(), exam)
|
||||
: restService
|
||||
.getBuilder(GetIndicator.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.get(pageContext::notifyError);
|
||||
|
||||
if (indicator == null) {
|
||||
log.error("Failed to get Indicator. "
|
||||
+ "Error was notified to the User. "
|
||||
+ "See previous logs for more infomation");
|
||||
return;
|
||||
}
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = pageContext.withEntityKey(indicator.getEntityKey());
|
||||
|
||||
// the default page layout
|
||||
final LocTextKey titleKey = new LocTextKey(
|
||||
(isNew)
|
||||
? "sebserver.exam.indicator.form.title.new"
|
||||
: "sebserver.exam.indicator.form.title");
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
formContext.getParent(),
|
||||
titleKey);
|
||||
|
||||
final FormHandle<Indicator> formHandle = this.pageFormService.getBuilder(
|
||||
formContext.copyOf(content), 4)
|
||||
.readonly(isReadonly)
|
||||
.putStaticValueIf(() -> !isNew,
|
||||
Domain.INDICATOR.ATTR_ID,
|
||||
indicator.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.EXAM.ATTR_INSTITUTION_ID,
|
||||
exam.getModelId())
|
||||
.putStaticValue(
|
||||
Domain.INDICATOR.ATTR_EXAM_ID,
|
||||
parentEntityKey.getModelId())
|
||||
.addField(FormBuilder.text(
|
||||
"examName",
|
||||
"sebserver.exam.indicator.form.exam",
|
||||
exam.name)
|
||||
.readonly(true))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.INDICATOR.ATTR_NAME,
|
||||
"sebserver.exam.indicator.form.name",
|
||||
indicator.name))
|
||||
.addField(FormBuilder.singleSelection(
|
||||
Domain.INDICATOR.ATTR_TYPE,
|
||||
"sebserver.exam.indicator.form.type",
|
||||
(indicator.type != null) ? indicator.type.name() : null,
|
||||
this.resourceService::indicatorTypeResources))
|
||||
.addField(FormBuilder.colorSelection(
|
||||
Domain.INDICATOR.ATTR_TYPE,
|
||||
"sebserver.exam.indicator.form.color",
|
||||
indicator.defaultColor))
|
||||
.buildFor((isNew)
|
||||
? restService.getRestCall(NewIndicator.class)
|
||||
: restService.getRestCall(SaveIndicator.class));
|
||||
|
||||
// propagate content actions to action-pane
|
||||
formContext.clearEntityKeys()
|
||||
.createAction(ActionDefinition.EXAM_INDICATOR_SAVE)
|
||||
.withParentEntityKey(parentEntityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.publishIf(() -> !isReadonly)
|
||||
|
||||
.createAction(ActionDefinition.EXAM_INDICATOR_CANCEL_MODIFY)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(Action::onEmptyEntityKeyGoToActivityHome)
|
||||
.withConfirm("sebserver.overall.action.modify.cancel.confirm")
|
||||
.publishIf(() -> !isReadonly);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ public class InstitutionForm implements TemplateComposer {
|
|||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
// get data or create new. Handle error if happen
|
||||
final Institution institution = (entityKey == null)
|
||||
final Institution institution = (isNew)
|
||||
? Institution.createNew()
|
||||
: this.restService
|
||||
.getBuilder(GetInstitution.class)
|
||||
|
@ -116,7 +116,7 @@ public class InstitutionForm implements TemplateComposer {
|
|||
// The Institution form
|
||||
final FormHandle<Institution> formHandle = this.pageFormService.getBuilder(
|
||||
formContext.copyOf(content), 4)
|
||||
.readonly(formContext.isReadonly())
|
||||
.readonly(isReadonly)
|
||||
.putStaticValueIf(() -> !isNew,
|
||||
Domain.INSTITUTION.ATTR_ID,
|
||||
institution.getModelId())
|
||||
|
|
|
@ -196,7 +196,7 @@ public class LmsSetupForm implements TemplateComposer {
|
|||
|
||||
.createAction(ActionDefinition.LMS_SETUP_ACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(restService::activation)
|
||||
.withExec(action -> activate(action, formHandle))
|
||||
.publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
|
||||
|
||||
.createAction(ActionDefinition.LMS_SETUP_SAVE)
|
||||
|
@ -210,6 +210,14 @@ public class LmsSetupForm implements TemplateComposer {
|
|||
.publishIf(() -> !readonly);
|
||||
}
|
||||
|
||||
/** Save and test connection before activation */
|
||||
private Action activate(final Action action, final FormHandle<LmsSetup> formHandle) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final Action testLmsSetup = this.testLmsSetup(action, formHandle);
|
||||
final Action activation = restService.activation(testLmsSetup);
|
||||
return activation;
|
||||
}
|
||||
|
||||
/** LmsSetup test action implementation */
|
||||
private Action testLmsSetup(final Action action, final FormHandle<LmsSetup> formHandle) {
|
||||
// If we are in edit-mode we have to save the form before testing
|
||||
|
|
|
@ -321,7 +321,6 @@ public enum ActionDefinition {
|
|||
ImageIcon.NEW,
|
||||
IndicatorForm.class,
|
||||
EXAM_VIEW_FROM_LIST,
|
||||
ActionCategory.FORM,
|
||||
false),
|
||||
EXAM_INDICATOR_MODIFY_FROM_LIST(
|
||||
new LocTextKey("sebserver.exam.indicator.action.list.modify"),
|
||||
|
@ -338,7 +337,7 @@ public enum ActionDefinition {
|
|||
ActionCategory.INDICATOR_LIST,
|
||||
true),
|
||||
EXAM_INDICATOR_SAVE(
|
||||
new LocTextKey("sebserver.exam.indicator.action.list.save"),
|
||||
new LocTextKey("sebserver.exam.indicator.action.save"),
|
||||
ImageIcon.SAVE,
|
||||
ExamForm.class,
|
||||
EXAM_VIEW_FROM_LIST,
|
||||
|
|
|
@ -195,7 +195,9 @@ public final class Form implements FormBinding {
|
|||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final Selection selection) {
|
||||
switch (selection.type()) {
|
||||
case MULTI : return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray);
|
||||
case MULTI:
|
||||
case MULTI_COMBO:
|
||||
return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray);
|
||||
default : return createAccessor(label, selection, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -216,6 +216,14 @@ public class FormBuilder {
|
|||
return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier);
|
||||
}
|
||||
|
||||
public static SelectionFieldBuilder colorSelection(
|
||||
final String name,
|
||||
final String label,
|
||||
final String value) {
|
||||
|
||||
return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null);
|
||||
}
|
||||
|
||||
public static ImageUploadFieldBuilder imageUpload(final String name, final String label, final String value) {
|
||||
return new ImageUploadFieldBuilder(name, label, value);
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ public final class SelectionFieldBuilder extends FieldBuilder {
|
|||
if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO) {
|
||||
final Composite composite = new Composite(builder.formParent, SWT.NONE);
|
||||
final GridLayout gridLayout = new GridLayout(1, true);
|
||||
gridLayout.verticalSpacing = 0;
|
||||
gridLayout.horizontalSpacing = 0;
|
||||
gridLayout.marginLeft = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
|
|
|
@ -206,7 +206,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
|||
|
||||
try {
|
||||
final OAuth2AccessToken accessToken = this.restTemplate.getAccessToken();
|
||||
log.debug("Got token for user: {} : {}", username, accessToken);
|
||||
log.debug("Got token for user: {} : {}", username, "--");
|
||||
this.loggedInUser = getLoggedInUser();
|
||||
return true;
|
||||
} catch (final OAuth2AccessDeniedException | AccessDeniedException e) {
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.widget;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.ColorDialog;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
||||
|
||||
public class ColorSelection extends Composite implements Selection {
|
||||
|
||||
private static final long serialVersionUID = 4775375044147842526L;
|
||||
private static final Logger log = LoggerFactory.getLogger(ColorSelection.class);
|
||||
|
||||
private static final int ACTION_COLUMN_WIDTH = 20;
|
||||
private final ColorDialog colorDialog;
|
||||
private final Composite colorField;
|
||||
private RGB selection;
|
||||
|
||||
ColorSelection(final Composite parent, final WidgetFactory widgetFactory) {
|
||||
super(parent, SWT.NONE);
|
||||
final GridLayout gridLayout = new GridLayout(2, false);
|
||||
gridLayout.verticalSpacing = 1;
|
||||
gridLayout.marginLeft = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
setLayout(gridLayout);
|
||||
|
||||
this.colorDialog = new ColorDialog(this.getShell(), SWT.NONE);
|
||||
|
||||
this.colorField = new Composite(this, SWT.NONE);
|
||||
final GridData colorCell = new GridData(SWT.FILL, SWT.TOP, true, false);
|
||||
colorCell.heightHint = 20;
|
||||
this.colorField.setLayoutData(colorCell);
|
||||
|
||||
final Label imageButton = widgetFactory.imageButton(
|
||||
ImageIcon.COLOR,
|
||||
this,
|
||||
new LocTextKey("Set Color"),
|
||||
this::addColorSelection);
|
||||
|
||||
final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
|
||||
actionCell.widthHint = ACTION_COLUMN_WIDTH;
|
||||
imageButton.setLayoutData(actionCell);
|
||||
|
||||
this.addListener(SWT.Resize, this::adaptColumnWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
return Type.COLOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyNewMapping(final List<Tuple<String>> mapping) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void select(final String keys) {
|
||||
this.selection = parseRGB(keys);
|
||||
applySelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSelectionValue() {
|
||||
return parseColorString(this.selection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.selection = null;
|
||||
}
|
||||
|
||||
private void addColorSelection(final Event event) {
|
||||
this.colorDialog.open(code -> {
|
||||
if (code == SWT.CANCEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selection = this.colorDialog.getRGB();
|
||||
applySelection();
|
||||
});
|
||||
}
|
||||
|
||||
private void applySelection() {
|
||||
if (this.selection != null) {
|
||||
this.colorField.setBackground(new Color(this.getDisplay(), this.selection));
|
||||
} else {
|
||||
this.colorField.setBackground(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void adaptColumnWidth(final Event event) {
|
||||
try {
|
||||
final int currentTableWidth = this.getClientArea().width;
|
||||
final GridData comboCell = (GridData) this.colorField.getLayoutData();
|
||||
comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH;
|
||||
this.layout();
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to adaptColumnWidth: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
static String parseColorString(final RGB color) {
|
||||
if (color == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Integer.toHexString(color.red)
|
||||
+ Integer.toHexString(color.green)
|
||||
+ Integer.toHexString(color.blue);
|
||||
}
|
||||
|
||||
static RGB parseRGB(final String colorString) {
|
||||
if (StringUtils.isBlank(colorString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int r = Integer.parseInt(colorString.substring(0, 2), 16);
|
||||
final int g = Integer.parseInt(colorString.substring(2, 4), 16);
|
||||
final int b = Integer.parseInt(colorString.substring(4, 6), 16);
|
||||
|
||||
return new RGB(r, g, b);
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import org.eclipse.swt.SWT;
|
|||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
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.Composite;
|
||||
|
@ -51,14 +52,14 @@ public class ImageUpload extends Composite {
|
|||
|
||||
ImageUpload(final Composite parent, final ServerPushService serverPushService, final boolean readonly) {
|
||||
super(parent, SWT.NONE);
|
||||
super.setLayout(new GridLayout(2, false));
|
||||
super.setLayout(new GridLayout(1, false));
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
|
||||
if (!readonly) {
|
||||
this.fileUpload = new FileUpload(this, SWT.NONE);
|
||||
this.fileUpload.setText("Select File");
|
||||
this.fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
|
||||
this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay()));
|
||||
this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
|
||||
|
||||
final FileUploadHandler uploadHandler = new FileUploadHandler(new FileUploadReceiver() {
|
||||
|
||||
|
@ -108,7 +109,7 @@ public class ImageUpload extends Composite {
|
|||
|
||||
public void setSelectionText(final String text) {
|
||||
if (this.fileUpload != null) {
|
||||
this.fileUpload.setText(text);
|
||||
this.fileUpload.setToolTipText(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,8 +125,8 @@ public class ImageUpload extends Composite {
|
|||
this.imageBase64 = imageBase64;
|
||||
final Base64InputStream input = new Base64InputStream(
|
||||
new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), false);
|
||||
this.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
|
||||
this.imageCanvas.setBackgroundImage(new Image(super.getDisplay(), input));
|
||||
|
||||
setImage(this, input);
|
||||
}
|
||||
|
||||
private static final boolean uploadInProgress(final ServerPushContext context) {
|
||||
|
@ -153,12 +154,20 @@ public class ImageUpload extends Composite {
|
|||
imageUpload.imageBase64.getBytes(StandardCharsets.UTF_8)),
|
||||
false);
|
||||
|
||||
imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
|
||||
imageUpload.imageCanvas.setBackgroundImage(new Image(context.getDisplay(), input));
|
||||
setImage(imageUpload, input);
|
||||
context.layout();
|
||||
imageUpload.layout();
|
||||
imageUpload.loadNewImage = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setImage(final ImageUpload imageUpload, final Base64InputStream input) {
|
||||
imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
|
||||
|
||||
final Image image = new Image(imageUpload.imageCanvas.getDisplay(), input);
|
||||
final ImageData imageData = image.getImageData().scaledTo(200, 100);
|
||||
|
||||
imageUpload.imageCanvas.setBackgroundImage(new Image(imageUpload.imageCanvas.getDisplay(), imageData));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,18 +9,23 @@
|
|||
package ch.ethz.seb.sebserver.gui.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.custom.TableEditor;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Combo;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.Table;
|
||||
import org.eclipse.swt.widgets.TableColumn;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -34,43 +39,40 @@ public class MultiSelectionCombo extends Composite implements Selection {
|
|||
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class);
|
||||
private static final long serialVersionUID = -7787134114963647332L;
|
||||
private static final int ACTION_COLUMN_WIDTH = 20;
|
||||
private static final String SELECTION_KEY = "SELECTION_KEY";
|
||||
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final Table table;
|
||||
private final Combo combo;
|
||||
private final List<String> selected = new ArrayList<>();
|
||||
private final List<Tuple<String>> mapping = new ArrayList<>();
|
||||
|
||||
private final List<Tuple<Control>> selectionControls = new ArrayList<>();
|
||||
private final List<Tuple<String>> selectedValues = new ArrayList<>();
|
||||
private final Map<String, String> mapping = new HashMap<>();
|
||||
//private final List<Tuple<String>> mapping = new ArrayList<>();
|
||||
|
||||
MultiSelectionCombo(final Composite parent, final WidgetFactory widgetFactory) {
|
||||
super(parent, SWT.NONE);
|
||||
this.widgetFactory = widgetFactory;
|
||||
final GridLayout gridLayout = new GridLayout(1, true);
|
||||
final GridLayout gridLayout = new GridLayout(2, false);
|
||||
gridLayout.verticalSpacing = 1;
|
||||
gridLayout.marginLeft = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
setLayout(gridLayout);
|
||||
|
||||
this.table = new Table(this, SWT.NONE);
|
||||
this.addListener(SWT.Resize, this::adaptColumnWidth);
|
||||
|
||||
TableColumn column = new TableColumn(this.table, SWT.NONE);
|
||||
column = new TableColumn(this.table, SWT.NONE);
|
||||
column.setWidth(ACTION_COLUMN_WIDTH);
|
||||
this.table.setHeaderVisible(false);
|
||||
this.table.addListener(SWT.Resize, this::adaptColumnWidth);
|
||||
this.combo = new Combo(this, SWT.NONE);
|
||||
final GridData comboCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||
this.combo.setLayoutData(comboCell);
|
||||
|
||||
final TableItem header = new TableItem(this.table, SWT.NONE);
|
||||
final TableEditor editor = new TableEditor(this.table);
|
||||
this.combo = new Combo(this.table, SWT.NONE);
|
||||
editor.grabHorizontal = true;
|
||||
editor.setEditor(this.combo, header, 0);
|
||||
|
||||
widgetFactory.imageButton(
|
||||
ImageIcon.ADD,
|
||||
this.table,
|
||||
final Label imageButton = widgetFactory.imageButton(
|
||||
ImageIcon.ADD_BOX,
|
||||
this,
|
||||
new LocTextKey("Add"),
|
||||
this::addComboSelection);
|
||||
|
||||
final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
|
||||
actionCell.widthHint = ACTION_COLUMN_WIDTH;
|
||||
imageButton.setLayoutData(actionCell);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -80,49 +82,132 @@ public class MultiSelectionCombo extends Composite implements Selection {
|
|||
|
||||
@Override
|
||||
public void applyNewMapping(final List<Tuple<String>> mapping) {
|
||||
this.selected.clear();
|
||||
this.mapping.clear();
|
||||
this.mapping.addAll(mapping);
|
||||
|
||||
this.mapping.putAll(mapping.stream()
|
||||
.collect(Collectors.toMap(t -> t._1, t -> t._2)));
|
||||
this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void select(final String keys) {
|
||||
clear();
|
||||
if (StringUtils.isBlank(keys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR))
|
||||
.stream()
|
||||
.forEach(this::addSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSelectionValue() {
|
||||
if (this.selected.isEmpty()) {
|
||||
if (this.selectedValues.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return this.mapping
|
||||
return this.selectedValues
|
||||
.stream()
|
||||
.filter(t -> this.selected.contains(t._2))
|
||||
.map(t -> t._1)
|
||||
.reduce("", (s1, s2) -> s1.concat(Constants.LIST_SEPARATOR).concat(s2));
|
||||
.reduce("", (s1, s2) -> {
|
||||
if (!StringUtils.isBlank(s1)) {
|
||||
return s1.concat(Constants.LIST_SEPARATOR).concat(s2);
|
||||
} else {
|
||||
return s1.concat(s2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.selected.clear();
|
||||
this.table.remove(1, this.table.getItemCount());
|
||||
final List<String> names = this.mapping
|
||||
this.selectedValues.clear();
|
||||
this.selectionControls
|
||||
.stream()
|
||||
.map(t -> t._2)
|
||||
.collect(Collectors.toList());
|
||||
this.combo.setItems(names.toArray(new String[names.size()]));
|
||||
.forEach(t -> {
|
||||
t._1.dispose();
|
||||
t._2.dispose();
|
||||
});
|
||||
this.selectionControls.clear();
|
||||
this.combo.setItems(this.mapping.values().toArray(new String[this.mapping.size()]));
|
||||
}
|
||||
|
||||
private void addComboSelection(final Event event) {
|
||||
final int selectionIndex = this.combo.getSelectionIndex();
|
||||
if (selectionIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String itemName = this.combo.getItem(selectionIndex);
|
||||
if (itemName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<Entry<String, String>> findFirst = this.mapping.entrySet()
|
||||
.stream()
|
||||
.filter(entity -> entity.getValue().equals(itemName))
|
||||
.findFirst();
|
||||
|
||||
if (!findFirst.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
addSelection(findFirst.get().getKey());
|
||||
}
|
||||
|
||||
private void addSelection(final String itemKey) {
|
||||
final String itemName = this.mapping.get(itemKey);
|
||||
if (itemName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedValues.add(new Tuple<>(itemKey, itemName));
|
||||
final Label label = this.widgetFactory.label(this, itemName);
|
||||
final Label imageButton = this.widgetFactory.imageButton(
|
||||
ImageIcon.REMOVE_BOX,
|
||||
this,
|
||||
new LocTextKey("Remove"),
|
||||
this::removeComboSelection);
|
||||
imageButton.setData(SELECTION_KEY, itemName);
|
||||
final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
|
||||
actionCell.widthHint = ACTION_COLUMN_WIDTH;
|
||||
imageButton.setLayoutData(actionCell);
|
||||
|
||||
this.selectionControls.add(new Tuple<>(label, imageButton));
|
||||
|
||||
this.combo.remove(itemName);
|
||||
this.getParent().layout();
|
||||
}
|
||||
|
||||
private void removeComboSelection(final Event event) {
|
||||
if (event.widget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String selectionKey = (String) event.widget.getData(SELECTION_KEY);
|
||||
final Optional<Tuple<Control>> findFirst = this.selectionControls.stream()
|
||||
.filter(t -> selectionKey.equals(t._2.getData(SELECTION_KEY)))
|
||||
.findFirst();
|
||||
if (!findFirst.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Tuple<Control> tuple = findFirst.get();
|
||||
final int indexOf = this.selectionControls.indexOf(tuple);
|
||||
this.selectionControls.remove(tuple);
|
||||
|
||||
tuple._1.dispose();
|
||||
tuple._2.dispose();
|
||||
|
||||
final Tuple<String> value = this.selectedValues.remove(indexOf);
|
||||
this.combo.add(value._2, this.combo.getItemCount());
|
||||
|
||||
this.getParent().layout();
|
||||
}
|
||||
|
||||
private void adaptColumnWidth(final Event event) {
|
||||
try {
|
||||
final int currentTableWidth = this.table.getParent().getClientArea().width;
|
||||
this.table.getColumn(0).setWidth(currentTableWidth - ACTION_COLUMN_WIDTH);
|
||||
final int currentTableWidth = this.getClientArea().width;
|
||||
final GridData comboCell = (GridData) this.combo.getLayoutData();
|
||||
comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH;
|
||||
this.layout();
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to adaptColumnWidth: ", e);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ public interface Selection {
|
|||
enum Type {
|
||||
SINGLE,
|
||||
MULTI,
|
||||
MULTI_COMBO
|
||||
MULTI_COMBO,
|
||||
COLOR,
|
||||
}
|
||||
|
||||
Type type();
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.widget;
|
||||
|
||||
public class ThresholdList {
|
||||
|
||||
}
|
|
@ -62,6 +62,8 @@ public class WidgetFactory {
|
|||
MINIMIZE("minimize.png"),
|
||||
ADD("add.png"),
|
||||
REMOVE("remove.png"),
|
||||
ADD_BOX("add_box.png"),
|
||||
REMOVE_BOX("remove_box.png"),
|
||||
EDIT("edit.png"),
|
||||
TEST("test.png"),
|
||||
IMPORT("import.png"),
|
||||
|
@ -73,7 +75,8 @@ public class WidgetFactory {
|
|||
SAVE("save.png"),
|
||||
NEW("new.png"),
|
||||
DELETE("delete.png"),
|
||||
SEARCH("lens.png");
|
||||
SEARCH("lens.png"),
|
||||
COLOR("color.png");
|
||||
|
||||
private String fileName;
|
||||
private ImageData image = null;
|
||||
|
@ -325,49 +328,21 @@ public class WidgetFactory {
|
|||
case MULTI_COMBO:
|
||||
selection = new MultiSelectionCombo(parent, this);
|
||||
break;
|
||||
case COLOR:
|
||||
selection = new ColorSelection(parent, this);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported Selection.Type: " + type);
|
||||
}
|
||||
|
||||
final Consumer<Selection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
|
||||
selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
|
||||
updateFunction.accept(selection);
|
||||
if (itemsSupplier != null) {
|
||||
final Consumer<Selection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
|
||||
selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
|
||||
updateFunction.accept(selection);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
// public SingleSelection singleSelectionLocalized(
|
||||
// final Composite parent,
|
||||
// final Supplier<List<Tuple<String>>> itemsSupplier) {
|
||||
//
|
||||
// final SingleSelection singleSelection = new SingleSelection(parent);
|
||||
// final Consumer<SingleSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
|
||||
// singleSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
|
||||
// updateFunction.accept(singleSelection);
|
||||
// return singleSelection;
|
||||
// }
|
||||
//
|
||||
// public MultiSelection multiSelectionLocalized(
|
||||
// final Composite parent,
|
||||
// final Supplier<List<Tuple<String>>> itemsSupplier) {
|
||||
//
|
||||
// final MultiSelection multiSelection = new MultiSelection(parent);
|
||||
// final Consumer<MultiSelection> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
|
||||
// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
|
||||
// updateFunction.accept(multiSelection);
|
||||
// return multiSelection;
|
||||
// }
|
||||
//
|
||||
// public MultiSelectionCombo multiSelectionComboLocalized(
|
||||
// final Composite parent,
|
||||
// final Supplier<List<Tuple<String>>> itemsSupplier) {
|
||||
//
|
||||
// final MultiSelectionCombo multiSelection = new MultiSelectionCombo(parent, this);
|
||||
// final Consumer<MultiSelectionCombo> updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
|
||||
// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
|
||||
// updateFunction.accept(multiSelection);
|
||||
// return multiSelection;
|
||||
// }
|
||||
|
||||
public ImageUpload imageUploadLocalized(
|
||||
final Composite parent,
|
||||
final LocTextKey locTextKey,
|
||||
|
|
|
@ -107,7 +107,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
|||
.withInstitutionalPrivilege(PrivilegeType.MODIFY)
|
||||
.withOwnerPrivilege(PrivilegeType.WRITE)
|
||||
.andForRole(UserRole.EXAM_SUPPORTER)
|
||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||
.withInstitutionalPrivilege(PrivilegeType.READ_ONLY)
|
||||
.create();
|
||||
|
||||
// grants for indicators
|
||||
|
@ -120,7 +120,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
|||
.withInstitutionalPrivilege(PrivilegeType.MODIFY)
|
||||
.withOwnerPrivilege(PrivilegeType.WRITE)
|
||||
.andForRole(UserRole.EXAM_SUPPORTER)
|
||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||
.withInstitutionalPrivilege(PrivilegeType.READ_ONLY)
|
||||
.create();
|
||||
|
||||
// TODO other entities
|
||||
|
|
|
@ -12,6 +12,8 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.security.SecureRandom;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.cryptonode.jncryptor.AES256JNCryptor;
|
||||
import org.cryptonode.jncryptor.JNCryptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -35,6 +37,9 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
|
|||
|
||||
private final Environment environment;
|
||||
|
||||
// TODO try to integrate with JNCryptor since this is also used by SEB
|
||||
private final JNCryptor cryptor = new AES256JNCryptor();
|
||||
|
||||
protected ClientCredentialServiceImpl(final Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
@ -142,6 +147,7 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
|
|||
}
|
||||
|
||||
try {
|
||||
|
||||
return Encryptors
|
||||
.delux(secret, getSalt(salt))
|
||||
.encrypt(text.toString());
|
||||
|
|
|
@ -95,7 +95,9 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
|
|||
* reported exception on error case */
|
||||
Result<T> save(T data);
|
||||
|
||||
/** Use this to delete a set Entity by a Collection of EntityKey
|
||||
/** Use this to delete all entities defined by a set of EntityKey
|
||||
* NOTE: the Set of EntityKey may contain EntityKey of other entity types like the concrete type of the DAO
|
||||
* use extractPKsFromKeys to get a list of concrete primary keys for entities to delete
|
||||
*
|
||||
* @param all The Collection of EntityKey to delete
|
||||
* @return Result referring a collection of all entities that has been deleted or refer to an error if
|
||||
|
@ -104,12 +106,12 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
|
|||
|
||||
/** Get a (unordered) collection of all Entities that matches the given filter criteria.
|
||||
* The possible filter criteria for a specific Entity type is defined by the entity type.
|
||||
*
|
||||
*
|
||||
* This adds filtering in SQL level by creating the select where clause from related
|
||||
* filter criteria of the specific Entity type. If the filterMap contains a value for
|
||||
* a particular filter criteria the value is extracted from the map and added to the where
|
||||
* clause of the SQL select statement.
|
||||
*
|
||||
*
|
||||
* @param filterMap FilterMap instance containing all the relevant filter criteria
|
||||
* @return Result referring to collection of all matching entities or an error if happened */
|
||||
@Transactional(readOnly = true)
|
||||
|
@ -119,16 +121,16 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
|
|||
|
||||
/** Get a (unordered) collection of all Entities that matches a given filter criteria
|
||||
* and a given predicate.
|
||||
*
|
||||
*
|
||||
* The possible filter criteria for a specific Entity type is defined by the entity type.
|
||||
* This adds filtering in SQL level by creating the select where clause from related
|
||||
* filter criteria of the specific Entity type. If the filterMap contains a value for
|
||||
* a particular filter criteria the value is extracted from the map and added to the where
|
||||
* clause of the SQL select statement.
|
||||
*
|
||||
*
|
||||
* The predicate is applied after the SQL query by filtering the resulting list with the
|
||||
* predicate after on the SQL query result, before returning.
|
||||
*
|
||||
*
|
||||
* @param filterMap FilterMap instance containing all the relevant filter criteria
|
||||
* @return Result referring to collection of all matching entities or an error if happened */
|
||||
Result<Collection<T>> allMatching(FilterMap filterMap, Predicate<T> predicate);
|
||||
|
|
|
@ -261,8 +261,6 @@ public class IndicatorDAOImpl implements IndicatorDAO {
|
|||
.execute()
|
||||
.stream()
|
||||
.map(tRec -> new Threshold(
|
||||
tRec.getId(),
|
||||
tRec.getIndicatorId(),
|
||||
tRec.getValue().doubleValue(),
|
||||
tRec.getColor()))
|
||||
.collect(Collectors.toList());
|
||||
|
@ -270,8 +268,8 @@ public class IndicatorDAOImpl implements IndicatorDAO {
|
|||
return new Indicator(
|
||||
record.getId(),
|
||||
examRecord.getInstitutionId(),
|
||||
examRecord.getOwner(),
|
||||
record.getExamId(),
|
||||
examRecord.getOwner(),
|
||||
record.getName(),
|
||||
IndicatorType.valueOf(record.getType()),
|
||||
record.getColor(),
|
||||
|
|
|
@ -57,25 +57,25 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
|||
this.mockups = new ArrayList<>();
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1", "Demo Quit Mockup",
|
||||
"2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
|
||||
"2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2", "Demo Quit Mockup",
|
||||
"2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
|
||||
"2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3", "Demo Quit Mockup",
|
||||
"2018-07-30 09:00:00", "2018-08-01 00:00:00", "http://lms.mockup.com/api/"));
|
||||
"2018-07-30T09:00:00", "2018-08-01T00:00:00", "http://lms.mockup.com/api/"));
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4", "Demo Quit Mockup",
|
||||
"2018-01-01 00:00:00", "2019-01-01 00:00:00", "http://lms.mockup.com/api/"));
|
||||
"2018-01-01T00:00:00", "2019-01-01T00:00:00", "http://lms.mockup.com/api/"));
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5", "Demo Quit Mockup",
|
||||
"2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
|
||||
"2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6", "Demo Quit Mockup",
|
||||
"2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
|
||||
"2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7", "Demo Quit Mockup",
|
||||
"2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
|
||||
"2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,9 +25,13 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2RequestAuthenticator;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
|
||||
|
@ -115,6 +119,19 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
|
|||
this.knownTokenAccessPaths);
|
||||
}
|
||||
|
||||
try {
|
||||
this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT);
|
||||
} catch (final Exception e) {
|
||||
if (this.restTemplate != null) {
|
||||
this.restTemplate.setAuthenticator(new EdxOAuth2RequestAuthenticator());
|
||||
}
|
||||
try {
|
||||
this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT);
|
||||
} catch (final Exception ee) {
|
||||
return LmsSetupTestResult.ofQuizRequestError(ee.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return LmsSetupTestResult.ofOkay();
|
||||
}
|
||||
|
||||
|
@ -192,28 +209,6 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
|
|||
return template;
|
||||
}
|
||||
|
||||
// private Result<List<QuizData>> getAllQuizes(final LmsSetup lmsSetup) {
|
||||
// if (this.allQuizzesSupplier == null) {
|
||||
// this.allQuizzesSupplier = new CircuitBreaker<>(
|
||||
// () -> collectAllCourses(lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT)
|
||||
// .stream()
|
||||
// .reduce(
|
||||
// new ArrayList<QuizData>(),
|
||||
// (list, courseData) -> {
|
||||
// list.add(quizDataOf(lmsSetup, courseData));
|
||||
// return list;
|
||||
// },
|
||||
// (list1, list2) -> {
|
||||
// list1.addAll(list2);
|
||||
// return list1;
|
||||
// }),
|
||||
// 5, 1000L); // TODO specify better CircuitBreaker params
|
||||
// }
|
||||
//
|
||||
// return this.allQuizzesSupplier.get();
|
||||
//
|
||||
// }
|
||||
|
||||
private Supplier<List<QuizData>> allQuizzesSupplier(final LmsSetup lmsSetup) {
|
||||
return () -> {
|
||||
return initRestTemplateAndRequestAccessToken()
|
||||
|
@ -320,8 +315,27 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
|
|||
params.add("client_id", resource.getClientId());
|
||||
params.add("client_secret", resource.getClientSecret());
|
||||
|
||||
return retrieveToken(request, resource, params, headers);
|
||||
final OAuth2AccessToken retrieveToken = retrieveToken(request, resource, params, headers);
|
||||
return retrieveToken;
|
||||
}
|
||||
}
|
||||
|
||||
private class EdxOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator {
|
||||
|
||||
@Override
|
||||
public void authenticate(
|
||||
final OAuth2ProtectedResourceDetails resource,
|
||||
final OAuth2ClientContext clientContext,
|
||||
final ClientHttpRequest request) {
|
||||
|
||||
final OAuth2AccessToken accessToken = clientContext.getAccessToken();
|
||||
if (accessToken == null) {
|
||||
throw new AccessTokenRequiredException(resource);
|
||||
}
|
||||
|
||||
request.getHeaders().set("Authorization", String.format("%s %s", "JWT:", accessToken.getValue()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.weblayer.api;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.seb.PingResponse;
|
||||
import ch.ethz.seb.sebserver.gbl.model.seb.RunningExams;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
|
||||
@WebServiceProfile
|
||||
@RestController
|
||||
@RequestMapping("/${sebserver.webservice.api.exam.endpoint}")
|
||||
public class ExamAPIController {
|
||||
|
||||
@RequestMapping(
|
||||
path = API.EXAM_API_HANDSHAKE_ENDPOINT,
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public RunningExams handshake(
|
||||
@RequestParam(name = API.PARAM_INSTITUTION_ID, required = true) final Long institutionId,
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response) {
|
||||
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT,
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.TEXT_XML_VALUE)
|
||||
public RunningExams getConfig(
|
||||
@RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken,
|
||||
@RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = true) final String examId,
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response) {
|
||||
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.EXAM_API_PING_ENDPOINT,
|
||||
method = RequestMethod.PUT,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.TEXT_XML_VALUE)
|
||||
public PingResponse ping(
|
||||
@RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken,
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response) {
|
||||
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.EXAM_API_EVENT_ENDPOINT,
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
public void event(
|
||||
@RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken,
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response) {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.IndicatorRecordDynamicSqlSupport;
|
||||
|
@ -28,11 +29,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
@WebServiceProfile
|
||||
@RestController
|
||||
@RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.EXAM_INDICATOR_ENDPOINT)
|
||||
public class ExamIndicatorController extends EntityController<Indicator, Indicator> {
|
||||
public class IndicatorController extends EntityController<Indicator, Indicator> {
|
||||
|
||||
private final ExamDAO examDao;
|
||||
|
||||
protected ExamIndicatorController(
|
||||
protected IndicatorController(
|
||||
final AuthorizationService authorization,
|
||||
final BulkActionService bulkActionService,
|
||||
final IndicatorDAO entityDAO,
|
||||
|
@ -53,7 +54,7 @@ public class ExamIndicatorController extends EntityController<Indicator, Indicat
|
|||
|
||||
@Override
|
||||
protected Indicator createNew(final POSTMapper postParams) {
|
||||
final Long examId = postParams.getLong(API.PARAM_EXAM_ID);
|
||||
final Long examId = postParams.getLong(Domain.INDICATOR.ATTR_EXAM_ID);
|
||||
|
||||
return this.examDao
|
||||
.byPK(examId)
|
|
@ -149,6 +149,31 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
|
|||
.flatMap(this::additionalConsistencyChecks);
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.PASSWORD_PATH_SEGMENT,
|
||||
method = RequestMethod.PUT,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) {
|
||||
|
||||
final String modelId = passwordChange.getModelId();
|
||||
return this.userDAO.byModelId(modelId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.map(ui -> checkPasswordChange(ui, passwordChange))
|
||||
.flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword()))
|
||||
.flatMap(this::revokeAccessToken)
|
||||
.flatMap(e -> this.userActivityLogDAO.log(ActivityType.PASSWORD_CHANGE, e))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
private Result<UserInfo> revokeAccessToken(final UserInfo userInfo) {
|
||||
return Result.tryCatch(() -> {
|
||||
this.applicationEventPublisher.publishEvent(
|
||||
new RevokeTokenEndpoint.RevokeTokenEvent(userInfo, userInfo.username));
|
||||
return userInfo;
|
||||
});
|
||||
}
|
||||
|
||||
/** Additional consistency checks that has to be checked before create and save actions */
|
||||
private <T extends UserAccount> Result<T> additionalConsistencyChecks(final T userInfo) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
@ -194,29 +219,4 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
|
|||
});
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.PASSWORD_PATH_SEGMENT,
|
||||
method = RequestMethod.PUT,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) {
|
||||
|
||||
final String modelId = passwordChange.getModelId();
|
||||
return this.userDAO.byModelId(modelId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.map(ui -> checkPasswordChange(ui, passwordChange))
|
||||
.flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword()))
|
||||
.flatMap(this::revokeAccessToken)
|
||||
.flatMap(e -> this.userActivityLogDAO.log(ActivityType.PASSWORD_CHANGE, e))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
private Result<UserInfo> revokeAccessToken(final UserInfo userInfo) {
|
||||
return Result.tryCatch(() -> {
|
||||
this.applicationEventPublisher.publishEvent(
|
||||
new RevokeTokenEndpoint.RevokeTokenEvent(userInfo, userInfo.username));
|
||||
return userInfo;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,9 +77,9 @@ sebserver.institution.list.column.active=Active
|
|||
sebserver.institution.action.list=Institution
|
||||
sebserver.institution.action.form=Institution
|
||||
sebserver.institution.action.new=New Institution
|
||||
sebserver.institution.action.list.view=View Selected
|
||||
sebserver.institution.action.modify=Edit Institution
|
||||
sebserver.institution.action.list.modify=Edit Selected
|
||||
sebserver.institution.action.list.view=View Institution
|
||||
sebserver.institution.action.list.modify=Edit Institution
|
||||
sebserver.institution.action.modify=Edit
|
||||
sebserver.institution.action.save=Save Institution
|
||||
sebserver.institution.action.activate=Activate
|
||||
sebserver.institution.action.deactivate=Deactivate
|
||||
|
@ -117,8 +117,8 @@ sebserver.useraccount.list.column.active=Active
|
|||
sebserver.useraccount.action.list=User Account
|
||||
sebserver.useraccount.action.form=User Account of {0}
|
||||
sebserver.useraccount.action.new=New User Account
|
||||
sebserver.useraccount.action.view=View Selected
|
||||
sebserver.useraccount.action.list.modify=Edit Selected
|
||||
sebserver.useraccount.action.view=View User Account
|
||||
sebserver.useraccount.action.list.modify=Edit User Account
|
||||
sebserver.useraccount.action.modify=Edit
|
||||
sebserver.useraccount.action.save=Save User Account
|
||||
sebserver.useraccount.action.activate=Activate
|
||||
|
@ -165,8 +165,8 @@ sebserver.lmssetup.list.column.active=Active
|
|||
sebserver.lmssetup.action.list=LMS Setup
|
||||
sebserver.lmssetup.action.form=LMS Setup
|
||||
sebserver.lmssetup.action.new=New LMS Setup
|
||||
sebserver.lmssetup.action.list.view=View Selected
|
||||
sebserver.lmssetup.action.list.modify=Edit Selected
|
||||
sebserver.lmssetup.action.list.view=View LMS Setup
|
||||
sebserver.lmssetup.action.list.modify=Edit LMS Setup
|
||||
sebserver.lmssetup.action.modify=Edit
|
||||
sebserver.lmssetup.action.test=Test Setup
|
||||
sebserver.lmssetup.action.test.ok=Successfully connect to the LMSs course API
|
||||
|
@ -225,6 +225,9 @@ sebserver.exam.list.column.type=Type
|
|||
sebserver.exam.list.empty=No Exams has been found. Please adapt the filter or import one from Quiz
|
||||
|
||||
sebserver.exam.action.list=Exam
|
||||
sebserver.exam.action.list.view=View Exam
|
||||
sebserver.exam.action.list.modify=Edit Exam
|
||||
sebserver.exam.action.modify=Edit
|
||||
sebserver.exam.action.import=Import From Quizzes
|
||||
sebserver.exam.action.save=Save
|
||||
sebserver.exam.action.activate=Activate
|
||||
|
@ -242,6 +245,7 @@ sebserver.exam.form.starttime=Start Time
|
|||
sebserver.exam.form.endtime=End Time
|
||||
sebserver.exam.form.status=Status
|
||||
sebserver.exam.form.type=Exam Type
|
||||
sebserver.exam.form.supporter=Exam Supporter
|
||||
|
||||
sebserver.exam.type.UNDEFINED=Not Defined
|
||||
sebserver.exam.type.MANAGED=Managed
|
||||
|
@ -252,8 +256,9 @@ sebserver.exam.indicator.list.actions=Selected Indicator
|
|||
sebserver.exam.indicator.list.title=Indicators
|
||||
sebserver.exam.indicator.list.column.type=Type
|
||||
sebserver.exam.indicator.list.column.name=Name
|
||||
sebserver.exam.indicator.list.column.thresholds
|
||||
sebserver.exam.indicator.list.column.thresholds=Thresholds
|
||||
sebserver.exam.indicator.list.empty=There is currently no Indicator defined for this Exam. Please create a new one
|
||||
sebserver.exam.indicator.list.pleaseSelect=Please Select an Indicator first
|
||||
|
||||
sebserver.exam.indicator.type.LAST_PING=Last Ping
|
||||
sebserver.exam.indicator.type.ERROR_COUNT=Error Count
|
||||
|
@ -262,4 +267,13 @@ sebserver.exam.indicator.info.pleaseSelect=Please Select an Indicator first
|
|||
|
||||
sebserver.exam.indicator.action.list.new=New Indicator
|
||||
sebserver.exam.indicator.action.list.modify=Modify Indicator
|
||||
sebserver.exam.indicator.action.save=Save
|
||||
|
||||
sebserver.exam.indicator.form.title=Indicator
|
||||
sebserver.exam.indicator.form.title.new=New Indicator
|
||||
sebserver.exam.indicator.form.exam=Exam
|
||||
sebserver.exam.indicator.form.name=Name
|
||||
sebserver.exam.indicator.form.type=Type
|
||||
sebserver.exam.indicator.form.color=Color
|
||||
|
||||
sebserver.exam.indicator.thresholds.list.title=Thresholds
|
||||
|
|
|
@ -345,9 +345,7 @@ Button {
|
|||
|
||||
/* Push Buttons */
|
||||
Button[PUSH],
|
||||
Button[PUSH]:default,
|
||||
FileUpload,
|
||||
FileUpload:default {
|
||||
Button[PUSH]:default {
|
||||
font: bold 12px Arial, Helvetica, sans-serif;
|
||||
background-color: #0069B4;
|
||||
background-gradient-color: #0069B4;
|
||||
|
@ -359,16 +357,14 @@ FileUpload:default {
|
|||
text-shadow: none;
|
||||
}
|
||||
|
||||
Button[PUSH]:pressed,
|
||||
FileUpload:pressed {
|
||||
Button[PUSH]:pressed {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
background-gradient-color: #444;
|
||||
background-image: gradient( linear, left top, left bottom, from( #444 ), to( #444 ) );
|
||||
}
|
||||
|
||||
Button[PUSH]:hover,
|
||||
FileUpload:hover {
|
||||
Button[PUSH]:hover {
|
||||
background-color: #82BE1E;
|
||||
background-gradient-color: #82BE1E;
|
||||
background-image: gradient( linear, left top, left bottom, from( #82BE1E ), to( #82BE1E ) );
|
||||
|
@ -376,8 +372,7 @@ FileUpload:hover {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
Button[PUSH]:disabled,
|
||||
FileUpload:disabled {
|
||||
Button[PUSH]:disabled {
|
||||
background-color: transparent;
|
||||
border: 1px solid #EAECEE;
|
||||
color: #c0c0c0;
|
||||
|
@ -418,6 +413,18 @@ Button[PUSH]:hover.header {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
FileUpload,
|
||||
FileUpload:default,
|
||||
FileUpload:hover,
|
||||
FileUpload:pressed {
|
||||
background-color: transparent;
|
||||
background-gradient-color: transparent;
|
||||
background-image: gradient( linear, left top, left bottom, from( transparent ), to( transparent ) );
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/* Sash default */
|
||||
Sash {
|
||||
|
|
BIN
src/main/resources/static/images/add_box.png
Normal file
BIN
src/main/resources/static/images/add_box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 B |
BIN
src/main/resources/static/images/color.png
Normal file
BIN
src/main/resources/static/images/color.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 223 B |
BIN
src/main/resources/static/images/remove_box.png
Normal file
BIN
src/main/resources/static/images/remove_box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 B |
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.widget;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ColorSelectionTest {
|
||||
|
||||
@Test
|
||||
public void testParseRGB() {
|
||||
String colorString = "FFFFFF";
|
||||
assertEquals(
|
||||
"RGB {255, 255, 255}",
|
||||
ColorSelection.parseRGB(colorString).toString());
|
||||
|
||||
colorString = "FFaa34";
|
||||
assertEquals(
|
||||
"RGB {255, 170, 52}",
|
||||
ColorSelection.parseRGB(colorString).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseColorString() {
|
||||
final RGB color = new RGB(255, 255, 255);
|
||||
assertEquals("ffffff", ColorSelection.parseColorString(color));
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue