SEBSERV-341 and SEBSERV-343

This commit is contained in:
anhefti 2022-07-20 11:53:29 +02:00
parent 921595c3b0
commit 224507d849
6 changed files with 76 additions and 97 deletions

View file

@ -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;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; 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.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.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@ -89,7 +88,7 @@ public class ExamList implements TemplateComposer {
private final TableFilterAttribute institutionFilter; private final TableFilterAttribute institutionFilter;
private final TableFilterAttribute lmsFilter; private final TableFilterAttribute lmsFilter;
private final TableFilterAttribute nameFilter = 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 stateFilter;
private final TableFilterAttribute typeFilter; private final TableFilterAttribute typeFilter;
@ -177,21 +176,21 @@ public class ExamList implements TemplateComposer {
.sortable()) .sortable())
.withColumn(new ColumnDefinition<>( .withColumn(new ColumnDefinition<>(
QuizData.QUIZ_ATTR_NAME, Domain.EXAM.ATTR_QUIZ_NAME,
COLUMN_TITLE_NAME_KEY, COLUMN_TITLE_NAME_KEY,
Exam::getName) Exam::getName)
.withFilter(this.nameFilter) .withFilter(this.nameFilter)
.sortable()) .sortable())
.withColumn(new ColumnDefinition<>( .withColumn(new ColumnDefinition<>(
QuizData.QUIZ_ATTR_START_TIME, Domain.EXAM.ATTR_QUIZ_START_TIME,
new LocTextKey( new LocTextKey(
EXAM_LIST_COLUMN_START_TIME, EXAM_LIST_COLUMN_START_TIME,
i18nSupport.getUsersTimeZoneTitleSuffix()), i18nSupport.getUsersTimeZoneTitleSuffix()),
Exam::getStartTime) Exam::getStartTime)
.withFilter(new TableFilterAttribute( .withFilter(new TableFilterAttribute(
CriteriaType.DATE, CriteriaType.DATE,
QuizData.FILTER_ATTR_START_TIME, Domain.EXAM.ATTR_QUIZ_START_TIME,
Utils.toDateTimeUTC(Utils.getMillisecondsNow()) Utils.toDateTimeUTC(Utils.getMillisecondsNow())
.minusYears(1) .minusYears(1)
.toString())) .toString()))

View file

@ -282,10 +282,11 @@ public class PaginationServiceImpl implements PaginationService {
final Map<String, String> examTableMap = new HashMap<>(); final Map<String, String> examTableMap = new HashMap<>();
examTableMap.put(Entity.FILTER_ATTR_INSTITUTION, institutionNameRef); examTableMap.put(Entity.FILTER_ATTR_INSTITUTION, institutionNameRef);
examTableMap.put(Domain.EXAM.ATTR_LMS_SETUP_ID, lmsSetupNameRef); examTableMap.put(Domain.EXAM.ATTR_LMS_SETUP_ID, lmsSetupNameRef);
examTableMap.put(Domain.EXAM.ATTR_QUIZ_NAME, ExamRecordDynamicSqlSupport.quizName.name());
// NOTE: This seems not to work and I was not able to figure out why. examTableMap.put(Domain.EXAM.ATTR_QUIZ_START_TIME, ExamRecordDynamicSqlSupport.quizStartTime.name());
// Now the type sorting is done within secondary sort for exams. examTableMap.put(Domain.EXAM.ATTR_QUIZ_END_TIME, ExamRecordDynamicSqlSupport.quizEndTime.name());
//examTableMap.put(Domain.EXAM.ATTR_TYPE, "'" + ExamRecordDynamicSqlSupport.type.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.sortColumnMapping.put(ExamRecordDynamicSqlSupport.examRecord.name(), examTableMap);
this.defaultSortColumn.put(ExamRecordDynamicSqlSupport.examRecord.name(), Domain.EXAM.ATTR_ID); this.defaultSortColumn.put(ExamRecordDynamicSqlSupport.examRecord.name(), Domain.EXAM.ATTR_ID);

View file

@ -19,6 +19,7 @@ import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; 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.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
@ -108,7 +109,7 @@ public class FilterMap extends POSTMapper {
} }
public DateTime getExamFromTime() { 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() { public DateTime getSEBClientConfigFromTime() {

View file

@ -25,7 +25,6 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.mybatis.dynamic.sql.update.UpdateDSL; import org.mybatis.dynamic.sql.update.UpdateDSL;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -121,42 +120,16 @@ public class ExamDAOImpl implements ExamDAO {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final Predicate<Exam> examDataFilter = createPredicate(filterMap);
return this.examRecordDAO return this.examRecordDAO
.allMatching(filterMap, null) .allMatching(filterMap, null)
.flatMap(this::toDomainModel) .flatMap(this::toDomainModel)
.getOrThrow() .getOrThrow()
.stream() .stream()
.filter(examDataFilter.and(predicate)) .filter(predicate)
.collect(Collectors.toList()); .collect(Collectors.toList());
}); });
} }
private Predicate<Exam> createPredicate(final FilterMap filterMap) {
final String name = filterMap.getQuizName();
final DateTime from = filterMap.getExamFromTime();
final Predicate<Exam> 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 @Override
public Result<Exam> updateState(final Long examId, final ExamStatus status, final String updateId) { public Result<Exam> updateState(final Long examId, final ExamStatus status, final String updateId) {
return this.examRecordDAO return this.examRecordDAO
@ -268,13 +241,12 @@ public class ExamDAOImpl implements ExamDAO {
.stream().map(s -> s.name()) .stream().map(s -> s.name())
.collect(Collectors.toList()) .collect(Collectors.toList())
: null; : null;
final Predicate<Exam> examDataFilter = createPredicate(filterMap);
return this.examRecordDAO return this.examRecordDAO
.allMatching(filterMap, stateNames) .allMatching(filterMap, stateNames)
.flatMap(this::toDomainModel) .flatMap(this::toDomainModel)
.getOrThrow() .getOrThrow()
.stream() .stream()
.filter(examDataFilter.and(predicate)) .filter(predicate)
.collect(Collectors.toList()); .collect(Collectors.toList());
}); });
} }

View file

@ -172,8 +172,6 @@ public class ExamRecordDAO {
ExamRecordDynamicSqlSupport.active, ExamRecordDynamicSqlSupport.active,
isEqualToWhenPresent(filterMap.getActiveAsInt())); isEqualToWhenPresent(filterMap.getActiveAsInt()));
//
whereClause = whereClause whereClause = whereClause
.and( .and(
ExamRecordDynamicSqlSupport.institutionId, ExamRecordDynamicSqlSupport.institutionId,
@ -209,6 +207,10 @@ public class ExamRecordDAO {
.and( .and(
ExamRecordDynamicSqlSupport.quizName, ExamRecordDynamicSqlSupport.quizName,
isLikeWhenPresent(filterMap.getSQLWildcard(EXAM.ATTR_QUIZ_NAME))) isLikeWhenPresent(filterMap.getSQLWildcard(EXAM.ATTR_QUIZ_NAME)))
.and(
ExamRecordDynamicSqlSupport.quizEndTime,
isGreaterThanOrEqualToWhenPresent(filterMap.getExamFromTime()),
or(ExamRecordDynamicSqlSupport.quizEndTime, isNull()))
.build() .build()
.execute(); .execute();
@ -465,20 +467,20 @@ public class ExamRecordDAO {
.execute(); .execute();
// check those in not running state (and not archived) and are within the time-frame or on wrong side of the time-frame // 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<String> finished = or( final SqlCriterion<String> finished = or(
ExamRecordDynamicSqlSupport.status, ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.FINISHED.name()), isEqualTo(ExamStatus.FINISHED.name()),
and( and(
ExamRecordDynamicSqlSupport.quizStartTime, ExamRecordDynamicSqlSupport.quizEndTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(now.plus(leadTime)))); SqlBuilder.isGreaterThanOrEqualToWhenPresent(now.plus(leadTime))));
// if up-coming but finished // if up-coming but running or finished
final SqlCriterion<String> upcoming = or( final SqlCriterion<String> upcoming = or(
ExamRecordDynamicSqlSupport.status, ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.UP_COMING.name()), isEqualTo(ExamStatus.UP_COMING.name()),
and( and(
ExamRecordDynamicSqlSupport.quizEndTime, ExamRecordDynamicSqlSupport.quizStartTime,
SqlBuilder.isLessThanWhenPresent(now.minus(followupTime))), SqlBuilder.isLessThanWhenPresent(now.minus(followupTime))),
finished); finished);

View file

@ -17,13 +17,12 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.FieldError; import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; 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.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; 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;
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM; import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; 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.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; 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;
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.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
@ -120,50 +118,56 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
return ExamRecordDynamicSqlSupport.examRecord; return ExamRecordDynamicSqlSupport.examRecord;
} }
@RequestMapping( // @RequestMapping(
method = RequestMethod.GET, // method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, // consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) // produces = MediaType.APPLICATION_JSON_VALUE)
@Override // @Override
public Page<Exam> getPage( // public Page<Exam> getPage(
@RequestParam( // @RequestParam(
name = API.PARAM_INSTITUTION_ID, // name = API.PARAM_INSTITUTION_ID,
required = true, // required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, // 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_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, // @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort, // @RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams, // @RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) { // final HttpServletRequest request) {
//
checkReadPrivilege(institutionId); // checkReadPrivilege(institutionId);
this.authorization.check( // this.authorization.check(
PrivilegeType.READ, // PrivilegeType.READ,
EntityType.EXAM, // EntityType.EXAM,
institutionId); // institutionId);
//
if (StringUtils.isBlank(sort) || // if (StringUtils.isBlank(sort) ||
(this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort))) { // (this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort))) {
//
return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request); // System.out.println("*********************** sort, filter on DB");
//
} else { // return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request);
//
final Collection<Exam> exams = this.examDAO // } else {
.allMatching(new FilterMap( //
allRequestParams, // System.out.println("*********************** sort, filter on List");
request.getQueryString()), //
this::hasReadAccess) // return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request);
.getOrThrow(); //
//// final Collection<Exam> exams = this.examDAO
return this.paginationService.buildPageFromList( //// .allMatching(new FilterMap(
pageNumber, //// allRequestParams,
pageSize, //// request.getQueryString()),
sort, //// this::hasReadAccess)
exams, //// .getOrThrow();
pageSort(sort)); ////
} //// return this.paginationService.buildPageFromList(
} //// pageNumber,
//// pageSize,
//// sort,
//// exams,
//// pageSort(sort));
// }
// }
@RequestMapping( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT path = API.MODEL_ID_VAR_PATH_SEGMENT
@ -586,13 +590,13 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
} }
if (sortBy.equals(Exam.FILTER_ATTR_NAME) || sortBy.equals(QuizData.QUIZ_ATTR_NAME)) { 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)) { 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)) { 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)) { if (PageSortOrder.DESCENDING == PageSortOrder.getSortOrder(sort)) {