SEBSERV-353 implementation
This commit is contained in:
parent
f72e57d7c7
commit
f380cd49f9
17 changed files with 656 additions and 18 deletions
|
@ -20,7 +20,9 @@ public final class API {
|
|||
|
||||
public enum BatchActionType {
|
||||
EXAM_CONFIG_STATE_CHANGE(EntityType.CONFIGURATION_NODE),
|
||||
EXAM_CONFIG_REST_TEMPLATE_SETTINGS(EntityType.CONFIGURATION_NODE);
|
||||
EXAM_CONFIG_REST_TEMPLATE_SETTINGS(EntityType.CONFIGURATION_NODE),
|
||||
ARCHIVE_EXAM(EntityType.EXAM),
|
||||
DELETE_EXAM(EntityType.EXAM);
|
||||
|
||||
public final EntityType entityType;
|
||||
|
||||
|
|
|
@ -21,5 +21,6 @@ public enum UserLogActivityType {
|
|||
FINISHED,
|
||||
DELETE,
|
||||
LOGIN,
|
||||
LOGOUT
|
||||
LOGOUT,
|
||||
ARCHIVE
|
||||
}
|
|
@ -307,6 +307,16 @@ public enum ActionDefinition {
|
|||
ImageIcon.TOGGLE_ON,
|
||||
PageStateDefinitionImpl.EXAM_LIST,
|
||||
ActionCategory.EXAM_LIST),
|
||||
EXAM_LIST_BULK_ARCHIVE(
|
||||
new LocTextKey("sebserver.exam.list.action.archive"),
|
||||
ImageIcon.ARCHIVE,
|
||||
PageStateDefinitionImpl.EXAM_LIST,
|
||||
ActionCategory.EXAM_LIST),
|
||||
EXAM_LIST_BULK_DELETE(
|
||||
new LocTextKey("sebserver.exam.list.action.delete"),
|
||||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.EXAM_LIST,
|
||||
ActionCategory.EXAM_LIST),
|
||||
|
||||
EXAM_MODIFY_SEB_RESTRICTION_DETAILS(
|
||||
new LocTextKey("sebserver.exam.action.sebrestriction.details"),
|
||||
|
@ -683,7 +693,6 @@ public enum ActionDefinition {
|
|||
ImageIcon.SWITCH,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST,
|
||||
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
||||
|
||||
SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE(
|
||||
new LocTextKey("sebserver.examconfig.list.action.reset"),
|
||||
ImageIcon.EXPORT,
|
||||
|
|
|
@ -69,7 +69,6 @@ public class SEBExamConfigBatchResetToTemplatePopup extends AbstractBatchActionW
|
|||
protected Supplier<PageContext> createResultPageSupplier(
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ConfigurationNode> formHandle) {
|
||||
|
||||
// No specific fields for this action
|
||||
return () -> pageContext;
|
||||
}
|
||||
|
@ -78,7 +77,6 @@ public class SEBExamConfigBatchResetToTemplatePopup extends AbstractBatchActionW
|
|||
protected void extendBatchActionRequest(
|
||||
final PageContext pageContext,
|
||||
final RestCall<BatchAction>.RestCallBuilder batchActionRequestBuilder) {
|
||||
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
|
@ -87,7 +85,6 @@ public class SEBExamConfigBatchResetToTemplatePopup extends AbstractBatchActionW
|
|||
final PageContext formContext,
|
||||
final FormBuilder formHead,
|
||||
final boolean readonly) {
|
||||
|
||||
// No specific fields for this action
|
||||
return formHead;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.exam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
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.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
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.AbstractBatchActionWizard;
|
||||
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.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamsByIds;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ExamBatchArchivePopup extends AbstractBatchActionWizard {
|
||||
|
||||
private final static LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.exam.list.batch.archive.title");
|
||||
private final static LocTextKey ACTION_DO_RESET =
|
||||
new LocTextKey("sebserver.exam.list.batch.action.archive");
|
||||
private final static LocTextKey FORM_INFO =
|
||||
new LocTextKey("sebserver.exam.list.batch.action.archive.info");
|
||||
|
||||
protected ExamBatchArchivePopup(final PageService pageService, final ServerPushService serverPushService) {
|
||||
super(pageService, serverPushService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getTitle() {
|
||||
return FORM_TITLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionInfo() {
|
||||
return FORM_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionTitle() {
|
||||
return ACTION_DO_RESET;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BatchActionType getBatchActionType() {
|
||||
return BatchActionType.ARCHIVE_EXAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<PageContext> createResultPageSupplier(
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ConfigurationNode> formHandle) {
|
||||
|
||||
// No specific fields for this action
|
||||
return () -> pageContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendBatchActionRequest(
|
||||
final PageContext pageContext,
|
||||
final RestCall<BatchAction>.RestCallBuilder batchActionRequestBuilder) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FormBuilder buildSpecificFormFields(
|
||||
final PageContext formContext,
|
||||
final FormBuilder formHead,
|
||||
final boolean readonly) {
|
||||
|
||||
// No specific fields for this action
|
||||
return formHead;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applySelectionList(
|
||||
final PageContext formContext,
|
||||
final Set<EntityKey> multiSelection) {
|
||||
|
||||
final ResourceService resourceService = this.pageService.getResourceService();
|
||||
|
||||
final String ids = StringUtils.join(
|
||||
multiSelection.stream().map(EntityKey::getModelId).collect(Collectors.toList()),
|
||||
Constants.LIST_SEPARATOR_CHAR);
|
||||
|
||||
final RestService restService = this.pageService.getRestService();
|
||||
final List<Exam> selected = new ArrayList<>(restService.getBuilder(GetExamsByIds.class)
|
||||
.withQueryParam(API.PARAM_MODEL_ID_LIST, ids)
|
||||
.call()
|
||||
.getOr(Collections.emptyList()));
|
||||
|
||||
selected.sort((exam1, exam2) -> exam1.name.compareTo(exam2.name));
|
||||
|
||||
this.pageService.staticListTableBuilder(selected, EntityType.EXAM)
|
||||
.withPaging(10)
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_LMS_SETUP_ID,
|
||||
ExamList.COLUMN_TITLE_LMS_KEY,
|
||||
ExamList.examLmsSetupNameFunction(resourceService)))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_QUIZ_NAME,
|
||||
ExamList.COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_STATUS,
|
||||
ExamList.COLUMN_TITLE_STATE_KEY,
|
||||
resourceService::localizedExamStatusName))
|
||||
|
||||
.compose(formContext);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.exam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
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.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
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.AbstractBatchActionWizard;
|
||||
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.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamsByIds;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ExamBatchDeletePopup extends AbstractBatchActionWizard {
|
||||
|
||||
private final static LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.exam.list.batch.delete.title");
|
||||
private final static LocTextKey ACTION_DO_RESET =
|
||||
new LocTextKey("sebserver.exam.list.batch.action.delete");
|
||||
private final static LocTextKey FORM_INFO =
|
||||
new LocTextKey("sebserver.exam.list.batch.action.delete.info");
|
||||
|
||||
protected ExamBatchDeletePopup(final PageService pageService, final ServerPushService serverPushService) {
|
||||
super(pageService, serverPushService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getTitle() {
|
||||
return FORM_TITLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionInfo() {
|
||||
return FORM_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionTitle() {
|
||||
return ACTION_DO_RESET;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BatchActionType getBatchActionType() {
|
||||
return BatchActionType.DELETE_EXAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<PageContext> createResultPageSupplier(
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ConfigurationNode> formHandle) {
|
||||
// No specific fields for this action
|
||||
return () -> pageContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendBatchActionRequest(final PageContext pageContext,
|
||||
final RestCall<BatchAction>.RestCallBuilder batchActionRequestBuilder) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FormBuilder buildSpecificFormFields(
|
||||
final PageContext formContext,
|
||||
final FormBuilder formHead,
|
||||
final boolean readonly) {
|
||||
|
||||
// No specific fields for this action
|
||||
return formHead;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applySelectionList(
|
||||
final PageContext formContext,
|
||||
final Set<EntityKey> multiSelection) {
|
||||
|
||||
final ResourceService resourceService = this.pageService.getResourceService();
|
||||
|
||||
final String ids = StringUtils.join(
|
||||
multiSelection.stream().map(EntityKey::getModelId).collect(Collectors.toList()),
|
||||
Constants.LIST_SEPARATOR_CHAR);
|
||||
|
||||
final RestService restService = this.pageService.getRestService();
|
||||
final List<Exam> selected = new ArrayList<>(restService.getBuilder(GetExamsByIds.class)
|
||||
.withQueryParam(API.PARAM_MODEL_ID_LIST, ids)
|
||||
.call()
|
||||
.getOr(Collections.emptyList()));
|
||||
|
||||
selected.sort((exam1, exam2) -> exam1.name.compareTo(exam2.name));
|
||||
|
||||
this.pageService.staticListTableBuilder(selected, EntityType.EXAM)
|
||||
.withPaging(10)
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_LMS_SETUP_ID,
|
||||
ExamList.COLUMN_TITLE_LMS_KEY,
|
||||
ExamList.examLmsSetupNameFunction(resourceService)))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_QUIZ_NAME,
|
||||
ExamList.COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_STATUS,
|
||||
ExamList.COLUMN_TITLE_STATE_KEY,
|
||||
resourceService::localizedExamStatusName))
|
||||
|
||||
.compose(formContext);
|
||||
}
|
||||
|
||||
}
|
|
@ -98,12 +98,19 @@ public class ExamList implements TemplateComposer {
|
|||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
private final ExamBatchArchivePopup examBatchArchivePopup;
|
||||
private final ExamBatchDeletePopup examBatchDeletePopup;
|
||||
|
||||
protected ExamList(
|
||||
final PageService pageService,
|
||||
final ExamBatchArchivePopup examBatchArchivePopup,
|
||||
final ExamBatchDeletePopup examBatchDeletePopup,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.examBatchArchivePopup = examBatchArchivePopup;
|
||||
this.examBatchDeletePopup = examBatchDeletePopup;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.institutionFilter = new TableFilterAttribute(
|
||||
|
@ -156,6 +163,7 @@ public class ExamList implements TemplateComposer {
|
|||
// table
|
||||
final EntityTable<Exam> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
|
||||
.withMultiSelection()
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRowDecorator(decorateOnExamConsistency(this.pageService))
|
||||
|
@ -224,7 +232,9 @@ public class ExamList implements TemplateComposer {
|
|||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_VIEW_FROM_LIST,
|
||||
ActionDefinition.EXAM_MODIFY_FROM_LIST))
|
||||
ActionDefinition.EXAM_MODIFY_FROM_LIST,
|
||||
ActionDefinition.EXAM_LIST_BULK_ARCHIVE,
|
||||
ActionDefinition.EXAM_LIST_BULK_DELETE))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
|
@ -241,7 +251,23 @@ public class ExamList implements TemplateComposer {
|
|||
table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
||||
action -> modifyExam(action, table),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im(), false);
|
||||
.publishIf(() -> userGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_LIST_BULK_ARCHIVE)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
this.examBatchArchivePopup.popupCreationFunction(pageContext),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_LIST_BULK_DELETE)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
this.examBatchDeletePopup.popupCreationFunction(pageContext),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrant.iw(), false);
|
||||
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_LIST_HIDE_MISSING)
|
||||
|
@ -322,7 +348,7 @@ public class ExamList implements TemplateComposer {
|
|||
});
|
||||
}
|
||||
|
||||
private static Function<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
|
||||
public static Function<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
|
||||
return exam -> resourceService.getLmsSetupNameFunction()
|
||||
.apply(String.valueOf(exam.lmsSetupId));
|
||||
}
|
||||
|
|
|
@ -84,6 +84,12 @@ public abstract class AbstractBatchActionWizard {
|
|||
final FormBuilder formHead,
|
||||
final boolean readonly);
|
||||
|
||||
protected void applySelectionList(
|
||||
final PageContext formContext,
|
||||
final Set<EntityKey> multiSelection) {
|
||||
|
||||
}
|
||||
|
||||
public Function<PageAction, PageAction> popupCreationFunction(final PageContext pageContext) {
|
||||
|
||||
return action -> {
|
||||
|
@ -135,6 +141,8 @@ public abstract class AbstractBatchActionWizard {
|
|||
false)
|
||||
.build();
|
||||
|
||||
applySelectionList(formContext, multiSelection);
|
||||
|
||||
return createResultPageSupplier(pageContext, formHandle);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.exam;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
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.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 GetExamsByIds extends RestCall<Collection<Exam>> {
|
||||
|
||||
public GetExamsByIds() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_LIST,
|
||||
EntityType.EXAM,
|
||||
new TypeReference<Collection<Exam>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.LIST_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -101,11 +101,13 @@ public class StaticListPageSupplier<T> implements PageSupplier<T> {
|
|||
return new Page<>(1, 1, this.column, this.list);
|
||||
}
|
||||
|
||||
final int numOfPages = this.list.size() / this.pageSize;
|
||||
|
||||
int numOfPages = this.list.size() / this.pageSize;
|
||||
if (numOfPages <= 0) {
|
||||
return new Page<>(1, 1, this.column, this.list);
|
||||
}
|
||||
if (this.list.size() % this.pageSize > 0) {
|
||||
numOfPages++;
|
||||
}
|
||||
|
||||
int from = (this.pageNumber - 1) * this.pageSize;
|
||||
if (from < 0) {
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.servicelayer.bulkaction.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@WebServiceProfile
|
||||
public class ArchiveExamAction implements BatchActionExec {
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final AuthorizationService authorization;
|
||||
private final UserActivityLogDAO userActivityLogDAO;
|
||||
|
||||
public ArchiveExamAction(
|
||||
final ExamDAO examDAO,
|
||||
final ExamAdminService examAdminService,
|
||||
final AuthorizationService authorization,
|
||||
final UserActivityLogDAO userActivityLogDAO) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.examAdminService = examAdminService;
|
||||
this.authorization = authorization;
|
||||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchActionType actionType() {
|
||||
return BatchActionType.ARCHIVE_EXAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIMessage checkConsistency(final Map<String, String> actionAttributes) {
|
||||
// no additional check here
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<EntityKey> doSingleAction(final String modelId, final BatchAction batchAction) {
|
||||
return this.examDAO.byModelId(modelId)
|
||||
.flatMap(this::checkWriteAccess)
|
||||
.flatMap(this.examAdminService::archiveExam)
|
||||
.flatMap(exam -> logArchived(exam, batchAction))
|
||||
.map(Exam::getEntityKey);
|
||||
}
|
||||
|
||||
private Result<Exam> checkWriteAccess(final Exam entity) {
|
||||
if (entity != null) {
|
||||
this.authorization.checkWrite(entity);
|
||||
}
|
||||
return Result.of(entity);
|
||||
}
|
||||
|
||||
private Result<Exam> logArchived(final Exam entity, final BatchAction batchAction) {
|
||||
return this.userActivityLogDAO.log(
|
||||
batchAction.ownerId,
|
||||
UserLogActivityType.ARCHIVE,
|
||||
entity,
|
||||
"Part of batch action: " + batchAction.processorId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.servicelayer.bulkaction.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@WebServiceProfile
|
||||
public class DeleteExamAction implements BatchActionExec {
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
private final AuthorizationService authorization;
|
||||
private final UserActivityLogDAO userActivityLogDAO;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
||||
public DeleteExamAction(
|
||||
final ExamDAO examDAO,
|
||||
final AuthorizationService authorization,
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final ExamSessionService examSessionService) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.authorization = authorization;
|
||||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
this.examSessionService = examSessionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchActionType actionType() {
|
||||
return BatchActionType.DELETE_EXAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIMessage checkConsistency(final Map<String, String> actionAttributes) {
|
||||
// no additional check here
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<EntityKey> doSingleAction(final String modelId, final BatchAction batchAction) {
|
||||
return this.examDAO.byModelId(modelId)
|
||||
.flatMap(this::checkWriteAccess)
|
||||
.flatMap(this::checkNoActiveSEBClientConnections)
|
||||
.flatMap(this::deleteExamWithRefs)
|
||||
.flatMap(exam -> logDeleted(exam, batchAction))
|
||||
.map(Exam::getEntityKey);
|
||||
}
|
||||
|
||||
private Result<Exam> deleteExamWithRefs(final Exam entity) {
|
||||
final Result<Collection<EntityKey>> delete =
|
||||
this.examDAO.delete(new HashSet<>(Arrays.asList(entity.getEntityKey())));
|
||||
if (delete.hasError()) {
|
||||
return Result.ofError(delete.getError());
|
||||
} else {
|
||||
return Result.of(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private Result<Exam> checkWriteAccess(final Exam entity) {
|
||||
if (entity != null) {
|
||||
this.authorization.checkWrite(entity);
|
||||
}
|
||||
return Result.of(entity);
|
||||
}
|
||||
|
||||
private Result<Exam> checkNoActiveSEBClientConnections(final Exam exam) {
|
||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||
return Result.ofError(new APIMessageException(
|
||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.of("Exam currently has active SEB Client connections.")));
|
||||
}
|
||||
|
||||
return Result.of(exam);
|
||||
}
|
||||
|
||||
private Result<Exam> logDeleted(final Exam entity, final BatchAction batchAction) {
|
||||
return this.userActivityLogDAO.log(
|
||||
batchAction.ownerId,
|
||||
UserLogActivityType.DELETE,
|
||||
entity,
|
||||
"Part of batch action: " + batchAction.processorId);
|
||||
}
|
||||
|
||||
}
|
|
@ -76,7 +76,6 @@ public class ExamConfigResetToTemplate implements BatchActionExec {
|
|||
return node;
|
||||
})
|
||||
.map(Entity::getEntityKey);
|
||||
|
||||
}
|
||||
|
||||
private ConfigurationNode checkConsistency(final ConfigurationNode configurationNode) {
|
||||
|
|
|
@ -78,6 +78,12 @@ public interface UserActivityLogDAO extends
|
|||
* @return Result of the Entity or referring to an Error if happened */
|
||||
<E extends Entity> Result<E> logModify(E entity);
|
||||
|
||||
/** Create a user activity log entry for the current user of activity type ARCHIVE
|
||||
*
|
||||
* @param entity the Entity
|
||||
* @return Result of the Entity or referring to an Error if happened */
|
||||
<E extends Entity> Result<E> logArchive(E entity);
|
||||
|
||||
/** Create a user activity log entry for the current user of activity type FINISHED
|
||||
*
|
||||
* @param entity the Entity
|
||||
|
@ -146,6 +152,19 @@ public interface UserActivityLogDAO extends
|
|||
E entity,
|
||||
String message);
|
||||
|
||||
/** Creates a user activity log entry.
|
||||
*
|
||||
* @param userUUID The User UUID
|
||||
* @param activityType the activity type
|
||||
* @param entity the Entity
|
||||
* @param message an optional message
|
||||
* @return Result of the Entity or referring to an Error if happened */
|
||||
<E extends Entity> Result<E> log(
|
||||
String userUUID,
|
||||
UserLogActivityType activityType,
|
||||
E entity,
|
||||
String message);
|
||||
|
||||
/** Creates a user activity log entry.
|
||||
*
|
||||
* @param user for specified SEBServerUser instance
|
||||
|
|
|
@ -161,6 +161,13 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <E extends Entity> Result<E> logArchive(final E entity) {
|
||||
return log(UserLogActivityType.ARCHIVE, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <E extends Entity> Result<E> logFinished(final E entity) {
|
||||
return log(UserLogActivityType.FINISHED, entity);
|
||||
}
|
||||
|
@ -244,7 +251,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
|
||||
try {
|
||||
log(
|
||||
this.userService.getCurrentUser(),
|
||||
this.userService.getCurrentUser().getUserInfo().uuid,
|
||||
activityType,
|
||||
entityType,
|
||||
entityId,
|
||||
|
@ -272,7 +279,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
|
||||
return Result.tryCatch(() -> {
|
||||
log(
|
||||
this.userService.getCurrentUser(),
|
||||
this.userService.getCurrentUser().getUserInfo().uuid,
|
||||
activityType,
|
||||
entityType,
|
||||
entityId,
|
||||
|
@ -295,7 +302,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
_message = "Entity details: " + entity;
|
||||
}
|
||||
|
||||
log(user, activityType, entity.entityType(), entity.getModelId(), _message);
|
||||
log(user.getUserInfo().uuid, activityType, entity.entityType(), entity.getModelId(), _message);
|
||||
return entity;
|
||||
})
|
||||
.onError(TransactionHandler::rollback)
|
||||
|
@ -308,8 +315,35 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
t));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <E extends Entity> Result<E> log(
|
||||
final String userUUID,
|
||||
final UserLogActivityType activityType,
|
||||
final E entity,
|
||||
final String message) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
String _message = message;
|
||||
if (message == null) {
|
||||
_message = "Entity details: " + entity;
|
||||
}
|
||||
|
||||
log(userUUID, activityType, entity.entityType(), entity.getModelId(), _message);
|
||||
return entity;
|
||||
})
|
||||
.onError(TransactionHandler::rollback)
|
||||
.onError(t -> log.error(
|
||||
"Unexpected error while trying to log user activity for user {}, action-type: {} entity-type: {} entity-id: {}",
|
||||
userUUID,
|
||||
activityType,
|
||||
entity.entityType().name(),
|
||||
entity.getModelId(),
|
||||
t));
|
||||
}
|
||||
|
||||
private void log(
|
||||
final SEBServerUser user,
|
||||
final String userUUID,
|
||||
final UserLogActivityType activityType,
|
||||
final EntityType entityType,
|
||||
final String entityId,
|
||||
|
@ -317,7 +351,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
|
||||
this.userLogRecordMapper.insertSelective(new UserActivityLogRecord(
|
||||
null,
|
||||
user.getUserInfo().uuid,
|
||||
userUUID,
|
||||
System.currentTimeMillis(),
|
||||
activityType.name(),
|
||||
entityType.name(),
|
||||
|
|
|
@ -193,7 +193,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
return this.examDAO.byPK(modelId)
|
||||
.flatMap(this::checkWriteAccess)
|
||||
.flatMap(this.examAdminService::archiveExam)
|
||||
.flatMap(this::logModify)
|
||||
.flatMap(exam -> super.userActivityLogDAO.logArchive(exam))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ sebserver.overall.types.entityType.CERTIFICATE=Certificate
|
|||
sebserver.overall.types.entityType.EXAM_TEMPLATE=Exam Template
|
||||
sebserver.overall.types.entityType.EXAM_PROCTOR_DATA=Exam Proctoring Settings
|
||||
sebserver.overall.types.entityType.BATCH_ACTION=Batch Action
|
||||
sebserver.overall.types.entityType.CLIENT_GROUP=Client Group
|
||||
|
||||
sebserver.overall.activity.title.serveradmin=SEB Server Administration
|
||||
sebserver.overall.activity.title.sebconfig=Configurations
|
||||
|
@ -513,6 +514,15 @@ sebserver.exam.list.empty=No Exam can be found. Please adapt the filter or impor
|
|||
sebserver.exam.list.modify.out.dated=Finished exams cannot be modified.
|
||||
sebserver.exam.list.action.no.modify.privilege=No Access: An Exam from another institution cannot be modified.
|
||||
|
||||
sebserver.exam.list.action.archive=Archive Selected Exams
|
||||
sebserver.exam.list.action.delete=Delete Selected Exams
|
||||
sebserver.exam.list.batch.archive.title=Batch Archive Exams
|
||||
sebserver.exam.list.batch.action.archive=Archive All
|
||||
sebserver.exam.list.batch.action.archive.info=This batch action archives all selected exams below.<br/>If a particular exam is in a invalid state for archiving it will be ignored.<br/>Please make sure that all selected exams shall be archived.
|
||||
sebserver.exam.list.batch.delete.title=Delete All
|
||||
sebserver.exam.list.batch.action.delete=Delete Selected Exams
|
||||
sebserver.exam.list.batch.action.delete.info=This batch action deletes all selected exams below.<br/>If a particular exam is in a invalid state for deleting it will be ignored.<br/>Since this action is irreversible and all deleted exams are lost, please make sure that all selected exams shall be deleted and use Cancel to abort the action if not so.
|
||||
|
||||
sebserver.exam.consistency.title=Note: This exam is already running but has some missing settings
|
||||
sebserver.exam.consistency.missing-supporter= - There are no Exam Supporter defined for this exam. Use 'Edit Exam' on the right to add an Exam Supporter.
|
||||
sebserver.exam.consistency.missing-indicator= - There is no indicator defined for this exam. Use 'Add Indicator" on the right to add an indicator.
|
||||
|
|
Loading…
Reference in a new issue