diff --git a/docs/exam.rst b/docs/exam.rst index b4764a4f..88108d8e 100644 --- a/docs/exam.rst +++ b/docs/exam.rst @@ -151,6 +151,29 @@ your institution use the type information of the exam to set them into context. - When you have selected a exam configuration the dialog shows you some additional information about the exam configuration. - If you want or need to put an password protected encryption to the exam configuration for this exam you can do so by give the password for the encryption also within the attachment dialog. Be aware that every SEB client that will receive an encrypted exam configuration from the SEB Server will prompt the user to give the correct password. In most cases an encryption of the exam configuration is not needed, because a secure HTTPS connection form SEB client to SEB Server is already in place. +**Archive an exam** + +Since SEB Server version 1.4 it is possible to archive an exam that has been finished. An archived exam and all its data is still available +on the SEB Server but read only and the exam is not been updated from the LMS data anymore and it is not possible to run this exam again. + +This is a good use-case to organize your exams since archived exam are not shown in the Exam list with the default filter anymore. They are +only shown if the status filter of the exam list is explicitly set to Archived status. An they are shown within the new "Finished Exam" +section in the monitoring view. + +.. image:: images/exam/archiveExamsFilter.png + :align: center + :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam/archiveExamsFilter.png + +This is also a good use-case if you want to remove an LMS and LMS Setup but still want to be able to access the exams data on the SEB Server. +In this case you can archive all exams from that LMS Setup before deactivating or deleting the respective LMS Setup. + +To archive a finished exam you just have to use the "Archive Exam" action on the right action pane of the exam view: + +.. image:: images/exam/archiveExam1.png + :align: center + :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam/archiveExam1.png + + **Delete an exam** If you have "Exam Administrator" privileges you are able to entirely delete an existing exam and its dependencies. diff --git a/docs/exam_template.rst b/docs/exam_template.rst index 1e0d6770..42ead0cf 100644 --- a/docs/exam_template.rst +++ b/docs/exam_template.rst @@ -55,6 +55,14 @@ And you are able to add/edit/remove monitoring indicators for the exam template .. image:: images/exam_template/indicator.png :align: center :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam_template/indicator.png + +There are also proctoring settings available since SEB Server version 1.4 for the exam template. They just have the same settings and +look like the ones on the Exam and will get copied for an exam imported with the respective template that defines the proctoring settings. + +.. image:: images/exam_template/proctoringSettings.png + :align: center + :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam_template/proctoringSettings.png + Import Exam with Template ------------------------- diff --git a/docs/images/exam/archiveExam1.png b/docs/images/exam/archiveExam1.png new file mode 100644 index 00000000..e2f6d699 Binary files /dev/null and b/docs/images/exam/archiveExam1.png differ diff --git a/docs/images/exam/archiveExamsFilter.png b/docs/images/exam/archiveExamsFilter.png new file mode 100644 index 00000000..8aeaaba6 Binary files /dev/null and b/docs/images/exam/archiveExamsFilter.png differ diff --git a/docs/images/exam_config/bulkActionSelection1.png b/docs/images/exam_config/bulkActionSelection1.png new file mode 100644 index 00000000..d0c2f2a9 Binary files /dev/null and b/docs/images/exam_config/bulkActionSelection1.png differ diff --git a/docs/images/exam_config/bulkActionSelection2.png b/docs/images/exam_config/bulkActionSelection2.png new file mode 100644 index 00000000..2f1913e0 Binary files /dev/null and b/docs/images/exam_config/bulkActionSelection2.png differ diff --git a/docs/images/exam_config/bulkStateChange1.png b/docs/images/exam_config/bulkStateChange1.png new file mode 100644 index 00000000..6d84fd4e Binary files /dev/null and b/docs/images/exam_config/bulkStateChange1.png differ diff --git a/docs/images/exam_config/bulkStateChange2.png b/docs/images/exam_config/bulkStateChange2.png new file mode 100644 index 00000000..7e4de905 Binary files /dev/null and b/docs/images/exam_config/bulkStateChange2.png differ diff --git a/docs/images/exam_config/bulkStateChange3.png b/docs/images/exam_config/bulkStateChange3.png new file mode 100644 index 00000000..40ffcbcf Binary files /dev/null and b/docs/images/exam_config/bulkStateChange3.png differ diff --git a/docs/images/exam_template/proctoringSettings.png b/docs/images/exam_template/proctoringSettings.png new file mode 100644 index 00000000..cd170bb5 Binary files /dev/null and b/docs/images/exam_template/proctoringSettings.png differ diff --git a/docs/images/monitoring/finishedClientConnection.png b/docs/images/monitoring/finishedClientConnection.png new file mode 100644 index 00000000..52c4226b Binary files /dev/null and b/docs/images/monitoring/finishedClientConnection.png differ diff --git a/docs/images/monitoring/finishedExam.png b/docs/images/monitoring/finishedExam.png new file mode 100644 index 00000000..aa12674d Binary files /dev/null and b/docs/images/monitoring/finishedExam.png differ diff --git a/docs/images/monitoring/finishedExams.png b/docs/images/monitoring/finishedExams.png new file mode 100644 index 00000000..535a3125 Binary files /dev/null and b/docs/images/monitoring/finishedExams.png differ diff --git a/docs/monitoring.rst b/docs/monitoring.rst index ae24dc71..dda9f633 100644 --- a/docs/monitoring.rst +++ b/docs/monitoring.rst @@ -181,7 +181,39 @@ A Student as well as a proctor is then able to use all the features of the meeti - In Zoom it is not possible to fully control a participant microphone. Therefore it may happen that participant can hear each other even if no proctor is in the meeting. - Within Jitsi Meet service when a proctor leaves the room it currently happens that a random participant became host/moderator since it is not possible in Jitsi Meet to have a meeting without host. We try to mitigate the problem with the `moderator plugin `_ or `Jitsi Meet SaS `_ - In both services while broadcasting, it is not guaranteed that a student always see the proctor. Usually the meeting service shows or pins the participant that is currently speaking automatically. - + + +Finished Exams +-------------- + +Since SEB Server version 1.4 there is a new section "Finished Exams" within the monitoring section to view finished and archived exams +like you do within the monitoring. You see all the SEB connections that has been connected to the exam when running and are able to view +particular SEB client connection details by either double-click on a SEB client connection entry in the list or by selection and using the View action +on the right action pane. + +In the "Finished Exams" list you can see all finished or archived exams and filter the list by Name, State and Type. + +.. image:: images/monitoring/finishedExams.png + :align: center + :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/monitoring/finishedExams.png + +To see a particular finished or archived exam you can just double-click in the list entry or use the View action on the right action pane. +In the exam view you see all SEB connections that has been connected to the exam during the exam run just like in the usual monitoring view +but with no update since the SEB connections are not active and the data is not changing anymore. You are able to filter the list by +User or Session Info, Connection Info or Status and are also be able to sort the list even for indicator columns. + +.. image:: images/monitoring/finishedExam.png + :align: center + :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/monitoring/finishedExam.png + +As in the usual monitoring view, you can show SEB client connection details by double-clicking on a list entry or by selecting a list entry +and use the View action on the right action pane. +In the detail view you see the same information for a particular SEB client connection as within the usual monitoring view. You can view +the SEB client logs of a SEB client connection here and analyze it after the exam was running. + +.. image:: images/monitoring/finishedClientConnection.png + :align: center + :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/monitoring/finishedClientConnection.png All SEB Client Logs diff --git a/docs/overview.rst b/docs/overview.rst index 24adcb9b..452c8c76 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -166,7 +166,7 @@ Since SEB Server version 1.4, multi-selection for some lists with bulk-actions i just click on the row as usual. If you then click on another (still not selected) row, this row get selected too. You can do this even over several pages. To deselect a selected row just click it again then it will be removed from the selection. -.. image:: images/overview/list.png +.. image:: images/overview/list_multiselect.png :align: center :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/overview/list_multiselect.png @@ -204,4 +204,20 @@ After correcting the missing or wrong input and saveing the form again, the SEB .. note:: If you navigate away from a form in edit mode, the GUI will inform you about possible data loss on this action and will prompt you to - proceed or abort the action. \ No newline at end of file + proceed or abort the action. + +**Actions** + +Actions are usually placed on the right action pane of the application and belongs to the actual site or view. There are generally three types of actions: + +- Form Actions that directly belongs to the actual view or object and either save, manipulate or create a new object. +- List Action - Single Selection are actions on a list page that effects the selected list entry. +- List Action - Multi Selection are actions that refer to the current multi selection on a list and apply for every selected item. + +.. note:: + List action are disabled when nothing is selected from the list and get enabled as soon as one or more list items are selected. + Actions that are considdered single selection actions, and are used with a multi selection on the list will only affect the first selected item in the list. + +.. image:: images/overview/list_multiselect_actions.png + :align: center + :target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/overview/list_multiselect_actions.png \ No newline at end of file diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 69a70493..bc988f3e 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -6,7 +6,29 @@ There shall be at least a problem description, an optional explanation if needed Please also have a look at `Open Issues `_ and/or `Ongoing Discussions `_ on the Git-Hub page. +-------------------------------- +- **Version** : 1.3.x + +- **Domain** : Exam Monitoring + +- **Problem** : SEB connections get lost and ping-times go up for already connected SEB clients + +- **Explanation** : This issue is due to a access token used by SEB client to authenticate on SEB Server that lasts not longer the one hour since SEB Server version 1.3 and since SEB client has no new access token request implements yet. + +- **Solution** : A workaround for SEB Server version 1.3.x is to make the access token expiry-date last long enough to minimize the possibility that the access token became invalid during a exam. We recommend to set it to 12 hours = 43200 seconds. Therefore please set the following SEB Server setup properties in the respective application-prod.properties configuration file of your SEB Server setup: + + sebserver.webservice.api.admin.accessTokenValiditySeconds=43200 + sebserver.webservice.api.exam.accessTokenValiditySeconds=43200 + +In SEB Server version 1.4 this is already set as default again and we are currently working on new SEB client versions that also +handle SEB Server communication token expiry by automatically requesting a new access token (with new lifetime) from SEB Server +when an old access token is not valid any longer. + +-------------------------------- + + +**Template** -------------------------------- - **Version** : 1.0.0 diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java index a30016ef..e36488bf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java @@ -30,7 +30,6 @@ import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; -import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @@ -89,7 +88,7 @@ public class ExamList implements TemplateComposer { private final TableFilterAttribute institutionFilter; private final TableFilterAttribute lmsFilter; private final TableFilterAttribute nameFilter = - new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME); + new TableFilterAttribute(CriteriaType.TEXT, Domain.EXAM.ATTR_QUIZ_NAME); private final TableFilterAttribute stateFilter; private final TableFilterAttribute typeFilter; @@ -177,21 +176,21 @@ public class ExamList implements TemplateComposer { .sortable()) .withColumn(new ColumnDefinition<>( - QuizData.QUIZ_ATTR_NAME, + Domain.EXAM.ATTR_QUIZ_NAME, COLUMN_TITLE_NAME_KEY, Exam::getName) .withFilter(this.nameFilter) .sortable()) .withColumn(new ColumnDefinition<>( - QuizData.QUIZ_ATTR_START_TIME, + Domain.EXAM.ATTR_QUIZ_START_TIME, new LocTextKey( EXAM_LIST_COLUMN_START_TIME, i18nSupport.getUsersTimeZoneTitleSuffix()), Exam::getStartTime) .withFilter(new TableFilterAttribute( CriteriaType.DATE, - QuizData.FILTER_ATTR_START_TIME, + Domain.EXAM.ATTR_QUIZ_START_TIME, Utils.toDateTimeUTC(Utils.getMillisecondsNow()) .minusYears(1) .toString())) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java index fbf24ec2..eb4d8e71 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java @@ -119,9 +119,14 @@ public interface PaginationService { final List sorted = pageFunction.apply(all); - final int _pageNumber = getPageNumber(pageNumber); + int _pageNumber = getPageNumber(pageNumber); final int _pageSize = getPageSize(pageSize); - final int start = (_pageNumber - 1) * _pageSize; + + int start = (_pageNumber - 1) * _pageSize; + if (start >= sorted.size()) { + start = 0; + _pageNumber = 1; + } int end = start + _pageSize; if (sorted.size() < end) { end = sorted.size(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java index dc99ebdf..1503ec37 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java @@ -282,10 +282,11 @@ public class PaginationServiceImpl implements PaginationService { final Map examTableMap = new HashMap<>(); examTableMap.put(Entity.FILTER_ATTR_INSTITUTION, institutionNameRef); examTableMap.put(Domain.EXAM.ATTR_LMS_SETUP_ID, lmsSetupNameRef); - - // NOTE: This seems not to work and I was not able to figure out why. - // Now the type sorting is done within secondary sort for exams. - //examTableMap.put(Domain.EXAM.ATTR_TYPE, "'" + ExamRecordDynamicSqlSupport.type.name() + "'"); + examTableMap.put(Domain.EXAM.ATTR_QUIZ_NAME, ExamRecordDynamicSqlSupport.quizName.name()); + examTableMap.put(Domain.EXAM.ATTR_QUIZ_START_TIME, ExamRecordDynamicSqlSupport.quizStartTime.name()); + examTableMap.put(Domain.EXAM.ATTR_QUIZ_END_TIME, ExamRecordDynamicSqlSupport.quizEndTime.name()); + examTableMap.put(Domain.EXAM.ATTR_STATUS, ExamRecordDynamicSqlSupport.status.name()); + examTableMap.put(Domain.EXAM.ATTR_TYPE, ExamRecordDynamicSqlSupport.type.name()); this.sortColumnMapping.put(ExamRecordDynamicSqlSupport.examRecord.name(), examTableMap); this.defaultSortColumn.put(ExamRecordDynamicSqlSupport.examRecord.name(), Domain.EXAM.ATTR_ID); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java index 8000a979..adaf45e2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java @@ -19,6 +19,7 @@ import org.springframework.util.MultiValueMap; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; +import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; @@ -108,7 +109,7 @@ public class FilterMap extends POSTMapper { } public DateTime getExamFromTime() { - return Utils.toDateTime(getString(QuizData.FILTER_ATTR_START_TIME)); + return Utils.toDateTime(getString(Domain.EXAM.ATTR_QUIZ_START_TIME)); } public DateTime getSEBClientConfigFromTime() { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index 1049592e..dd17c5ff 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -25,7 +25,6 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; import org.mybatis.dynamic.sql.update.UpdateDSL; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; @@ -121,42 +120,16 @@ public class ExamDAOImpl implements ExamDAO { return Result.tryCatch(() -> { - final Predicate examDataFilter = createPredicate(filterMap); return this.examRecordDAO .allMatching(filterMap, null) .flatMap(this::toDomainModel) .getOrThrow() .stream() - .filter(examDataFilter.and(predicate)) + .filter(predicate) .collect(Collectors.toList()); }); } - private Predicate createPredicate(final FilterMap filterMap) { - final String name = filterMap.getQuizName(); - final DateTime from = filterMap.getExamFromTime(); - final Predicate quizDataFilter = exam -> { - if (StringUtils.isNotBlank(name)) { - if (!exam.name.contains(name)) { - return false; - } - } - - if (from != null && exam.startTime != null) { - // always show exams that has not ended yet - if (exam.endTime == null || exam.endTime.isAfter(from)) { - return true; - } - if (exam.startTime.isBefore(from)) { - return false; - } - } - - return true; - }; - return quizDataFilter; - } - @Override public Result updateState(final Long examId, final ExamStatus status, final String updateId) { return this.examRecordDAO @@ -268,13 +241,12 @@ public class ExamDAOImpl implements ExamDAO { .stream().map(s -> s.name()) .collect(Collectors.toList()) : null; - final Predicate examDataFilter = createPredicate(filterMap); return this.examRecordDAO .allMatching(filterMap, stateNames) .flatMap(this::toDomainModel) .getOrThrow() .stream() - .filter(examDataFilter.and(predicate)) + .filter(predicate) .collect(Collectors.toList()); }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamRecordDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamRecordDAO.java index a7aed3cb..3b791e0a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamRecordDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamRecordDAO.java @@ -172,8 +172,6 @@ public class ExamRecordDAO { ExamRecordDynamicSqlSupport.active, isEqualToWhenPresent(filterMap.getActiveAsInt())); - // - whereClause = whereClause .and( ExamRecordDynamicSqlSupport.institutionId, @@ -204,8 +202,15 @@ public class ExamRecordDAO { isNotEqualTo(ExamStatus.ARCHIVED.name())); } - final List records = whereClause + if (filterMap.getExamFromTime() != null) { + whereClause = whereClause + .and( + ExamRecordDynamicSqlSupport.quizEndTime, + isGreaterThanOrEqualToWhenPresent(filterMap.getExamFromTime()), + or(ExamRecordDynamicSqlSupport.quizEndTime, isNull())); + } + final List records = whereClause .and( ExamRecordDynamicSqlSupport.quizName, isLikeWhenPresent(filterMap.getSQLWildcard(EXAM.ATTR_QUIZ_NAME))) @@ -465,20 +470,20 @@ public class ExamRecordDAO { .execute(); // check those in not running state (and not archived) and are within the time-frame or on wrong side of the time-frame - // if finished but up-coming + // if finished but up-coming or running final SqlCriterion finished = or( ExamRecordDynamicSqlSupport.status, isEqualTo(ExamStatus.FINISHED.name()), and( - ExamRecordDynamicSqlSupport.quizStartTime, + ExamRecordDynamicSqlSupport.quizEndTime, SqlBuilder.isGreaterThanOrEqualToWhenPresent(now.plus(leadTime)))); - // if up-coming but finished + // if up-coming but running or finished final SqlCriterion upcoming = or( ExamRecordDynamicSqlSupport.status, isEqualTo(ExamStatus.UP_COMING.name()), and( - ExamRecordDynamicSqlSupport.quizEndTime, + ExamRecordDynamicSqlSupport.quizStartTime, SqlBuilder.isLessThanWhenPresent(now.minus(followupTime))), finished); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index e87b585b..fb292d8e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -17,13 +17,12 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.http.MediaType; -import org.springframework.util.MultiValueMap; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -39,14 +38,13 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.EntityType; 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.EntityKey; -import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.PageSortOrder; import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; 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.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; @@ -120,50 +118,56 @@ public class ExamAdministrationController extends EntityController { return ExamRecordDynamicSqlSupport.examRecord; } - @RequestMapping( - method = RequestMethod.GET, - consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - @Override - public Page 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 allRequestParams, - final HttpServletRequest request) { - - checkReadPrivilege(institutionId); - this.authorization.check( - PrivilegeType.READ, - EntityType.EXAM, - institutionId); - - if (StringUtils.isBlank(sort) || - (this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort))) { - - return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request); - - } else { - - final Collection exams = this.examDAO - .allMatching(new FilterMap( - allRequestParams, - request.getQueryString()), - this::hasReadAccess) - .getOrThrow(); - - return this.paginationService.buildPageFromList( - pageNumber, - pageSize, - sort, - exams, - pageSort(sort)); - } - } +// @RequestMapping( +// method = RequestMethod.GET, +// consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, +// produces = MediaType.APPLICATION_JSON_VALUE) +// @Override +// public Page 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 allRequestParams, +// final HttpServletRequest request) { +// +// checkReadPrivilege(institutionId); +// this.authorization.check( +// PrivilegeType.READ, +// EntityType.EXAM, +// institutionId); +// +// if (StringUtils.isBlank(sort) || +// (this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort))) { +// +// System.out.println("*********************** sort, filter on DB"); +// +// return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request); +// +// } else { +// +// System.out.println("*********************** sort, filter on List"); +// +// return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request); +// +//// final Collection exams = this.examDAO +//// .allMatching(new FilterMap( +//// allRequestParams, +//// request.getQueryString()), +//// this::hasReadAccess) +//// .getOrThrow(); +//// +//// return this.paginationService.buildPageFromList( +//// pageNumber, +//// pageSize, +//// sort, +//// exams, +//// pageSort(sort)); +// } +// } @RequestMapping( path = API.MODEL_ID_VAR_PATH_SEGMENT @@ -586,13 +590,13 @@ public class ExamAdministrationController extends EntityController { } if (sortBy.equals(Exam.FILTER_ATTR_NAME) || sortBy.equals(QuizData.QUIZ_ATTR_NAME)) { - list.sort(Comparator.comparing(exam -> exam.name)); + list.sort(Comparator.comparing(exam -> (exam.name != null) ? exam.name : StringUtils.EMPTY)); } if (sortBy.equals(Exam.FILTER_ATTR_TYPE)) { - list.sort(Comparator.comparing(exam -> exam.type)); + list.sort(Comparator.comparing(exam -> (exam.type != null) ? exam.type : ExamType.UNDEFINED)); } if (sortBy.equals(QuizData.FILTER_ATTR_START_TIME) || sortBy.equals(QuizData.QUIZ_ATTR_START_TIME)) { - list.sort(Comparator.comparing(exam -> exam.startTime)); + list.sort(Comparator.comparing(exam -> (exam.startTime != null) ? exam.startTime : new DateTime(0))); } if (PageSortOrder.DESCENDING == PageSortOrder.getSortOrder(sort)) {