SEBSERV-63 monitoring and running exam list page

This commit is contained in:
anhefti 2019-07-08 14:54:17 +02:00
parent 43283fe14f
commit 5f9a2c6fe0
27 changed files with 546 additions and 96 deletions

View file

@ -17,7 +17,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
@ -49,6 +48,8 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@GuiProfile
public class ExamList implements TemplateComposer {
private static final LocTextKey PAGE_TITLE_KEY =
new LocTextKey("sebserver.exam.list.title");
private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
new LocTextKey("sebserver.exam.list.action.no.modify.privilege");
private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
@ -69,6 +70,7 @@ public class ExamList implements TemplateComposer {
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
private final TableFilterAttribute startTimeFilter =
new TableFilterAttribute(CriteriaType.DATE, QuizData.FILTER_ATTR_START_TIME);
private final TableFilterAttribute typeFilter;
private final PageService pageService;
private final ResourceService resourceService;
@ -77,16 +79,21 @@ public class ExamList implements TemplateComposer {
protected ExamList(
final PageService pageService,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.resourceService = resourceService;
this.pageSize = (pageSize != null) ? pageSize : 20;
this.pageSize = pageSize;
this.lmsFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
LmsSetup.FILTER_ATTR_LMS_SETUP,
this.resourceService::lmsSetupResource);
this.typeFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
Exam.FILTER_ATTR_TYPE,
this.resourceService::examTypeResources);
}
@Override
@ -100,9 +107,10 @@ public class ExamList implements TemplateComposer {
// content page layout with title
final Composite content = widgetFactory.defaultPageLayout(
pageContext.getParent(),
new LocTextKey("sebserver.exam.list.title"));
PAGE_TITLE_KEY);
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
// table
final EntityTable<Exam> table =
@ -132,7 +140,8 @@ public class ExamList implements TemplateComposer {
.withColumn(new ColumnDefinition<>(
Domain.EXAM.ATTR_TYPE,
COLUMN_TITLE_TYPE_KEY,
this::examTypeName)
this.resourceService::examTypeName)
.withFilter(this.typeFilter)
.sortable())
.withDefaultAction(actionBuilder
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
@ -177,13 +186,4 @@ public class ExamList implements TemplateComposer {
.apply(String.valueOf(exam.lmsSetupId));
}
private String examTypeName(final Exam exam) {
if (exam.type == null) {
return Constants.EMPTY_NOTE;
}
return this.resourceService.getI18nSupport()
.getText(ResourceService.EXAM_TYPE_PREFIX + exam.type.name());
}
}

View file

@ -9,6 +9,7 @@
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;
@ -51,15 +52,18 @@ public class InstitutionList implements TemplateComposer {
private final PageService pageService;
private final RestService restService;
private final CurrentUser currentUser;
private final int pageSize;
protected InstitutionList(
final PageService pageService,
final RestService restService,
final CurrentUser currentUser) {
final CurrentUser currentUser,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.restService = restService;
this.currentUser = currentUser;
this.pageSize = pageSize;
}
@Override
@ -75,7 +79,7 @@ public class InstitutionList implements TemplateComposer {
final EntityTable<Institution> table =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetInstitutionPage.class))
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
.withPaging(3)
.withPaging(this.pageSize)
.withColumn(new ColumnDefinition<>(
Domain.INSTITUTION.ATTR_NAME,
NAME_TEXT_KEY,

View file

@ -20,6 +20,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.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
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;
@ -66,6 +67,7 @@ public class LmsSetupList implements TemplateComposer {
private final TableFilterAttribute nameFilter =
new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME);
private final TableFilterAttribute typeFilter;
private final TableFilterAttribute activityFilter;
private final PageService pageService;
private final ResourceService resourceService;
@ -74,11 +76,11 @@ public class LmsSetupList implements TemplateComposer {
protected LmsSetupList(
final PageService pageService,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.resourceService = resourceService;
this.pageSize = (pageSize != null) ? pageSize : 20;
this.pageSize = pageSize;
this.institutionFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
@ -89,6 +91,11 @@ public class LmsSetupList implements TemplateComposer {
CriteriaType.SINGLE_SELECTION,
Domain.LMS_SETUP.ATTR_LMS_TYPE,
this.resourceService::lmsTypeResources);
this.activityFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
UserInfo.FILTER_ATTR_ACTIVE,
this.resourceService::activityResources);
}
@Override
@ -135,6 +142,7 @@ public class LmsSetupList implements TemplateComposer {
Domain.LMS_SETUP.ATTR_ACTIVE,
ACTIVITY_TEXT_KEY,
LmsSetup::getActive)
.withFilter(this.activityFilter)
.sortable())
.withDefaultAction(actionBuilder
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)

View file

@ -0,0 +1,33 @@
/*
* 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.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
@Lazy
@Component
@GuiProfile
public class MonitoringRunningExam implements TemplateComposer {
public MonitoringRunningExam() {
// TODO Auto-generated constructor stub
}
@Override
public void compose(final PageContext pageContext) {
// TODO Auto-generated method stub
}
}

View file

@ -0,0 +1,137 @@
/*
* 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.eclipse.swt.widgets.Composite;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
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;
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.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetRunningExamPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
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 MonitoringRunningExamList implements TemplateComposer {
private static final LocTextKey PAGE_TITLE_KEY =
new LocTextKey("sebserver.monitoring.exam.list.title");
private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.info.pleaseSelect");
private final static LocTextKey COLUMN_TITLE_NAME_KEY =
new LocTextKey("sebserver.monitoring.exam.list.column.name");
private final static LocTextKey COLUMN_TITLE_TYPE_KEY =
new LocTextKey("sebserver.monitoring.exam.list.column.type");
private final static LocTextKey EMPTY_LIST_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.list.empty");
private final TableFilterAttribute nameFilter =
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
private final TableFilterAttribute typeFilter;
private final PageService pageService;
private final ResourceService resourceService;
private final int pageSize;
protected MonitoringRunningExamList(
final PageService pageService,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.resourceService = resourceService;
this.pageSize = pageSize;
this.typeFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
Exam.FILTER_ATTR_TYPE,
this.resourceService::examTypeResources);
}
@Override
public void compose(final PageContext pageContext) {
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final CurrentUser currentUser = this.resourceService.getCurrentUser();
final RestService restService = this.resourceService.getRestService();
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
// content page layout with title
final Composite content = widgetFactory.defaultPageLayout(
pageContext.getParent(),
PAGE_TITLE_KEY);
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
// table
final EntityTable<Exam> table =
this.pageService.entityTableBuilder(restService.getRestCall(GetRunningExamPage.class))
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
.withPaging(this.pageSize)
.withColumn(new ColumnDefinition<>(
QuizData.QUIZ_ATTR_NAME,
COLUMN_TITLE_NAME_KEY,
Exam::getName)
.withFilter(this.nameFilter)
.sortable())
.withColumn(new ColumnDefinition<>(
Domain.EXAM.ATTR_TYPE,
COLUMN_TITLE_TYPE_KEY,
this.resourceService::examTypeName)
.withFilter(this.typeFilter)
.sortable())
.withColumn(new ColumnDefinition<>(
QuizData.QUIZ_ATTR_START_TIME,
new LocTextKey(
"sebserver.monitoring.exam.list.column.startTime",
i18nSupport.getUsersTimeZoneTitleSuffix()),
Exam::getStartTime)
.sortable())
.withColumn(new ColumnDefinition<>(
QuizData.QUIZ_ATTR_END_TIME,
new LocTextKey(
"sebserver.monitoring.exam.list.column.endTime",
i18nSupport.getUsersTimeZoneTitleSuffix()),
Exam::getEndTime)
.sortable())
.withDefaultAction(actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM)
.create())
.compose(content);
actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM)
.withSelect(table::getSelection, PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER));
}
}

View file

@ -94,12 +94,12 @@ public class QuizDiscoveryList implements TemplateComposer {
protected QuizDiscoveryList(
final PageService pageService,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.widgetFactory = pageService.getWidgetFactory();
this.resourceService = resourceService;
this.pageSize = (pageSize != null) ? pageSize : 20;
this.pageSize = pageSize;
this.lmsFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,

View file

@ -22,6 +22,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.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
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;
@ -72,6 +73,7 @@ public class SebClientConfigList implements TemplateComposer {
DateTime.now(DateTimeZone.UTC)
.minusYears(1)
.toString(Constants.DEFAULT_DATE_TIME_FORMAT));
private final TableFilterAttribute activityFilter;
private final PageService pageService;
private final RestService restService;
@ -83,7 +85,7 @@ public class SebClientConfigList implements TemplateComposer {
final PageService pageService,
final RestService restService,
final CurrentUser currentUser,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.restService = restService;
@ -95,6 +97,11 @@ public class SebClientConfigList implements TemplateComposer {
CriteriaType.SINGLE_SELECTION,
Entity.FILTER_ATTR_INSTITUTION,
this.resourceService::institutionResource);
this.activityFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
UserInfo.FILTER_ATTR_ACTIVE,
this.resourceService::activityResources);
}
@Override
@ -139,6 +146,7 @@ public class SebClientConfigList implements TemplateComposer {
Domain.SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE,
ACTIVE_TEXT_KEY,
SebClientConfig::getActive)
.withFilter(this.activityFilter)
.sortable())
.withDefaultAction(pageActionBuilder
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST)

View file

@ -73,7 +73,7 @@ public class SebExamConfigList implements TemplateComposer {
final PageService pageService,
final RestService restService,
final CurrentUser currentUser,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.restService = restService;

View file

@ -73,6 +73,7 @@ public class UserAccountList implements TemplateComposer {
private final TableFilterAttribute mailFilter =
new TableFilterAttribute(CriteriaType.TEXT, UserInfo.FILTER_ATTR_EMAIL);
private final TableFilterAttribute languageFilter;
private final TableFilterAttribute activityFilter;
// dependencies
private final PageService pageService;
@ -82,11 +83,11 @@ public class UserAccountList implements TemplateComposer {
protected UserAccountList(
final PageService pageService,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.resourceService = resourceService;
this.pageSize = (pageSize != null) ? pageSize : 20;
this.pageSize = pageSize;
this.institutionFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
@ -97,6 +98,11 @@ public class UserAccountList implements TemplateComposer {
CriteriaType.SINGLE_SELECTION,
UserInfo.FILTER_ATTR_LANGUAGE,
this.resourceService::languageResources);
this.activityFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
UserInfo.FILTER_ATTR_ACTIVE,
this.resourceService::activityResources);
}
@Override
@ -154,12 +160,13 @@ public class UserAccountList implements TemplateComposer {
.withFilter(this.languageFilter)
.localized()
.sortable()
.widthProportion(2))
.widthProportion(1))
.withColumn(new ColumnDefinition<>(
Domain.USER.ATTR_ACTIVE,
ACTIVE_TEXT_KEY,
UserInfo::getActive)
.sortable()
.withFilter(this.activityFilter)
.widthProportion(1))
.withDefaultAction(actionBuilder
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST)

View file

@ -21,6 +21,7 @@ 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),
RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1),
VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 100),
;

View file

@ -388,7 +388,14 @@ public enum ActionDefinition {
PageStateDefinition.SEB_EXAM_CONFIG_EDIT,
ActionCategory.SEB_EXAM_CONFIG_LIST),
;
RUNNING_EXAM_VIEW_LIST(
new LocTextKey("sebserver.monitoring.action.list"),
PageStateDefinition.MONITORING_RUNNING_EXAM_LIST),
MONITOR_EXAM(
new LocTextKey("sebserver.monitoring.exam.action.list.view"),
ImageIcon.SHOW,
PageStateDefinition.MONITORING_RUNNING_EXAM,
ActionCategory.RUNNING_EXAM_LIST);
public final LocTextKey title;
public final ImageIcon icon;

View file

@ -202,7 +202,18 @@ public class ActivitiesPane implements TemplateComposer {
.newAction(ActionDefinition.SEB_EXAM_CONFIG_LIST)
.create());
}
}
// Monitoring exams
if (this.currentUser.get().hasRole(UserRole.EXAM_SUPPORTER)) {
final TreeItem clientConfig = this.widgetFactory.treeItemLocalized(
navigation,
ActivityDefinition.MONITORING_EXAMS.displayName);
injectActivitySelection(
clientConfig,
actionBuilder
.newAction(ActionDefinition.RUNNING_EXAM_VIEW_LIST)
.create());
}
// TODO other activities

View file

@ -19,7 +19,8 @@ public enum ActivityDefinition implements Activity {
EXAM(new LocTextKey("sebserver.exam.action.list")),
SEB_CONFIGURATION(new LocTextKey("sebserver.sebconfig.activity.name")),
SEB_CLIENT_CONFIG(new LocTextKey("sebserver.clientconfig.action.list")),
SEB_EXAM_CONFIG(new LocTextKey("sebserver.examconfig.action.list"));
SEB_EXAM_CONFIG(new LocTextKey("sebserver.examconfig.action.list")),
MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list"));
public final LocTextKey displayName;

View file

@ -16,12 +16,14 @@ import ch.ethz.seb.sebserver.gui.content.InstitutionForm;
import ch.ethz.seb.sebserver.gui.content.InstitutionList;
import ch.ethz.seb.sebserver.gui.content.LmsSetupForm;
import ch.ethz.seb.sebserver.gui.content.LmsSetupList;
import ch.ethz.seb.sebserver.gui.content.MonitoringRunningExam;
import ch.ethz.seb.sebserver.gui.content.MonitoringRunningExamList;
import ch.ethz.seb.sebserver.gui.content.QuizDiscoveryList;
import ch.ethz.seb.sebserver.gui.content.SebClientConfigForm;
import ch.ethz.seb.sebserver.gui.content.SebClientConfigList;
import ch.ethz.seb.sebserver.gui.content.SebExamConfigSettingsForm;
import ch.ethz.seb.sebserver.gui.content.SebExamConfigList;
import ch.ethz.seb.sebserver.gui.content.SebExamConfigPropForm;
import ch.ethz.seb.sebserver.gui.content.SebExamConfigSettingsForm;
import ch.ethz.seb.sebserver.gui.content.UserAccountChangePasswordForm;
import ch.ethz.seb.sebserver.gui.content.UserAccountForm;
import ch.ethz.seb.sebserver.gui.content.UserAccountList;
@ -62,6 +64,9 @@ public enum PageStateDefinition implements PageState {
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),
MONITORING_RUNNING_EXAM_LIST(Type.LIST_VIEW, MonitoringRunningExamList.class, ActivityDefinition.MONITORING_EXAMS),
MONITORING_RUNNING_EXAM(Type.FORM_VIEW, MonitoringRunningExam.class, ActivityDefinition.MONITORING_EXAMS)
;
public final Type type;

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.gui.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -25,6 +26,7 @@ import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
@ -93,6 +95,13 @@ public class ResourceService {
.collect(Collectors.toList());
}
public List<Tuple<String>> activityResources() {
final List<Tuple<String>> result = new ArrayList<>();
result.add(new Tuple<>("true", this.i18nSupport.getText("sebserver.overall.status.active")));
result.add(new Tuple<>("false", this.i18nSupport.getText("sebserver.overall.status.inactive")));
return result;
}
public List<Tuple<String>> lmsTypeResources() {
return Arrays.asList(LmsType.values())
.stream()
@ -119,20 +128,6 @@ public class ResourceService {
.collect(Collectors.toList());
}
// public Function<String, String> getExamConfigurationNameFunction() {
// final Map<String, String> idNameMap = getExamConfigurationSelection()
// .getOr(Collections.emptyList())
// .stream()
// .collect(Collectors.toMap(e -> e.modelId, e -> e.name));
//
// return id -> {
// if (!idNameMap.containsKey(id)) {
// return Constants.EMPTY_NOTE;
// }
// return idNameMap.get(id);
// };
// }
public List<Tuple<String>> userRoleResources() {
return UserRole.publicRolesForUser(this.currentUser.get())
.stream()
@ -229,6 +224,7 @@ public class ResourceService {
public List<Tuple<String>> examTypeResources() {
return Arrays.asList(ExamType.values())
.stream()
.filter(type -> type != ExamType.UNDEFINED)
.map(type -> new Tuple<>(
type.name(),
this.i18nSupport.getText(EXAM_TYPE_PREFIX + type.name())))
@ -313,4 +309,13 @@ public class ResourceService {
.call();
}
public String examTypeName(final Exam exam) {
if (exam.type == null) {
return Constants.EMPTY_NOTE;
}
return this.i18nSupport
.getText(ResourceService.EXAM_TYPE_PREFIX + exam.type.name());
}
}

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.session;
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.exam.Exam;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetRunningExamPage extends RestCall<Page<Exam>> {
public GetRunningExamPage() {
super(new TypeKey<>(
CallType.GET_PAGE,
EntityType.EXAM,
new TypeReference<Page<Exam>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT);
}
}

View file

@ -92,9 +92,10 @@ public class TableFilter<ROW extends Entity> {
.map(FilterComponent::reset)
.collect(Collectors.toList()));
final FilterComponent lastComp = this.components.get(this.components.size() - 1);
if (lastComp instanceof TableFilter.NullFilter) {
FilterComponent lastComp = this.components.get(this.components.size() - 1);
while (lastComp instanceof TableFilter.NullFilter) {
this.components.remove(lastComp);
lastComp = this.components.get(this.components.size() - 1);
}
addActions();

View file

@ -269,6 +269,9 @@ public class PaginationServiceImpl implements PaginationService {
configurationNodeTableMap.put(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
ConfigurationNodeRecordDynamicSqlSupport.description.name());
configurationNodeTableMap.put(
Domain.CONFIGURATION_NODE.ATTR_STATUS,
ConfigurationNodeRecordDynamicSqlSupport.status.name());
this.sortColumnMapping.put(
ConfigurationNodeRecordDynamicSqlSupport.configurationNodeRecord.name(),
configurationNodeTableMap);

View file

@ -10,10 +10,12 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session;
import java.io.OutputStream;
import java.util.Collection;
import java.util.function.Predicate;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
/** A Service to handle running exam sessions */
public interface ExamSessionService {
@ -33,13 +35,22 @@ public interface ExamSessionService {
* @return Result referencing the running Exam or an error if the Exam not exists or is not currently running */
Result<Exam> getRunningExam(Long examId);
/** Gets all all currently running Exams for a particular Institution.
/** Gets all currently running Exams for a particular Institution.
*
* @param institutionId the Institution identifier
* @return Result referencing the list of all currently running Exams of the institution or to an error if
* happened. */
Result<Collection<Exam>> getRunningExamsForInstitution(Long institutionId);
/** Gets all currently running Exams for a particular FilterMap.
*
* @param filterMap the FilterMap containing the filter attributes
* @param predicate additional filter predicate
* @return Result referencing the list of all currently running Exams or to an error if happened. */
Result<Collection<Exam>> getFilteredRunningExams(
FilterMap filterMap,
Predicate<Exam> predicate);
/** Streams the default SEB Exam Configuration to a ClientConnection with given connectionToken.
*
* @param connectionToken The connection token that identifiers the ClientConnection

View file

@ -13,6 +13,7 @@ import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.slf4j.Logger;
@ -30,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
@Lazy
@ -96,6 +98,18 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.collect(Collectors.toList()));
}
@Override
public Result<Collection<Exam>> getFilteredRunningExams(
final FilterMap filterMap,
final Predicate<Exam> predicate) {
return this.examDAO.allMatching(filterMap, predicate)
.map(col -> col.stream()
.map(exam -> this.examSessionCacheService.getRunningExam(exam.id))
.filter(exam -> exam != null)
.collect(Collectors.toList()));
}
@Override
public void streamDefaultExamConfig(
final String connectionToken,

View file

@ -126,6 +126,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
final Exception ex,
final WebRequest request) {
log.error("Unexpected internal error catched at the API endpoint: ", ex);
return APIMessage.ErrorMessage.UNEXPECTED
.createErrorResponse(ex.getMessage());
}

View file

@ -144,7 +144,8 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
pageSize,
sort,
getSQLTableOfEntity().name(),
() -> getAll(filterMap)).getOrThrow();
() -> getAll(filterMap))
.getOrThrow();
}
// ******************

View file

@ -120,39 +120,54 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
EntityType.EXAM,
institutionId);
final int pageNum = this.paginationService.getPageNumber(pageNumber);
final int pSize = this.paginationService.getPageSize(pageSize);
final List<Exam> exams = new ArrayList<>(
this.examDAO.allMatching(new FilterMap(allRequestParams)).getOrThrow());
this.examDAO
.allMatching(new FilterMap(allRequestParams))
.getOrThrow());
if (!StringUtils.isBlank(sort)) {
final String sortBy = PageSortOrder.decode(sort);
if (sortBy.equals(QuizData.QUIZ_ATTR_NAME)) {
Collections.sort(exams, (exam1, exam2) -> exam1.name.compareTo(exam2.name));
}
if (sortBy.equals(QuizData.FILTER_ATTR_START_TIME)) {
Collections.sort(exams, (exam1, exam2) -> exam1.startTime.compareTo(exam2.startTime));
}
}
if (PageSortOrder.DESCENDING == PageSortOrder.getSortOrder(sort)) {
Collections.reverse(exams);
}
final int start = (pageNum - 1) * pSize;
int end = start + pageSize;
if (exams.size() < end) {
end = exams.size();
}
return new Page<>(
exams.size() / pSize,
pageNum,
return buildSortedExamPage(
this.paginationService.getPageNumber(pageNumber),
this.paginationService.getPageSize(pageSize),
sort,
exams.subList(start, end));
exams);
}
}
public static Page<Exam> buildSortedExamPage(
final Integer pageNumber,
final Integer pageSize,
final String sort,
final List<Exam> exams) {
if (!StringUtils.isBlank(sort)) {
final String sortBy = PageSortOrder.decode(sort);
if (sortBy.equals(Exam.FILTER_ATTR_NAME)) {
Collections.sort(exams, (exam1, exam2) -> exam1.name.compareTo(exam2.name));
}
if (sortBy.equals(Exam.FILTER_ATTR_TYPE)) {
Collections.sort(exams, (exam1, exam2) -> exam1.type.compareTo(exam2.type));
}
if (sortBy.equals(QuizData.FILTER_ATTR_START_TIME)) {
Collections.sort(exams, (exam1, exam2) -> exam1.startTime.compareTo(exam2.startTime));
}
}
if (PageSortOrder.DESCENDING == PageSortOrder.getSortOrder(sort)) {
Collections.reverse(exams);
}
final int start = (pageNumber - 1) * pageSize;
int end = start + pageSize;
if (exams.size() < end) {
end = exams.size();
}
return new Page<>(
exams.size() / pageSize,
pageNumber,
sort,
exams.subList(start, end));
}
@Override
protected Exam createNew(final POSTMapper postParams) {

View file

@ -1,17 +0,0 @@
/*
* 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;
public class ExamMonitiroingEndpoint {
public ExamMonitiroingEndpoint() {
// TODO Auto-generated constructor stub
}
}

View file

@ -0,0 +1,135 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
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.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
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.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
@WebServiceProfile
@RestController
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_MONITORING_ENDPOINT)
public class ExamMonitoringController {
private final ExamSessionService examSessionService;
private final AuthorizationService authorization;
private final PaginationService paginationService;
public ExamMonitoringController(
final ExamSessionService examSessionService,
final AuthorizationService authorization,
final PaginationService paginationService) {
this.examSessionService = examSessionService;
this.authorization = authorization;
this.paginationService = paginationService;
}
/** This is called by Spring to initialize the WebDataBinder and is used here to
* initialize the default value binding for the institutionId request-parameter
* that has the current users insitutionId as default.
*
* See also UserService.addUsersInstitutionDefaultPropertySupport */
@InitBinder
public void initBinder(final WebDataBinder binder) throws Exception {
this.authorization
.getUserService()
.addUsersInstitutionDefaultPropertySupport(binder);
}
// ******************
// * GET (getAll)
// ******************
/** Get a page of all currently running exams
*
* GET /{api}/{entity-type-endpoint-name}
*
* GET /admin-api/v1/monitoring
* GET /admin-api/v1/monitoring?page_number=2&page_size=10&sort=-name
* GET /admin-api/v1/monitoring?name=seb&active=true
*
* @param institutionId The institution identifier of the request.
* Default is the institution identifier of the institution of the current user
* @param pageNumber the number of the page that is requested
* @param pageSize the size of the page that is requested
* @param sort the sort parameter to sort the list of entities before paging
* the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for
* descending sort order
* @param allRequestParams a MultiValueMap of all request parameter that is used for filtering
* @return Page of domain-model-entities of specified type */
@RequestMapping(
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Page<Exam> getPage(
@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) {
// check if user has EXAM_SUPPORTER privilege.
final SEBServerUser currentUser = this.authorization
.getUserService()
.getCurrentUser();
if (!currentUser.getUserRoles().contains(UserRole.EXAM_SUPPORTER)) {
throw new PermissionDeniedException(
EntityType.EXAM,
PrivilegeType.READ,
currentUser.getUserInfo().uuid);
}
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, EntityType.EXAM)) {
filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
}
final List<Exam> exams = new ArrayList<>(this.examSessionService
.getFilteredRunningExams(filterMap, exam -> true)
.getOrThrow());
return ExamAdministrationController.buildSortedExamPage(
this.paginationService.getPageNumber(pageNumber),
this.paginationService.getPageSize(pageSize),
sort,
exams);
}
}

View file

@ -852,11 +852,29 @@ sebserver.examconfig.props.label.enableF10=Enable F10
sebserver.examconfig.props.label.enableF11=Enable F11
sebserver.examconfig.props.label.enableF12=Enable F12
sebserver.examconfig.props.validation.password.confirm=Please enter correct confirm password
sebserver.examconfig.props.validation.unexpected=Unexpected error happened. Value was not set correctly
sebserver.examconfig.props.validation.IntegerTypeValidator=Invalid number
sebserver.examconfig.props.validation.DecimalTypeValidator=Invalid decimal number
sebserver.examconfig.props.validation.ExitKeySequenceValidator=Key is already in sequence
sebserver.examconfig.props.validation.WindowsSizeValidator=Invalid number
sebserver.examconfig.props.validation.WindowsSizeValidator=Invalid number
################################
# Monitoring
################################
sebserver.monitoring.action.list=Monitoring
sebserver.monitoring.exam.list.title=Running Exams
sebserver.monitoring.exam.list.actions=Selected Exam
sebserver.monitoring.exam.action.list.view=Monitoring
sebserver.monitoring.exam.info.pleaseSelect=Please select an exam first
sebserver.monitoring.exam.list.empty=There are currently no running exams
sebserver.monitoring.exam.list.column.name=Name
sebserver.monitoring.exam.list.column.type=Type
sebserver.monitoring.exam.list.column.startTime=Start Time
sebserver.monitoring.exam.list.column.endTime=End Time

View file

@ -281,11 +281,11 @@ Combo-Button {
background-color: #ffffff;
background-image: gradient(linear, left top, left bottom, from(#ffffff), to(#ffffff));
border: none;
width: 30px;
width: 20px;
}
Combo-Field {
padding: 3px 10px 1px 10px;
padding: 3px 0px 1px 10px;
}
Combo.error {