SEBSERV-72 back and front-end implementation

This commit is contained in:
anhefti 2019-10-15 16:48:18 +02:00
parent 80effa4fe9
commit b71968628c
29 changed files with 1070 additions and 68 deletions

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.gbl.api;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
public final class API {
public enum BulkActionType {
@ -25,6 +27,7 @@ public final class API {
public static final String PARAM_ENTITY_TYPE = "entityType";
public static final String PARAM_BULK_ACTION_TYPE = "bulkActionType";
public static final String PARAM_CLIENT_CONFIG_SECRET = "client_config_secret";
public static final String DEFAULT_CONFIG_TEMPLATE_ID = String.valueOf(ConfigurationNode.DEFAULT_TEMPLATE_ID);
public static final String INSTITUTION_VAR_PATH_SEGMENT = "/{" + PARAM_INSTITUTION_ID + "}";
public static final String MODEL_ID_VAR_PATH_SEGMENT = "/{" + PARAM_MODEL_ID + "}";
@ -124,6 +127,10 @@ public final class API {
public static final String IMPORT_PASSWORD_ATTR_NAME = "importFilePassword";
public static final String IMPORT_FILE_ATTR_NAME = "importFile";
public static final String TEMPLATE_ATTRIBUTE_ENDPOINT = "/template-attribute";
public static final String CONFIGURATION_TEMPLATE_ENDPOINT = "/exam-config-template";
public static final String ORIENTATION_ENDPOINT = "/orientation";
public static final String VIEW_ENDPOINT = ORIENTATION_ENDPOINT + "/view";

View file

@ -0,0 +1,156 @@
/*
* 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.sebconfig;
import java.util.Comparator;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION;
import ch.ethz.seb.sebserver.gbl.model.Entity;
public final class TemplateAttribute implements Entity {
public static final String ATTR_CONFIG_ATTRIBUTE = "configAttribute";
public static final String ATTR_ORIENTATION = "orientation";
public static final String FILTER_ATTR_VIEW = "view";
public static final String FILTER_ATTR_GROUP = "group";
@NotNull
@JsonProperty(CONFIGURATION.ATTR_INSTITUTION_ID)
public final Long institutionId;
@NotNull
@JsonProperty(CONFIGURATION.ATTR_CONFIGURATION_NODE_ID)
public final Long templateId;
@NotNull
@JsonProperty(ATTR_CONFIG_ATTRIBUTE)
private final ConfigurationAttribute configAttribute;
@JsonProperty(ATTR_ORIENTATION)
private final Orientation orientation;
public TemplateAttribute(
@JsonProperty(CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId,
@JsonProperty(CONFIGURATION.ATTR_CONFIGURATION_NODE_ID) final Long templateId,
@JsonProperty(ATTR_CONFIG_ATTRIBUTE) final ConfigurationAttribute configAttribute,
@JsonProperty(ATTR_ORIENTATION) final Orientation orientation) {
this.institutionId = institutionId;
this.templateId = templateId;
this.configAttribute = configAttribute;
this.orientation = orientation;
}
@Override
public String getModelId() {
return this.institutionId != null
? String.valueOf(this.institutionId)
: null;
}
@Override
public EntityType entityType() {
return EntityType.CONFIGURATION_NODE;
}
@Override
public String getName() {
return this.configAttribute.name;
}
public Long getInstitutionId() {
return this.institutionId;
}
public Long getTemplateId() {
return this.templateId;
}
public ConfigurationAttribute getConfigAttribute() {
return this.configAttribute;
}
public Orientation getOrientation() {
return this.orientation;
}
@JsonIgnore
public String getViewModelId() {
if (this.orientation == null || this.orientation.viewId == null) {
return null;
}
return String.valueOf(this.orientation.viewId);
}
@JsonIgnore
public String getGroupId() {
if (this.orientation == null) {
return null;
}
return this.orientation.groupId;
}
@JsonIgnore
public boolean isNameLike(final String name) {
if (StringUtils.isBlank(name)) {
return true;
}
return this.configAttribute.name.contains(name);
}
@JsonIgnore
public boolean isInView(final Long viewId) {
if (viewId == null) {
return true;
}
return this.orientation != null && this.orientation.viewId.equals(viewId);
}
@JsonIgnore
public boolean isGroupLike(final String groupId) {
if (StringUtils.isBlank(groupId)) {
return true;
}
return this.orientation != null
&& this.orientation.groupId != null
&& this.orientation.groupId.contains(groupId);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("TemplateAttribute [institutionId=");
builder.append(this.institutionId);
builder.append(", templateId=");
builder.append(this.templateId);
builder.append(", configAttribute=");
builder.append(this.configAttribute);
builder.append(", orientation=");
builder.append(this.orientation);
builder.append("]");
return builder.toString();
}
public static final Comparator<TemplateAttribute> nameComparator(final boolean descending) {
return (attr1, attr2) -> attr1.configAttribute.name.compareToIgnoreCase(
attr2.configAttribute.name) * ((descending) ? -1 : 1);
}
}

View file

@ -22,6 +22,8 @@ import ch.ethz.seb.sebserver.gbl.model.Entity;
@JsonIgnoreProperties(ignoreUnknown = true)
public class View implements Entity {
public static final String FILTER_ATTR_TEMPLATE = "templateId";
@JsonProperty(VIEW.ATTR_ID)
public final Long id;

View file

@ -0,0 +1,50 @@
/*
* 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.content;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
@Lazy
@Component
@GuiProfile
public class ConfigTemplateAttributeForm implements TemplateComposer {
private final PageService pageService;
private final RestService restService;
private final CurrentUser currentUser;
private final ResourceService resourceService;
protected ConfigTemplateAttributeForm(
final PageService pageService,
final RestService restService,
final CurrentUser currentUser) {
this.pageService = pageService;
this.restService = restService;
this.currentUser = currentUser;
this.resourceService = pageService.getResourceService();
}
@Override
public void compose(final PageContext pageContext) {
// TODO Auto-generated method stub
}
}

View file

@ -0,0 +1,243 @@
/*
* 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.content;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
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.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
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.service.ResourceService;
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.PageService;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetTemplateAttributePage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ConfigTemplateForm implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(ConfigTemplateForm.class);
private static final LocTextKey FORM_TITLE_NEW =
new LocTextKey("sebserver.configtemplate.form.title.new");
private static final LocTextKey FORM_TITLE =
new LocTextKey("sebserver.configtemplate.form.title");
private static final LocTextKey FORM_NAME_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.form.name");
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.form.description");
private static final LocTextKey ATTRIBUTES_LIST_TITLE_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.attrs.list.title");
private static final LocTextKey ATTRIBUTES_LIST_NAME_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.attrs.list.name");
private static final LocTextKey ATTRIBUTES_LIST_VIEW_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.attrs.list.view");
private static final LocTextKey ATTRIBUTES_LIST_GROUP_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.attrs.list.group");
private final PageService pageService;
private final RestService restService;
private final CurrentUser currentUser;
private final ResourceService resourceService;
private final TableFilterAttribute nameFilter =
new TableFilterAttribute(CriteriaType.TEXT, TemplateAttribute.FILTER_ATTR_NAME);
private final TableFilterAttribute groupFilter =
new TableFilterAttribute(CriteriaType.TEXT, TemplateAttribute.FILTER_ATTR_GROUP);
protected ConfigTemplateForm(
final PageService pageService,
final RestService restService,
final CurrentUser currentUser) {
this.pageService = pageService;
this.restService = restService;
this.currentUser = currentUser;
this.resourceService = pageService.getResourceService();
}
@Override
public void compose(final PageContext pageContext) {
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final ResourceService resourceService = this.pageService.getResourceService();
final UserInfo user = this.currentUser.get();
final EntityKey entityKey = pageContext.getEntityKey();
final boolean isNew = entityKey == null;
// get data or create new. Handle error if happen
final ConfigurationNode examConfig = (isNew)
? ConfigurationNode.createNewTemplate(user.institutionId)
: this.restService
.getBuilder(GetExamConfigNode.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.get(pageContext::notifyError);
if (examConfig == null) {
log.error("Failed to get ConfigurationNode for Template. "
+ "Error was notified to the User. "
+ "See previous logs for more infomation");
return;
}
final EntityGrantCheck entityGrant = this.currentUser.entityGrantCheck(examConfig);
final boolean writeGrant = entityGrant.w();
final boolean modifyGrant = entityGrant.m();
final boolean isReadonly = pageContext.isReadonly();
// new PageContext with actual EntityKey
final PageContext formContext = pageContext
.withEntityKey(examConfig.getEntityKey());
// the default page layout with interactive title
final LocTextKey titleKey = (isNew)
? FORM_TITLE_NEW
: FORM_TITLE;
final Composite content = widgetFactory.defaultPageLayout(
formContext.getParent(),
titleKey);
// The SebClientConfig form
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
formContext.copyOf(content), 4)
.readonly(isReadonly)
.putStaticValueIf(() -> !isNew,
Domain.CONFIGURATION_NODE.ATTR_ID,
examConfig.getModelId())
.putStaticValue(
Domain.CONFIGURATION_NODE.ATTR_INSTITUTION_ID,
String.valueOf(examConfig.getInstitutionId()))
.putStaticValue(
Domain.CONFIGURATION_NODE.ATTR_TYPE,
ConfigurationType.TEMPLATE.name())
.putStaticValue(
Domain.CONFIGURATION_NODE.ATTR_STATUS,
ConfigurationStatus.IN_USE.name())
.addField(FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_NAME,
FORM_NAME_TEXT_KEY,
examConfig.name))
.addField(FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
FORM_DESCRIPTION_TEXT_KEY,
examConfig.description)
.asArea())
.buildFor((isNew)
? this.restService.getRestCall(NewExamConfig.class)
: this.restService.getRestCall(SaveExamConfig.class));
if (isReadonly) {
widgetFactory.label(content, "");
widgetFactory.labelLocalizedTitle(
content,
ATTRIBUTES_LIST_TITLE_TEXT_KEY);
final TableFilterAttribute viewFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
TemplateAttribute.FILTER_ATTR_VIEW,
() -> this.resourceService.getViewResources(entityKey.modelId));
final EntityTable<TemplateAttribute> attrTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetTemplateAttributePage.class))
.withRestCallAdapter(restCall -> restCall.withURIVariable(
API.PARAM_MODEL_ID,
entityKey.modelId))
.withPaging(15)
.withColumn(new ColumnDefinition<>(
Domain.CONFIGURATION_ATTRIBUTE.ATTR_NAME,
ATTRIBUTES_LIST_NAME_TEXT_KEY,
TemplateAttribute::getName)
.withFilter(this.nameFilter)
.sortable())
.withColumn(new ColumnDefinition<>(
Domain.ORIENTATION.ATTR_VIEW_ID,
ATTRIBUTES_LIST_VIEW_TEXT_KEY,
getViewNameFunction(entityKey))
.withFilter(viewFilter)
.sortable())
.withColumn(new ColumnDefinition<>(
Domain.ORIENTATION.ATTR_GROUP_ID,
ATTRIBUTES_LIST_GROUP_TEXT_KEY,
TemplateAttribute::getGroupId)
.withFilter(this.groupFilter)
.sortable())
// .withDefaultAction(pageActionBuilder
// .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST)
// .create())
.compose(pageContext.copyOf(content));
// TODO list of all attributes with filter
}
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_NEW)
.publishIf(() -> writeGrant && isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_MODIFY)
.withEntityKey(entityKey)
.publishIf(() -> modifyGrant && isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_SAVE)
.withEntityKey(entityKey)
.withExec(formHandle::processFormSave)
.ignoreMoveAwayFromEdit()
.publishIf(() -> !isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_CANCEL_MODIFY)
.withEntityKey(entityKey)
.withExec(this.pageService.backToCurrentFunction())
.publishIf(() -> !isReadonly);
}
private final Function<TemplateAttribute, String> getViewNameFunction(final EntityKey templateId) {
final Map<String, String> mapping = this.resourceService.getViewResources(templateId.modelId)
.stream()
.collect(Collectors.toMap(tuple -> tuple._1, tuple -> tuple._2));
return attr -> mapping.get(attr.getViewModelId());
}
}

View file

@ -9,7 +9,6 @@
package ch.ethz.seb.sebserver.gui.content;
import org.eclipse.swt.widgets.Composite;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@ -17,6 +16,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
@ -35,6 +35,7 @@ import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@ -43,10 +44,14 @@ public class SebExamConfigList implements TemplateComposer {
private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
new LocTextKey("sebserver.examconfig.list.action.no.modify.privilege");
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
private static final LocTextKey EMPTY_CONFIG_LIST_TEXT_KEY =
new LocTextKey("sebserver.examconfig.list.empty");
private static final LocTextKey TITLE_TEXT_KEY =
private static final LocTextKey EMPTY_TEMPLATE_LIST_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.list.empty");
private static final LocTextKey TITLE_CONFIGURATION_TEXT_KEY =
new LocTextKey("sebserver.examconfig.list.title");
private static final LocTextKey TITLE_TEMPLATE_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.list.title");
private static final LocTextKey INSTITUTION_TEXT_KEY =
new LocTextKey("sebserver.examconfig.list.column.institution");
private static final LocTextKey NAME_TEXT_KEY =
@ -57,6 +62,8 @@ public class SebExamConfigList implements TemplateComposer {
new LocTextKey("sebserver.examconfig.list.column.status");
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.examconfig.info.pleaseSelect");
private static final LocTextKey EMPTY_TEMPLATE_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.configtemplate.info.pleaseSelect");
private final TableFilterAttribute institutionFilter;
private final TableFilterAttribute nameFilter =
@ -67,19 +74,16 @@ public class SebExamConfigList implements TemplateComposer {
private final RestService restService;
private final CurrentUser currentUser;
private final ResourceService resourceService;
private final int pageSize;
protected SebExamConfigList(
final PageService pageService,
final RestService restService,
final CurrentUser currentUser,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
final CurrentUser currentUser) {
this.pageService = pageService;
this.restService = restService;
this.currentUser = currentUser;
this.resourceService = pageService.getResourceService();
this.pageSize = pageSize;
this.institutionFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
@ -94,20 +98,24 @@ public class SebExamConfigList implements TemplateComposer {
@Override
public void compose(final PageContext pageContext) {
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
final Composite content = widgetFactory.defaultPageLayout(
pageContext.getParent(),
TITLE_TEXT_KEY);
TITLE_CONFIGURATION_TEXT_KEY);
final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
final PageActionBuilder pageActionBuilder =
this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
// table
final EntityTable<ConfigurationNode> table =
// exam configuration table
final EntityTable<ConfigurationNode> configTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigNodePage.class))
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
.withPaging(this.pageSize)
.withStaticFilter(
Domain.CONFIGURATION_NODE.ATTR_TYPE,
ConfigurationType.EXAM_CONFIG.name())
.withEmptyMessage(EMPTY_CONFIG_LIST_TEXT_KEY)
.withPaging(6)
.withColumnIf(
() -> isSEBAdmin,
() -> new ColumnDefinition<>(
@ -139,27 +147,80 @@ public class SebExamConfigList implements TemplateComposer {
.create())
.compose(pageContext.copyOf(content));
// configuration template table
widgetFactory.label(content, "");
widgetFactory.labelLocalizedTitle(
content,
TITLE_TEMPLATE_TEXT_KEY);
final EntityTable<ConfigurationNode> templateTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigNodePage.class))
.withStaticFilter(
Domain.CONFIGURATION_NODE.ATTR_TYPE,
ConfigurationType.TEMPLATE.name())
.withEmptyMessage(EMPTY_TEMPLATE_LIST_TEXT_KEY)
.withPaging(6)
.withColumnIf(
() -> isSEBAdmin,
() -> new ColumnDefinition<>(
Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
INSTITUTION_TEXT_KEY,
this.resourceService::localizedExamConfigInstitutionName)
.withFilter(this.institutionFilter)
.sortable())
.withColumn(new ColumnDefinition<>(
Domain.CONFIGURATION_NODE.ATTR_NAME,
NAME_TEXT_KEY,
ConfigurationNode::getName)
.withFilter(this.nameFilter)
.sortable())
.withColumn(new ColumnDefinition<>(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
DESCRIPTION_TEXT_KEY,
ConfigurationNode::getDescription)
.withFilter(this.nameFilter)
.sortable())
.withDefaultAction(pageActionBuilder
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST)
.create())
.compose(pageContext.copyOf(content));
final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE);
pageActionBuilder
// Exam Configuration actions...
.newAction(ActionDefinition.SEB_EXAM_CONFIG_NEW)
.publishIf(examConfigGrant::iw)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> table.hasAnyContent())
.withSelect(configTable::getSelection, PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> configTable.hasAnyContent())
.newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST)
.withSelect(
table.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> examConfigGrant.im() && table.hasAnyContent())
.publishIf(() -> examConfigGrant.im() && configTable.hasAnyContent())
.newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY_FROM_LIST)
.withSelect(
table.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> examConfigGrant.im() && table.hasAnyContent());
.publishIf(() -> examConfigGrant.im() && configTable.hasAnyContent())
// Exam Configuration template actions...
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_NEW)
.publishIf(examConfigGrant::iw)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST)
.withSelect(templateTable::getSelection, PageAction::applySingleSelection,
EMPTY_TEMPLATE_SELECTION_TEXT_KEY)
.publishIf(() -> templateTable.hasAnyContent())
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_MODIFY_FROM_LIST)
.withSelect(
templateTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
PageAction::applySingleSelection, EMPTY_TEMPLATE_SELECTION_TEXT_KEY)
.publishIf(() -> examConfigGrant.im() && templateTable.hasAnyContent());
}
}

View file

@ -113,15 +113,11 @@ public class SebExamConfigPropForm implements TemplateComposer {
final UserInfo user = this.currentUser.get();
final EntityKey entityKey = pageContext.getEntityKey();
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
final boolean isNew = entityKey == null;
// get data or create new. Handle error if happen
final ConfigurationNode examConfig = (isNew)
? ConfigurationNode.createNewExamConfig((parentEntityKey != null)
? Long.valueOf(parentEntityKey.modelId)
: user.institutionId)
? ConfigurationNode.createNewExamConfig(user.institutionId)
: this.restService
.getBuilder(GetExamConfigNode.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)

View file

@ -21,6 +21,8 @@ public enum ActionCategory {
INDICATOR_LIST(new LocTextKey("sebserver.exam.indicator.list.actions"), 2),
SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.list.actions"), 1),
SEB_EXAM_CONFIG_LIST(new LocTextKey("sebserver.examconfig.list.actions"), 1),
SEB_CONFIG_TEMPLATE_LIST(new LocTextKey("sebserver.configtemplate.list.actions"), 2),
SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST(new LocTextKey("sebserver.configtemplate.attr.list.actions"), 1),
RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1),
CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1),
LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),

View file

@ -415,6 +415,58 @@ public enum ActionDefinition {
PageStateDefinitionImpl.SEB_EXAM_CONFIG_EDIT,
ActionCategory.FORM),
SEB_EXAM_CONFIG_TEMPLATE_NEW(
new LocTextKey("sebserver.exam.configtemplate.action.list.new"),
ImageIcon.NEW,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
ActionCategory.VARIA),
SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST(
new LocTextKey("sebserver.configtemplate.action.list.view"),
ImageIcon.SHOW,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_VIEW,
ActionCategory.SEB_CONFIG_TEMPLATE_LIST),
SEB_EXAM_CONFIG_TEMPLATE_VIEW(
new LocTextKey("sebserver.configtemplate.action.view"),
ImageIcon.SHOW,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_VIEW,
ActionCategory.FORM),
SEB_EXAM_CONFIG_TEMPLATE_MODIFY_FROM_LIST(
new LocTextKey("sebserver.configtemplate.action.list.modify"),
ImageIcon.EDIT,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
ActionCategory.SEB_CONFIG_TEMPLATE_LIST),
SEB_EXAM_CONFIG_TEMPLATE_MODIFY(
new LocTextKey("sebserver.configtemplate.action.modify"),
ImageIcon.EDIT,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
ActionCategory.FORM),
SEB_EXAM_CONFIG_TEMPLATE_CANCEL_MODIFY(
new LocTextKey("sebserver.overall.action.modify.cancel"),
ImageIcon.CANCEL,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_VIEW,
ActionCategory.FORM),
SEB_EXAM_CONFIG_TEMPLATE_SAVE(
new LocTextKey("sebserver.configtemplate.action.save"),
ImageIcon.SAVE,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_VIEW,
ActionCategory.FORM),
SEB_EXAM_CONFIG_TEMPLATE_ATTR_EDIT(
new LocTextKey("sebserver.configtemplate.attr.list.actions.modify"),
ImageIcon.EDIT,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_ATTRIBUTE_VIEW,
ActionCategory.SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST),
SEB_EXAM_CONFIG_TEMPLATE_ATTR_SET_DEFAULT(
new LocTextKey("sebserver.configtemplate.attr.list.actions.setdefault"),
ImageIcon.SAVE,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_ATTRIBUTE_VIEW,
ActionCategory.SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST),
SEB_EXAM_CONFIG_TEMPLATE_ATTR_REMOVE_VIEW(
new LocTextKey("sebserver.configtemplate.attr.list.actions.removeview"),
ImageIcon.DELETE,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_ATTRIBUTE_VIEW,
ActionCategory.SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST),
RUNNING_EXAM_VIEW_LIST(
new LocTextKey("sebserver.monitoring.action.list"),
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM_LIST),

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.gui.content.activity;
import ch.ethz.seb.sebserver.gui.content.ConfigTemplateAttributeForm;
import ch.ethz.seb.sebserver.gui.content.ConfigTemplateForm;
import ch.ethz.seb.sebserver.gui.content.ExamForm;
import ch.ethz.seb.sebserver.gui.content.ExamList;
import ch.ethz.seb.sebserver.gui.content.ExamSebConfigMapForm;
@ -67,6 +69,18 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
SEB_EXAM_CONFIG_PROP_EDIT(Type.FORM_EDIT, SebExamConfigPropForm.class, ActivityDefinition.SEB_EXAM_CONFIG),
SEB_EXAM_CONFIG_EDIT(Type.FORM_VIEW, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG),
SEB_EXAM_CONFIG_TEMPLATE_VIEW(Type.FORM_VIEW, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG),
SEB_EXAM_CONFIG_TEMPLATE_EDIT(Type.FORM_EDIT, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG),
SEB_EXAM_CONFIG_TEMPLATE_ATTRIBUTE_VIEW(
Type.FORM_VIEW,
ConfigTemplateAttributeForm.class,
ActivityDefinition.SEB_EXAM_CONFIG),
SEB_EXAM_CONFIG_TEMPLATE_ATTRIBUTE_EDIT(
Type.FORM_EDIT,
ConfigTemplateAttributeForm.class,
ActivityDefinition.SEB_EXAM_CONFIG),
MONITORING_RUNNING_EXAM_LIST(Type.LIST_VIEW, MonitoringRunningExamList.class, ActivityDefinition.MONITORING_EXAMS),
MONITORING_RUNNING_EXAM(Type.FORM_VIEW, MonitoringRunningExam.class, ActivityDefinition.MONITORING_EXAMS),
MONITORING_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.MONITORING_EXAMS),

View file

@ -27,6 +27,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.ProxyData.ProxyAuthType;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@ -40,6 +41,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
@ -58,6 +60,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExams;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetViews;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
@ -465,6 +468,23 @@ public class ResourceService {
.collect(Collectors.toList());
}
public List<Tuple<String>> getViewResources() {
return getViewResources(API.DEFAULT_CONFIG_TEMPLATE_ID);
}
public List<Tuple<String>> getViewResources(final String templateId) {
return this.restService.getBuilder(GetViews.class)
.withQueryParam(
View.FILTER_ATTR_TEMPLATE,
templateId)
.call()
.getOr(Collections.emptyList())
.stream()
.map(view -> new Tuple<>(view.getModelId(), view.name))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
public Map<Long, String> getExamNameMapping() {
final UserInfo userInfo = this.currentUser.get();
return this.restService.getBuilder(GetExamNames.class)

View file

@ -0,0 +1,43 @@
/*
* 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.service.remote.webservice.api.seb.examconfig;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetTemplateAttributePage extends RestCall<Page<TemplateAttribute>> {
public GetTemplateAttributePage() {
super(new TypeKey<>(
CallType.GET_PAGE,
EntityType.CONFIGURATION_NODE,
new TypeReference<Page<TemplateAttribute>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.CONFIGURATION_NODE_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.TEMPLATE_ATTRIBUTE_ENDPOINT);
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.service.remote.webservice.api.seb.examconfig;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetViewPage extends RestCall<Page<View>> {
public GetViewPage() {
super(new TypeKey<>(
CallType.GET_PAGE,
EntityType.VIEW,
new TypeReference<Page<View>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.VIEW_ENDPOINT);
}
}

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.service.remote.webservice.api.seb.examconfig;
import java.util.List;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.PageToListCallAdapter;
@Lazy
@Component
@GuiProfile
public class GetViews extends PageToListCallAdapter<View> {
public GetViews() {
super(
GetViewPage.class,
EntityType.VIEW,
new TypeReference<List<View>>() {
},
API.VIEW_ENDPOINT);
}
}

View file

@ -36,6 +36,7 @@ import org.eclipse.swt.widgets.Widget;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@ -78,6 +79,7 @@ public class EntityTable<ROW extends Entity> {
private final TableFilter<ROW> filter;
private final Table table;
private final TableNavigator navigator;
private final MultiValueMap<String, String> staticQueryParams;
int pageNumber = 1;
int pageSize;
@ -96,7 +98,8 @@ public class EntityTable<ROW extends Entity> {
final int pageSize,
final LocTextKey emptyMessage,
final Function<EntityTable<ROW>, PageAction> defaultActionFunction,
final boolean hideNavigation) {
final boolean hideNavigation,
final MultiValueMap<String, String> staticQueryParams) {
this.composite = new Composite(pageContext.getParent(), type);
this.pageService = pageService;
@ -116,6 +119,7 @@ public class EntityTable<ROW extends Entity> {
this.composite.setLayout(layout);
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
this.composite.setLayoutData(gridData);
this.staticQueryParams = staticQueryParams;
// TODO just for debugging, remove when tested
// this.composite.setBackground(new Color(parent.getDisplay(), new RGB(0, 200, 0)));
@ -375,6 +379,7 @@ public class EntityTable<ROW extends Entity> {
.withPaging(pageNumber, pageSize)
.withSorting(sortColumn, sortOrder)
.withQueryParams((this.filter != null) ? this.filter.getFilterParameter() : null)
.withQueryParams(this.staticQueryParams)
.apply(this.restCallAdapter)
.call()
.map(this::createTableRowsFromPage)
@ -494,7 +499,8 @@ public class EntityTable<ROW extends Entity> {
item.setText(index, this.i18nSupport.formatDisplayDate((DateTime) value));
} else {
if (value != null) {
item.setText(index, String.valueOf(value));
final String val = String.valueOf(value).replace('\n', ' ');
item.setText(index, val);
} else {
item.setText(index, Constants.EMPTY_NOTE);
}

View file

@ -15,6 +15,8 @@ import java.util.function.Function;
import java.util.function.Supplier;
import org.eclipse.swt.SWT;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
@ -28,6 +30,7 @@ public class TableBuilder<ROW extends Entity> {
private final PageService pageService;
final RestCall<Page<ROW>> restCall;
private final MultiValueMap<String, String> staticQueryParams;
final List<ColumnDefinition<ROW>> columns = new ArrayList<>();
LocTextKey emptyMessage;
private Function<EntityTable<ROW>, PageAction> defaultActionFunction;
@ -42,6 +45,7 @@ public class TableBuilder<ROW extends Entity> {
this.pageService = pageService;
this.restCall = restCall;
this.staticQueryParams = new LinkedMultiValueMap<>();
}
public TableBuilder<ROW> hideNavigation() {
@ -85,6 +89,11 @@ public class TableBuilder<ROW extends Entity> {
return this;
}
public TableBuilder<ROW> withStaticFilter(final String name, final String value) {
this.staticQueryParams.add(name, value);
return this;
}
public TableBuilder<ROW> withDefaultActionIf(
final BooleanSupplier condition,
final Supplier<PageAction> actionSupplier) {
@ -128,7 +137,8 @@ public class TableBuilder<ROW extends Entity> {
this.pageSize,
this.emptyMessage,
this.defaultActionFunction,
this.hideNavigation);
this.hideNavigation,
this.staticQueryParams);
}
}

View file

@ -17,7 +17,7 @@ public interface ConfigurationAttributeDAO extends EntityDAO<ConfigurationAttrib
/** Use this to get all ConfigurationAttribute that are root attributes and no child
* attributes (has no parent reference).
*
*
* @return Collection of all ConfigurationAttribute that are root attributes */
Result<Collection<ConfigurationAttribute>> getAllRootAttributes();

View file

@ -9,17 +9,25 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection;
import java.util.Map;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.util.Result;
public interface OrientationDAO extends EntityDAO<Orientation, Orientation> {
/** Use this to delete all Orientation of a defined template.
*
*
* @param templateId the template identifier (PK)
* @return Collection of all EntityKey of Orientations that has been deleted */
Result<Collection<EntityKey>> deleteAllOfTemplate(Long templateId);
Result<ConfigurationNode> copyDefaultOrientationsForTemplate(
ConfigurationNode node,
Map<Long, Long> viewMapping);
Result<Collection<Orientation>> getAllOfTemplate(Long templateId);
}

View file

@ -8,8 +8,14 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Map;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
import ch.ethz.seb.sebserver.gbl.util.Result;
public interface ViewDAO extends EntityDAO<View, View> {
Result<Map<Long, Long>> copyDefaultViewsForTemplate(ConfigurationNode node);
}

View file

@ -113,10 +113,10 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
SqlBuilder.isEqualToWhenPresent(filterMap.getInstitutionId()))
.and(
ConfigurationNodeRecordDynamicSqlSupport.name,
SqlBuilder.isEqualToWhenPresent(filterMap.getName()))
SqlBuilder.isLikeWhenPresent(filterMap.getName()))
.and(
ConfigurationNodeRecordDynamicSqlSupport.description,
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigNodeDesc()))
SqlBuilder.isLikeWhenPresent(filterMap.getConfigNodeDesc()))
.and(
ConfigurationNodeRecordDynamicSqlSupport.type,
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigNodeType()))

View file

@ -105,7 +105,7 @@ public class InstitutionDAOImpl implements InstitutionDAO {
SqlBuilder.isEqualToWhenPresent(filterMap.getActiveAsInt()))
.and(
InstitutionRecordDynamicSqlSupport.name,
SqlBuilder.isEqualToWhenPresent(filterMap.getName()))
SqlBuilder.isLikeWhenPresent(filterMap.getName()))
.build()
.execute()
.stream()

View file

@ -14,6 +14,7 @@ import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -25,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TitleOrientation;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@ -151,6 +153,55 @@ public class OrientationDAOImpl implements OrientationDAO {
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<ConfigurationNode> copyDefaultOrientationsForTemplate(
final ConfigurationNode node,
final Map<Long, Long> viewMapping) {
return Result.tryCatch(() -> {
this.orientationRecordMapper
.selectByExample()
.where(
OrientationRecordDynamicSqlSupport.templateId,
SqlBuilder.isEqualTo(ConfigurationNode.DEFAULT_TEMPLATE_ID))
.build()
.execute()
.stream()
.forEach(record -> createNew(new Orientation(
null,
record.getConfigAttributeId(),
node.id,
viewMapping.get(record.getViewId()),
record.getGroupId(),
record.getxPosition(),
record.getyPosition(),
record.getWidth(),
record.getHeight(),
record.getTitle() != null
? TitleOrientation.valueOf(record.getTitle())
: TitleOrientation.NONE)));
return node;
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Orientation>> getAllOfTemplate(final Long templateId) {
return Result.tryCatch(() -> this.orientationRecordMapper
.selectByExample()
.where(
OrientationRecordDynamicSqlSupport.templateId,
SqlBuilder.isEqualToWhenPresent(templateId))
.build()
.execute()
.stream()
.map(OrientationDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()));
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
@ -215,5 +266,4 @@ public class OrientationDAOImpl implements OrientationDAO {
record.getHeight(),
TitleOrientation.valueOf(record.getTitle())));
}
}

View file

@ -13,6 +13,7 @@ import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -24,9 +25,11 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ViewRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ViewRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ViewRecord;
@ -85,6 +88,9 @@ public class ViewDAOImpl implements ViewDAO {
.where(
ViewRecordDynamicSqlSupport.name,
SqlBuilder.isEqualToWhenPresent(filterMap.getName()))
.and(
ViewRecordDynamicSqlSupport.templateId,
SqlBuilder.isEqualToWhenPresent(filterMap.getLong(View.FILTER_ATTR_TEMPLATE)))
.build()
.execute()
.stream()
@ -132,6 +138,34 @@ public class ViewDAOImpl implements ViewDAO {
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Map<Long, Long>> copyDefaultViewsForTemplate(final ConfigurationNode node) {
return Result.tryCatch(() -> {
// get all default views, copy them with new template_id and save
return this.viewRecordMapper
.selectByExample()
.where(
ViewRecordDynamicSqlSupport.templateId,
SqlBuilder.isEqualTo(ConfigurationNode.DEFAULT_TEMPLATE_ID))
.build()
.execute()
.stream()
.map(view -> {
final ViewRecord newRecord = new ViewRecord(
null,
view.getName(),
view.getColumns(),
view.getPosition(),
node.id);
this.viewRecordMapper.insert(newRecord);
return new Tuple<>(view.getId(), newRecord.getId());
})
.collect(Collectors.toMap(t -> t._1, t -> t._2));
});
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {

View file

@ -12,18 +12,25 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@ -34,11 +41,19 @@ import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
@ -46,9 +61,13 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.Authorization
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.OrientationDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ViewDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@ -60,6 +79,9 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
private static final Logger log = LoggerFactory.getLogger(ConfigurationNodeController.class);
private final ConfigurationDAO configurationDAO;
private final ViewDAO viewDAO;
private final OrientationDAO orientationDAO;
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final SebExamConfigService sebExamConfigService;
protected ConfigurationNodeController(
@ -70,6 +92,9 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
final PaginationService paginationService,
final BeanValidationService beanValidationService,
final ConfigurationDAO configurationDAO,
final ViewDAO viewDAO,
final OrientationDAO orientationDAO,
final ConfigurationAttributeDAO configurationAttributeDAO,
final SebExamConfigService sebExamConfigService) {
super(authorization,
@ -80,6 +105,9 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
beanValidationService);
this.configurationDAO = configurationDAO;
this.viewDAO = viewDAO;
this.orientationDAO = orientationDAO;
this.configurationAttributeDAO = configurationAttributeDAO;
this.sebExamConfigService = sebExamConfigService;
}
@ -206,4 +234,101 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
}
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.TEMPLATE_ATTRIBUTE_ENDPOINT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Page<TemplateAttribute> getTemplateAttributePage(
@PathVariable final Long modelId,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
// at least current user must have read access for specified entity type within its own institution
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
// if current user has no read access for specified entity type within other institution
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance
if (!this.authorization.hasGrant(PrivilegeType.READ, getGrantEntityType())) {
filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
}
final Map<Long, Orientation> orentiations = this.orientationDAO.getAllOfTemplate(modelId)
.getOrThrow()
.stream()
.collect(Collectors.toMap(
o -> o.attributeId,
Function.identity()));
final List<TemplateAttribute> attrs = this.configurationAttributeDAO
.getAllRootAttributes()
.getOrThrow()
.stream()
.map(attr -> new TemplateAttribute(institutionId, modelId, attr, orentiations.get(attr.id)))
.filter(attr -> attr.isNameLike(filterMap.getString(TemplateAttribute.FILTER_ATTR_NAME))
&& attr.isGroupLike(filterMap.getString(TemplateAttribute.FILTER_ATTR_GROUP))
&& attr.isInView(filterMap.getLong(TemplateAttribute.FILTER_ATTR_VIEW)))
.collect(Collectors.toList());
if (!StringUtils.isBlank(sort)) {
final String sortBy = PageSortOrder.decode(sort);
final PageSortOrder sortOrder = PageSortOrder.getSortOrder(sort);
if (sortBy.equals(Domain.CONFIGURATION_NODE.ATTR_NAME)) {
Collections.sort(attrs, TemplateAttribute.nameComparator(sortOrder == PageSortOrder.DESCENDING));
}
}
final int start = (pageNumber - 1) * pageSize;
int end = start + pageSize;
if (attrs.size() < end) {
end = attrs.size();
}
return new Page<>(
attrs.size() / pageSize,
pageNumber,
sort,
attrs.subList(start, end));
}
@Override
protected Result<ConfigurationNode> validForSave(final ConfigurationNode entity) {
return super.validForSave(entity)
.map(e -> {
final ConfigurationNode existingNode = this.entityDAO.byPK(entity.id)
.getOrThrow();
if (existingNode.type != entity.type) {
throw new APIConstraintViolationException(
"The Type of ConfigurationNode cannot change after creation");
}
return e;
});
}
@Override
protected Result<ConfigurationNode> notifyCreated(final ConfigurationNode entity) {
return super.notifyCreated(entity)
.map(this::createTemplate);
}
private ConfigurationNode createTemplate(final ConfigurationNode node) {
if (node.type != null && node.type == ConfigurationType.TEMPLATE) {
// create views and orientations for node
return this.viewDAO.copyDefaultViewsForTemplate(node)
.flatMap(viewMapping -> this.orientationDAO.copyDefaultOrientationsForTemplate(
node,
viewMapping))
.getOrThrow();
}
return node;
}
}

View file

@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
@ -57,6 +58,11 @@ public class ViewController extends EntityController<View, View> {
return ViewRecordDynamicSqlSupport.viewRecord;
}
@Override
protected EntityType getGrantEntityType() {
return EntityType.CONFIGURATION_NODE;
}
@Override
protected Result<View> checkCreateAccess(final View entity) {
// Skips the entity based grant check

View file

@ -41,17 +41,17 @@ INSERT IGNORE INTO threshold VALUES
;
INSERT IGNORE INTO view VALUES
(1, 'general', 4, 1, null),
(2, 'user_interface', 12, 2, null),
(3, 'browser', 12, 3, null),
(4, 'down_upload', 12, 4, null),
(5, 'exam', 12, 5, null),
(6, 'applications', 12, 6, null),
(7, 'resources', 12, 7, null),
(8, 'network', 12, 8, null),
(9, 'security', 12, 9, null),
(10, 'registry', 12, 10, null),
(11, 'hooked_keys', 12, 11, null);
(1, 'general', 4, 1, 0),
(2, 'user_interface', 12, 2, 0),
(3, 'browser', 12, 3, 0),
(4, 'down_upload', 12, 4, 0),
(5, 'exam', 12, 5, 0),
(6, 'applications', 12, 6, 0),
(7, 'resources', 12, 7, 0),
(8, 'network', 12, 8, 0),
(9, 'security', 12, 9, 0),
(10, 'registry', 12, 10, 0),
(11, 'hooked_keys', 12, 11, 0);
INSERT IGNORE INTO configuration_attribute VALUES
(1, 'hashedAdminPassword', 'PASSWORD_FIELD', null, null, null, null, null),

View file

@ -14,17 +14,17 @@ INSERT IGNORE INTO user_role VALUES
;
INSERT IGNORE INTO view VALUES
(1, 'general', 4, 1, null),
(2, 'user_interface', 12, 2, null),
(3, 'browser', 12, 3, null),
(4, 'down_upload', 12, 4, null),
(5, 'exam', 12, 5, null),
(6, 'applications', 12, 6, null),
(7, 'resources', 12, 7, null),
(8, 'network', 12, 8, null),
(9, 'security', 12, 9, null),
(10, 'registry', 12, 10, null),
(11, 'hooked_keys', 12, 11, null);
(1, 'general', 4, 1, 0),
(2, 'user_interface', 12, 2, 0),
(3, 'browser', 12, 3, 0),
(4, 'down_upload', 12, 4, 0),
(5, 'exam', 12, 5, 0),
(6, 'applications', 12, 6, 0),
(7, 'resources', 12, 7, 0),
(8, 'network', 12, 8, 0),
(9, 'security', 12, 9, 0),
(10, 'registry', 12, 10, 0),
(11, 'hooked_keys', 12, 11, 0);
INSERT IGNORE INTO configuration_attribute VALUES
(1, 'hashedAdminPassword', 'PASSWORD_FIELD', null, null, null, null, null),

View file

@ -429,7 +429,7 @@ sebserver.examconfig.list.column.status=Status
sebserver.examconfig.list.actions=Selected Configuration
sebserver.examconfig.list.empty=There is currently no SEB-Exam configuration available. Please create a new one
sebserver.examconfig.info.pleaseSelect=Please Select an exam configuration first
sebserver.examconfig.info.pleaseSelect=Please select an exam configuration first
sebserver.examconfig.list.action.no.modify.privilege=No Access: An Exam Configuration from other institution cannot be modified.
sebserver.examconfig.action.list.new=Add Exam Configuration
@ -925,6 +925,38 @@ sebserver.examconfig.props.validation.DecimalTypeValidator=Invalid decimal numbe
sebserver.examconfig.props.validation.ExitKeySequenceValidator=Key is already in sequence
sebserver.examconfig.props.validation.WindowsSizeValidator=Invalid number
################################
# SEB Exam Configuration Template
################################
sebserver.configtemplate.list.title=Configuration Templates
sebserver.configtemplate.list.empty=There is currently no SEB-Exam configuration template available. Please create a new one
sebserver.configtemplate.list.actions=Selected Template
sebserver.configtemplate.info.pleaseSelect=Please select an exam configuration template first
sebserver.exam.configtemplate.action.list.new=New Template
sebserver.configtemplate.action.list.view=View Template
sebserver.configtemplate.action.view=View Template
sebserver.configtemplate.action.list.modify=Edit Template
sebserver.configtemplate.action.modify=Edit Template
sebserver.configtemplate.form.title.new=New Template
sebserver.configtemplate.form.title=Configuration Template
sebserver.configtemplate.form.name=Name
sebserver.configtemplate.form.description=Description
sebserver.configtemplate.action.save=Save Template
sebserver.configtemplate.attrs.list.title=Configuration Attributes
sebserver.configtemplate.attrs.list.name=Name
sebserver.configtemplate.attrs.list.view=View
sebserver.configtemplate.attrs.list.group=Group
sebserver.configtemplate.attr.list.actions=Selected Attribute
sebserver.configtemplate.attr.list.actions.modify=Edit Attribute
sebserver.configtemplate.attr.list.actions.setdefault=Set Default Values
sebserver.configtemplate.attr.list.actions.removeview=Remove View
################################
# Monitoring

View file

@ -22,17 +22,17 @@ INSERT IGNORE INTO threshold VALUES
;
INSERT IGNORE INTO view VALUES
(1, 'general', 4, 1, null),
(2, 'user_interface', 12, 2, null),
(3, 'browser', 12, 3, null),
(4, 'down_upload', 12, 4, null),
(5, 'exam', 12, 5, null),
(6, 'applications', 12, 6, null),
(7, 'resources', 12, 7, null),
(8, 'network', 12, 8, null),
(9, 'security', 12, 9, null),
(10, 'registry', 12, 10, null),
(11, 'hooked_keys', 12, 11, null);
(1, 'general', 4, 1, 0),
(2, 'user_interface', 12, 2, 0),
(3, 'browser', 12, 3, 0),
(4, 'down_upload', 12, 4, 0),
(5, 'exam', 12, 5, 0),
(6, 'applications', 12, 6, 0),
(7, 'resources', 12, 7, 0),
(8, 'network', 12, 8, 0),
(9, 'security', 12, 9, 0),
(10, 'registry', 12, 10, 0),
(11, 'hooked_keys', 12, 11, 0);
INSERT IGNORE INTO configuration_attribute VALUES
(1, 'hashedAdminPassword', 'PASSWORD_FIELD', null, null, null, null, null),