SEBSERV-29 SEBSERV-30 Indicator, ColorSelector, edx integration

This commit is contained in:
anhefti 2019-03-26 13:00:09 +01:00
parent 584a900529
commit 100bf77bd4
38 changed files with 896 additions and 268 deletions

View file

@ -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>

View file

@ -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();

View file

@ -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";
}

View file

@ -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
// + "]";
// }
}

View file

@ -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;
}

View file

@ -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 {
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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;
// }
}

View file

@ -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);
}

View file

@ -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())

View file

@ -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

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -19,7 +19,8 @@ public interface Selection {
enum Type {
SINGLE,
MULTI,
MULTI_COMBO
MULTI_COMBO,
COLOR,
}
Type type();

View file

@ -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 {
}

View file

@ -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,

View file

@ -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

View file

@ -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());

View file

@ -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);

View file

@ -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(),

View file

@ -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

View file

@ -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()));
}
}
}

View file

@ -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
}
}

View file

@ -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)

View file

@ -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;
});
}
}

View file

@ -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

View file

@ -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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

View file

@ -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));
}
}