SEBSERV-160 implemented reset from template
This commit is contained in:
parent
0921b713f5
commit
55baa2d518
45 changed files with 446 additions and 89 deletions
|
@ -20,7 +20,7 @@ public final class API {
|
|||
|
||||
public enum BatchActionType {
|
||||
EXAM_CONFIG_STATE_CHANGE(EntityType.CONFIGURATION_NODE),
|
||||
EXAM_CONFIG_APPLY_TEMPLATE_VALUES(EntityType.CONFIGURATION_NODE);
|
||||
EXAM_CONFIG_REST_TEMPLATE_SETTINGS(EntityType.CONFIGURATION_NODE);
|
||||
|
||||
public final EntityType entityType;
|
||||
|
||||
|
|
|
@ -246,7 +246,7 @@ public class APIMessage implements Serializable {
|
|||
}
|
||||
|
||||
public APIMessageException(final APIMessage apiMessage) {
|
||||
super();
|
||||
super(apiMessage.systemMessage + " " + apiMessage.details);
|
||||
this.apiMessages = Arrays.asList(apiMessage);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ public enum UserLogActivityType {
|
|||
PASSWORD_CHANGE,
|
||||
DEACTIVATE,
|
||||
ACTIVATE,
|
||||
FINISHED,
|
||||
DELETE,
|
||||
LOGIN,
|
||||
LOGOUT
|
||||
|
|
|
@ -533,6 +533,12 @@ public enum ActionDefinition {
|
|||
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,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST,
|
||||
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
||||
|
||||
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
|
||||
new LocTextKey("sebserver.examconfig.action.list.modify.properties"),
|
||||
ImageIcon.EDIT,
|
||||
|
|
|
@ -138,14 +138,14 @@ public class InstitutionList implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.INSTITUTION_MODIFY_FROM_LIST)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> instGrant.m(), false)
|
||||
|
|
|
@ -222,11 +222,12 @@ public class UserAccountList implements TemplateComposer {
|
|||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_MODIFY_FROM_LIST)
|
||||
.withSelect(table::getSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY)
|
||||
|
|
|
@ -244,7 +244,7 @@ public class UserActivityLogs implements TemplateComposer {
|
|||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
action -> this.showDetails(action, table.getSingleSelectedROWData()),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
|
|
|
@ -159,7 +159,7 @@ public class CertificateList implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_CERTIFICATE_REMOVE)
|
||||
.withConfirm(() -> FORM_ACTION_MESSAGE_REMOVE_CONFIRM_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
this::removeCertificate,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> grantCheck.iw(), false);
|
||||
|
|
|
@ -249,7 +249,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_EDIT)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
attrTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant, false)
|
||||
|
@ -257,7 +257,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_SET_DEFAULT)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
attrTable::getMultiSelection,
|
||||
action -> this.resetToDefaults(action, attrTable),
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
|
@ -266,7 +266,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_REMOVE_VIEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
attrTable::getMultiSelection,
|
||||
action -> this.removeFormView(action, attrTable),
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
|
@ -275,7 +275,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_ATTACH_DEFAULT_VIEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
attrTable::getMultiSelection,
|
||||
action -> this.attachView(action, attrTable),
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
|
|
|
@ -147,7 +147,7 @@ public class ConfigTemplateList implements TemplateComposer {
|
|||
.publishIf(examConfigGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST)
|
||||
.withSelect(templateTable::getSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
.withSelect(templateTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_TEMPLATE_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
|
|
|
@ -173,7 +173,8 @@ public class SEBClientConfigList implements TemplateComposer {
|
|||
.publishIf(clientConfigGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_MODIFY_FROM_LIST)
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.configs;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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.model.BatchAction;
|
||||
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.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;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SEBExamConfigBatchResetToTemplatePopup extends AbstractBatchActionWizard {
|
||||
|
||||
private final static LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.reset.title");
|
||||
private final static LocTextKey ACTION_DO_RESET =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.reset");
|
||||
private final static LocTextKey FORM_INFO =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.reset.info");
|
||||
|
||||
protected SEBExamConfigBatchResetToTemplatePopup(
|
||||
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.EXAM_CONFIG_REST_TEMPLATE_SETTINGS;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
|
@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
|||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SEBExamConfigStateChangePopup extends AbstractBatchActionWizard {
|
||||
public class SEBExamConfigBatchStateChangePopup extends AbstractBatchActionWizard {
|
||||
|
||||
private static final String ATTR_SELECTED_TARGET_STATE = "selectedTargetState";
|
||||
|
||||
|
@ -45,7 +45,7 @@ public class SEBExamConfigStateChangePopup extends AbstractBatchActionWizard {
|
|||
private final static LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.status");
|
||||
|
||||
protected SEBExamConfigStateChangePopup(
|
||||
protected SEBExamConfigBatchStateChangePopup(
|
||||
final PageService pageService,
|
||||
final ServerPushService serverPushService) {
|
||||
|
|
@ -70,7 +70,8 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
private final PageService pageService;
|
||||
private final SEBExamConfigImportPopup sebExamConfigImportPopup;
|
||||
private final SEBExamConfigCreationPopup sebExamConfigCreationPopup;
|
||||
private final SEBExamConfigStateChangePopup sebExamConfigStateChangePopup;
|
||||
private final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup;
|
||||
private final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup;
|
||||
private final CurrentUser currentUser;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
@ -79,13 +80,15 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
final PageService pageService,
|
||||
final SEBExamConfigImportPopup sebExamConfigImportPopup,
|
||||
final SEBExamConfigCreationPopup sebExamConfigCreationPopup,
|
||||
final SEBExamConfigStateChangePopup sebExamConfigStateChangePopup,
|
||||
final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup,
|
||||
final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.sebExamConfigImportPopup = sebExamConfigImportPopup;
|
||||
this.sebExamConfigCreationPopup = sebExamConfigCreationPopup;
|
||||
this.sebExamConfigStateChangePopup = sebExamConfigStateChangePopup;
|
||||
this.sebExamConfigBatchStateChangePopup = sebExamConfigBatchStateChangePopup;
|
||||
this.sebExamConfigBatchResetToTemplatePopup = sebExamConfigBatchResetToTemplatePopup;
|
||||
this.currentUser = pageService.getCurrentUser();
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.pageSize = pageSize;
|
||||
|
@ -159,7 +162,8 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE))
|
||||
ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
|
@ -171,7 +175,7 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST)
|
||||
.withSelect(
|
||||
configTable::getSelection,
|
||||
configTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
@ -205,8 +209,16 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE)
|
||||
.withSelect(
|
||||
configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
||||
this.sebExamConfigStateChangePopup.popupCreationFunction(pageContext),
|
||||
configTable::getMultiSelection,
|
||||
this.sebExamConfigBatchStateChangePopup.popupCreationFunction(pageContext),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> examConfigGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE)
|
||||
.withSelect(
|
||||
configTable::getMultiSelection,
|
||||
this.sebExamConfigBatchResetToTemplatePopup.popupCreationFunction(pageContext),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> examConfigGrant.im(), false)
|
||||
|
|
|
@ -133,7 +133,7 @@ public class ExamFormIndicators implements TemplateComposer {
|
|||
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false)
|
||||
|
@ -141,7 +141,7 @@ public class ExamFormIndicators implements TemplateComposer {
|
|||
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false)
|
||||
|
|
|
@ -210,7 +210,8 @@ public class ExamList implements TemplateComposer {
|
|||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST)
|
||||
|
|
|
@ -268,7 +268,7 @@ public class ExamTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.INDICATOR_TEMPLATE_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false)
|
||||
|
@ -276,7 +276,7 @@ public class ExamTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.INDICATOR_TEMPLATE_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false)
|
||||
|
|
|
@ -175,11 +175,13 @@ public class ExamTemplateList implements TemplateComposer {
|
|||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_MODIFY_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im(), false);
|
||||
}
|
||||
|
||||
|
|
|
@ -169,7 +169,10 @@ public class LmsSetupList implements TemplateComposer {
|
|||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST)
|
||||
|
|
|
@ -236,7 +236,7 @@ public class QuizLookupList implements TemplateComposer {
|
|||
actionBuilder
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
action -> this.showDetails(
|
||||
action,
|
||||
table.getSingleSelectedROWData(),
|
||||
|
|
|
@ -170,7 +170,10 @@ public class FinishedExam implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(examKey)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(isExamSupporter, false);
|
||||
}
|
||||
|
||||
|
|
|
@ -141,7 +141,10 @@ public class FinishedExamList implements TemplateComposer {
|
|||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||
|
||||
}
|
||||
|
|
|
@ -264,7 +264,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
.withParentEntityKey(parentEntityKey)
|
||||
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
||||
.withSelect(
|
||||
() -> notificationTable.getSelection(),
|
||||
() -> notificationTable.getMultiSelection(),
|
||||
action -> this.confirmNotification(action, connectionData, notificationTable),
|
||||
|
||||
NOTIFICATION_LIST_NO_SELECTION_KEY)
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
|
@ -172,7 +173,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
.withParentEntityKey(entityKey)
|
||||
.create(),
|
||||
this.pageService)
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
.withSelectionListener(this.getSelectionPublisherClientConnectionTable(
|
||||
pageContext,
|
||||
ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION,
|
||||
ActionDefinition.MONITOR_EXAM_QUIT_SELECTED,
|
||||
|
@ -505,4 +506,13 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
return action;
|
||||
}
|
||||
|
||||
private Consumer<ClientConnectionTable> getSelectionPublisherClientConnectionTable(
|
||||
final PageContext pageContext,
|
||||
final ActionDefinition... actionDefinitions) {
|
||||
|
||||
return table -> this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(table.getSingleSelection() != null, actionDefinitions),
|
||||
pageContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -141,7 +141,10 @@ public class MonitoringRunningExamList implements TemplateComposer {
|
|||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ public class SEBClientEvents implements TemplateComposer {
|
|||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
action -> this.sebClientEventDetailsPopup.showDetails(action, table.getSingleSelectedROWData()),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
|
|
|
@ -172,7 +172,7 @@ public interface PageService {
|
|||
* @return a message supplier to notify deactivation dependencies to the user */
|
||||
default <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final EntityTable<T> table) {
|
||||
return () -> {
|
||||
final List<EntityKey> multiSelection = table.getMultiSelection();
|
||||
final Set<EntityKey> multiSelection = table.getMultiSelection();
|
||||
if (multiSelection.size() > 1) {
|
||||
throw new PageMessageException(MESSAGE_NO_MULTISELECTION);
|
||||
}
|
||||
|
@ -204,12 +204,12 @@ public interface PageService {
|
|||
* @param pageContext the current PageContext
|
||||
* @param actionDefinitions list of action definitions that activity should be toggled on table selection
|
||||
* @return the selection publisher that handles this defines action activation on table selection */
|
||||
default <T> Consumer<Set<T>> getSelectionPublisher(
|
||||
default <T extends ModelIdAware> Consumer<EntityTable<T>> getSelectionPublisher(
|
||||
final PageContext pageContext,
|
||||
final ActionDefinition... actionDefinitions) {
|
||||
|
||||
return rows -> firePageEvent(
|
||||
new ActionActivationEvent(!rows.isEmpty(), actionDefinitions),
|
||||
return table -> firePageEvent(
|
||||
new ActionActivationEvent(table.hasSelection(), actionDefinitions),
|
||||
pageContext);
|
||||
}
|
||||
|
||||
|
@ -225,15 +225,15 @@ public interface PageService {
|
|||
* @param pageContext the current PageContext
|
||||
* @param actionDefinitions list of action definitions that activity should be toggled on table selection
|
||||
* @return the selection publisher that handles this defines action activation on table selection */
|
||||
default <T extends Activatable> Consumer<Set<T>> getSelectionPublisher(
|
||||
default <T extends Activatable & ModelIdAware> Consumer<EntityTable<T>> getSelectionPublisher(
|
||||
final ActionDefinition toggle,
|
||||
final ActionDefinition activate,
|
||||
final ActionDefinition deactivate,
|
||||
final PageContext pageContext,
|
||||
final ActionDefinition... actionDefinitions) {
|
||||
|
||||
return rows -> {
|
||||
|
||||
return table -> {
|
||||
final Set<T> rows = table.getPageSelectionData();
|
||||
if (!rows.isEmpty()) {
|
||||
firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
|
|
|
@ -171,7 +171,6 @@ public final class PageAction {
|
|||
// if selection is needed, check selection fist, before confirm dialog
|
||||
if (this.selectionSupplier != null) {
|
||||
getMultiSelection();
|
||||
|
||||
}
|
||||
|
||||
final LocTextKey confirmMessage = this.confirm.apply(this);
|
||||
|
|
|
@ -249,7 +249,7 @@ public class PageServiceImpl implements PageService {
|
|||
final Function<PageAction, PageAction> testBeforeActivation) {
|
||||
|
||||
return action -> {
|
||||
final List<EntityKey> multiSelection = table.getMultiSelection();
|
||||
final Set<EntityKey> multiSelection = table.getMultiSelection();
|
||||
if (multiSelection == null || multiSelection.isEmpty()) {
|
||||
throw new PageMessageException(noSelectionText);
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
|||
private final Table table;
|
||||
private final ColorData colorData;
|
||||
private final Function<ClientConnectionData, String> localizedClientConnectionStatusNameFunction;
|
||||
private Consumer<Set<EntityKey>> selectionListener;
|
||||
private Consumer<ClientConnectionTable> selectionListener;
|
||||
|
||||
private int tableWidth;
|
||||
private boolean needsSort = false;
|
||||
|
@ -238,7 +238,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
|||
}
|
||||
}
|
||||
|
||||
public ClientConnectionTable withSelectionListener(final Consumer<Set<EntityKey>> selectionListener) {
|
||||
public ClientConnectionTable withSelectionListener(final Consumer<ClientConnectionTable> selectionListener) {
|
||||
this.selectionListener = selectionListener;
|
||||
return this;
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
|||
return;
|
||||
}
|
||||
|
||||
this.selectionListener.accept(this.getSelection());
|
||||
this.selectionListener.accept(this);
|
||||
}
|
||||
|
||||
private void notifyTableInfoClick(final Event event) {
|
||||
|
|
|
@ -95,7 +95,7 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
|
||||
private final MultiValueMap<String, String> staticQueryParams;
|
||||
private final BiConsumer<TableItem, ROW> rowDecorator;
|
||||
private final Consumer<Set<ROW>> selectionListener;
|
||||
private final Consumer<EntityTable<ROW>> selectionListener;
|
||||
private final Consumer<Integer> contentChangeListener;
|
||||
|
||||
private final Set<String> multiselection;
|
||||
|
@ -125,7 +125,7 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
final boolean hideNavigation,
|
||||
final MultiValueMap<String, String> staticQueryParams,
|
||||
final BiConsumer<TableItem, ROW> rowDecorator,
|
||||
final Consumer<Set<ROW>> selectionListener,
|
||||
final Consumer<EntityTable<ROW>> selectionListener,
|
||||
final Consumer<Integer> contentChangeListener,
|
||||
final String defaultSortColumn,
|
||||
final PageSortOrder defaultSortOrder) {
|
||||
|
@ -263,6 +263,10 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
return this.table.getItemCount() > 0;
|
||||
}
|
||||
|
||||
public boolean hasSelection() {
|
||||
return (this.multiselection != null && !this.multiselection.isEmpty()) || this.table.getSelectionCount() > 0;
|
||||
}
|
||||
|
||||
public void setPageSize(final int pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
updateTableRows(
|
||||
|
@ -363,15 +367,18 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
return getEntityKey(selection[0]);
|
||||
}
|
||||
|
||||
public List<EntityKey> getMultiSelection() {
|
||||
public Set<EntityKey> getMultiSelection() {
|
||||
if (this.multiselection == null) {
|
||||
return Collections.emptyList();
|
||||
return getPageSelectionData()
|
||||
.stream()
|
||||
.map(row -> new EntityKey(row.getModelId(), getEntityType()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
return this.multiselection
|
||||
.stream()
|
||||
.map(modelId -> new EntityKey(modelId, getEntityType()))
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public ROW getFirstRowData() {
|
||||
|
@ -396,7 +403,7 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
return getRowData(selection[0]);
|
||||
}
|
||||
|
||||
private Set<ROW> getPageSelectionData() {
|
||||
public Set<ROW> getPageSelectionData() {
|
||||
final TableItem[] selection = this.table.getSelection();
|
||||
if (selection == null || selection.length == 0) {
|
||||
return Collections.emptySet();
|
||||
|
@ -407,10 +414,6 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<EntityKey> getSelection() {
|
||||
return getSelection(null);
|
||||
}
|
||||
|
||||
public Set<EntityKey> getSelection(final Predicate<ROW> grantCheck) {
|
||||
final TableItem[] selection = this.table.getSelection();
|
||||
if (selection == null) {
|
||||
|
@ -685,7 +688,7 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
return;
|
||||
}
|
||||
|
||||
this.selectionListener.accept(this.getPageSelectionData());
|
||||
this.selectionListener.accept(this);
|
||||
}
|
||||
|
||||
private void updateCurrentPageAttr() {
|
||||
|
@ -807,6 +810,9 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
this.multiselection.remove(modelId);
|
||||
} else {
|
||||
this.multiselection.add(modelId);
|
||||
Arrays.asList(this.table.getSelection())
|
||||
.stream()
|
||||
.forEach(i -> this.multiselection.add(getModelId(i)));
|
||||
}
|
||||
multiselectFromPage();
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.gui.table;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -48,7 +47,7 @@ public class TableBuilder<ROW extends ModelIdAware> {
|
|||
private boolean hideNavigation = false;
|
||||
private Function<PageSupplier.Builder<ROW>, PageSupplier.Builder<ROW>> restCallAdapter;
|
||||
private BiConsumer<TableItem, ROW> rowDecorator;
|
||||
private Consumer<Set<ROW>> selectionListener;
|
||||
private Consumer<EntityTable<ROW>> selectionListener;
|
||||
private Consumer<Integer> contentChangeListener;
|
||||
private boolean markupEnabled = false;
|
||||
private String defaultSortColumn = null;
|
||||
|
@ -152,7 +151,7 @@ public class TableBuilder<ROW extends ModelIdAware> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public TableBuilder<ROW> withSelectionListener(final Consumer<Set<ROW>> selectionListener) {
|
||||
public TableBuilder<ROW> withSelectionListener(final Consumer<EntityTable<ROW>> selectionListener) {
|
||||
this.selectionListener = selectionListener;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -201,6 +201,7 @@ public class PaginationServiceImpl implements PaginationService {
|
|||
final PageSortOrder sortOrder = PageSortOrder.getSortOrder(sort);
|
||||
final String sortColumnName = verifySortColumnName(sort, sortMappingName);
|
||||
if (StringUtils.isNotBlank(sortColumnName)) {
|
||||
startPage.setOrderByOnly(false);
|
||||
switch (sortOrder) {
|
||||
case DESCENDING: {
|
||||
PageHelper.orderBy(sortColumnName + " DESC");
|
||||
|
@ -211,6 +212,7 @@ public class PaginationServiceImpl implements PaginationService {
|
|||
break;
|
||||
}
|
||||
}
|
||||
PageHelper.orderBy("id");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
|||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
|
||||
|
@ -51,4 +52,9 @@ public interface UserService {
|
|||
* @param binder Springs WebDataBinder is injected on controller side */
|
||||
void addUsersInstitutionDefaultPropertySupport(final WebDataBinder binder);
|
||||
|
||||
/** Used to set authentication on different thread.
|
||||
*
|
||||
* @param authentication */
|
||||
void setAuthenticationIfAbsent(Authentication authentication);
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Collections;
|
|||
import java.util.EnumSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.CredentialsContainer;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
@ -26,7 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
|||
*
|
||||
* This implements Spring's UserDetails and CredentialsContainer to act as a principal
|
||||
* within internal authentication and authorization processes. */
|
||||
public final class SEBServerUser implements UserDetails, CredentialsContainer {
|
||||
public final class SEBServerUser implements UserDetails, CredentialsContainer, Authentication {
|
||||
|
||||
private static final long serialVersionUID = 5726250141482925769L;
|
||||
|
||||
|
@ -160,4 +161,34 @@ public final class SEBServerUser implements UserDetails, CredentialsContainer {
|
|||
return new SEBServerUser(user.id, UserInfo.of(user.userInfo), user.password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.userInfo.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDetails() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated() {
|
||||
return isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -94,6 +94,13 @@ public class UserServiceImpl implements UserService {
|
|||
binder.registerCustomEditor(Long.class, usersInstitutionDefaultEditor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticationIfAbsent(final Authentication authentication) {
|
||||
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. OAuth2Authentication strategy
|
||||
@Lazy
|
||||
@Component
|
||||
|
@ -115,6 +122,21 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
}
|
||||
|
||||
// 2. Separated thread strategy
|
||||
@Lazy
|
||||
@Component
|
||||
public static class OtherThreadUserExtractStrategy implements ExtractUserFromAuthenticationStrategy {
|
||||
|
||||
@Override
|
||||
public SEBServerUser extract(final Principal principal) {
|
||||
if (principal instanceof SEBServerUser) {
|
||||
return (SEBServerUser) principal;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final SEBServerUser ANONYMOUS_USER = new SEBServerUser(
|
||||
-1L,
|
||||
new UserInfo("SEB_SERVER_ANONYMOUS_USER", -2L, null, "anonymous", "anonymous", "anonymous", null, false,
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
|
@ -38,6 +40,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionServi
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.BatchActionDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
|
@ -46,17 +50,23 @@ public class BatchActionServiceImpl implements BatchActionService {
|
|||
private static final Logger log = LoggerFactory.getLogger(BatchActionServiceImpl.class);
|
||||
|
||||
private final BatchActionDAO batchActionDAO;
|
||||
private final UserDAO userDAO;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final UserActivityLogDAO userActivityLogDAO;
|
||||
private final EnumMap<BatchActionType, BatchActionExec> batchExecutions;
|
||||
|
||||
private ScheduledFuture<?> runningBatchProcess = null;
|
||||
|
||||
public BatchActionServiceImpl(
|
||||
final BatchActionDAO batchActionDAO,
|
||||
final UserDAO userDAO,
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final Collection<BatchActionExec> batchExecutions,
|
||||
final TaskScheduler taskScheduler) {
|
||||
|
||||
this.batchActionDAO = batchActionDAO;
|
||||
this.userDAO = userDAO;
|
||||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
this.taskScheduler = taskScheduler;
|
||||
|
||||
this.batchExecutions = new EnumMap<>(BatchActionType.class);
|
||||
|
@ -158,7 +168,8 @@ public class BatchActionServiceImpl implements BatchActionService {
|
|||
new BatchActionProcess(
|
||||
new BatchActionHandlerImpl(action),
|
||||
this.batchExecutions.get(action.actionType),
|
||||
action),
|
||||
action,
|
||||
getAuthentication(action)),
|
||||
Instant.now());
|
||||
})
|
||||
.onError(error -> {
|
||||
|
@ -174,22 +185,43 @@ public class BatchActionServiceImpl implements BatchActionService {
|
|||
}
|
||||
}
|
||||
|
||||
private Authentication getAuthentication(final BatchAction action) {
|
||||
try {
|
||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null) {
|
||||
return authentication;
|
||||
}
|
||||
|
||||
return this.userDAO.byModelId(action.ownerId)
|
||||
.flatMap(userInfo -> this.userDAO.sebServerUserByUsername(userInfo.username))
|
||||
.onError(error -> log.error("Failed to get batch action owner user -> ", error))
|
||||
.getOr(null);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to get authentication: ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final static class BatchActionProcess implements Runnable {
|
||||
|
||||
private final BatchActionHandler batchActionHandler;
|
||||
private final BatchActionExec batchActionExec;
|
||||
private final BatchAction batchAction;
|
||||
private final Authentication authentication;
|
||||
|
||||
private Set<String> processingIds;
|
||||
|
||||
public BatchActionProcess(
|
||||
final BatchActionHandler batchActionHandler,
|
||||
final BatchActionExec batchActionExec,
|
||||
final BatchAction batchAction) {
|
||||
final BatchAction batchAction,
|
||||
final Authentication authentication) {
|
||||
|
||||
this.batchActionHandler = batchActionHandler;
|
||||
this.batchActionExec = batchActionExec;
|
||||
this.batchAction = batchAction;
|
||||
this.authentication = authentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -198,6 +230,13 @@ public class BatchActionServiceImpl implements BatchActionService {
|
|||
|
||||
log.info("Starting or continuing batch action - {}", this.batchAction);
|
||||
|
||||
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
if (this.authentication == null) {
|
||||
throw new IllegalStateException("No authentication found within batch context");
|
||||
}
|
||||
SecurityContextHolder.getContext().setAuthentication(this.authentication);
|
||||
}
|
||||
|
||||
this.processingIds = new HashSet<>(this.batchAction.sourceIds);
|
||||
this.processingIds.removeAll(this.batchAction.successful);
|
||||
|
||||
|
@ -256,10 +295,10 @@ public class BatchActionServiceImpl implements BatchActionService {
|
|||
@Override
|
||||
public void handleError(final String modelId, final Exception error) {
|
||||
log.error(
|
||||
"Failed to process single entity on batch action. ModelId: {}, action: ",
|
||||
"Failed to process single entity on batch action. ModelId: {}, action: {}, errorMessage: {}",
|
||||
modelId,
|
||||
this.batchAction,
|
||||
error);
|
||||
error.getMessage());
|
||||
|
||||
BatchActionServiceImpl.this.batchActionDAO.setFailure(
|
||||
this.batchAction.id,
|
||||
|
@ -276,6 +315,10 @@ public class BatchActionServiceImpl implements BatchActionService {
|
|||
.onError(error -> log.error(
|
||||
"Failed to mark batch action as finished: {}",
|
||||
this.batchAction, error));
|
||||
|
||||
BatchActionServiceImpl.this.batchActionDAO.byPK(this.batchAction.id)
|
||||
.flatMap(BatchActionServiceImpl.this.userActivityLogDAO::logFinished)
|
||||
.onError(error -> log.error("Failed to put audit log for batch action finish: ", error));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.api.authorization.PrivilegeType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
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.ConfigurationNodeDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@WebServiceProfile
|
||||
public class ExamConfigResetToTemplate implements BatchActionExec {
|
||||
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ExamConfigService sebExamConfigService;
|
||||
private final ExamConfigUpdateService examConfigUpdateService;
|
||||
private final AuthorizationService authorizationService;
|
||||
|
||||
public ExamConfigResetToTemplate(
|
||||
final ConfigurationNodeDAO configurationNodeDAO,
|
||||
final ExamConfigService sebExamConfigService,
|
||||
final ExamConfigUpdateService examConfigUpdateService,
|
||||
final AuthorizationService authorizationService) {
|
||||
|
||||
this.configurationNodeDAO = configurationNodeDAO;
|
||||
this.sebExamConfigService = sebExamConfigService;
|
||||
this.examConfigUpdateService = examConfigUpdateService;
|
||||
this.authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchActionType actionType() {
|
||||
return BatchActionType.EXAM_CONFIG_REST_TEMPLATE_SETTINGS;
|
||||
}
|
||||
|
||||
@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.configurationNodeDAO
|
||||
.byModelId(modelId)
|
||||
.flatMap(node -> this.authorizationService.check(PrivilegeType.MODIFY, node))
|
||||
.map(this::checkConsistency)
|
||||
.flatMap(this.sebExamConfigService::resetToTemplateSettings)
|
||||
.map(node -> {
|
||||
this.examConfigUpdateService
|
||||
.processExamConfigurationChange(node.id)
|
||||
.getOrThrow();
|
||||
return node;
|
||||
})
|
||||
.map(Entity::getEntityKey);
|
||||
|
||||
}
|
||||
|
||||
private ConfigurationNode checkConsistency(final ConfigurationNode configurationNode) {
|
||||
return configurationNode;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,36 +21,31 @@ import ch.ethz.seb.sebserver.gbl.model.Entity;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
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.ConfigurationNodeDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
|
||||
import io.micrometer.core.instrument.util.StringUtils;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@WebServiceProfile
|
||||
public class SingleExamConfigStateChange implements BatchActionExec {
|
||||
public class ExamConfigStateChange implements BatchActionExec {
|
||||
|
||||
private final ExamConfigService sebExamConfigService;
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final AuthorizationService authorizationService;
|
||||
private final UserDAO userDAO;
|
||||
|
||||
public SingleExamConfigStateChange(
|
||||
public ExamConfigStateChange(
|
||||
final ExamConfigService sebExamConfigService,
|
||||
final ConfigurationNodeDAO configurationNodeDAO,
|
||||
final AuthorizationService authorizationService,
|
||||
final UserDAO userDAO) {
|
||||
final AuthorizationService authorizationService) {
|
||||
|
||||
this.sebExamConfigService = sebExamConfigService;
|
||||
this.configurationNodeDAO = configurationNodeDAO;
|
||||
this.authorizationService = authorizationService;
|
||||
this.userDAO = userDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,13 +66,9 @@ public class SingleExamConfigStateChange implements BatchActionExec {
|
|||
@Override
|
||||
public Result<EntityKey> doSingleAction(final String modelId, final BatchAction batchAction) {
|
||||
|
||||
final UserInfo user = this.userDAO
|
||||
.byModelId(batchAction.ownerId)
|
||||
.getOrThrow();
|
||||
|
||||
return this.configurationNodeDAO
|
||||
.byModelId(modelId)
|
||||
.map(node -> this.authorizationService.check(PrivilegeType.MODIFY, user, node))
|
||||
.flatMap(node -> this.authorizationService.check(PrivilegeType.MODIFY, node))
|
||||
.map(node -> new ConfigurationNode(
|
||||
node.id, null, null, null, null, null, null,
|
||||
getTargetState(batchAction.attributes)))
|
|
@ -77,6 +77,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 FINISHED
|
||||
*
|
||||
* @param entity the Entity
|
||||
* @return Result of the Entity or referring to an Error if happened */
|
||||
<E extends Entity> Result<E> logFinished(E entity);
|
||||
|
||||
/** Create a user activity log entry for the current user of activity type DELETE
|
||||
*
|
||||
* @param entity the Entity
|
||||
|
|
|
@ -160,6 +160,11 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
return log(UserLogActivityType.MODIFY, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Entity> Result<E> logFinished(final E entity) {
|
||||
return log(UserLogActivityType.FINISHED, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <E extends Entity> Result<E> logDelete(final E entity) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.io.OutputStream;
|
|||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -414,6 +415,18 @@ public class ExamConfigServiceImpl implements ExamConfigService {
|
|||
"The Type of ConfigurationNode cannot change after creation");
|
||||
}
|
||||
|
||||
// if configuration is in use, "Ready to Use" is not possible
|
||||
if (configurationNode.status == ConfigurationStatus.READY_TO_USE) {
|
||||
if (!this.examConfigurationMapDAO
|
||||
.getExamIdsForConfigNodeId(configurationNode.id)
|
||||
.getOr(Collections.emptyList())
|
||||
.isEmpty()) {
|
||||
throw new APIMessageException(
|
||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.of("Exam configuration has references to at least one exam."));
|
||||
}
|
||||
}
|
||||
|
||||
// if changing to archived check possibility
|
||||
if (configurationNode.status == ConfigurationStatus.ARCHIVED) {
|
||||
if (existingNode.status != ConfigurationStatus.ARCHIVED) {
|
||||
|
|
|
@ -49,6 +49,7 @@ sebserver.overall.types.activityType.ACTIVATE=Activate
|
|||
sebserver.overall.types.activityType.DELETE=Delete
|
||||
sebserver.overall.types.activityType.LOGIN=Login
|
||||
sebserver.overall.types.activityType.LOGOUT=Logout
|
||||
sebserver.overall.types.activityType.FINISHED=Finished
|
||||
|
||||
sebserver.overall.types.entityType.CONFIGURATION_ATTRIBUTE=Configuration Attribute
|
||||
sebserver.overall.types.entityType.CONFIGURATION_VALUE=Configuration Value
|
||||
|
@ -70,6 +71,7 @@ sebserver.overall.types.entityType.EXAM_SEB_RESTRICTION=SEB Exam Restriction
|
|||
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.activity.title.serveradmin=SEB Server Administration
|
||||
sebserver.overall.activity.title.sebconfig=Configurations
|
||||
|
@ -814,13 +816,16 @@ sebserver.examconfig.list.action.no.modify.privilege=No Access: An Exam Configur
|
|||
sebserver.examconfig.action.list.new=Add Exam Configuration
|
||||
sebserver.examconfig.action.list.view=View Exam Configuration
|
||||
sebserver.examconfig.list.action.statechange=State Change
|
||||
sebserver.examconfig.list.batch.statechange.title=Bulk State Change
|
||||
sebserver.examconfig.list.batch.statechange.title=State Change All
|
||||
sebserver.examconfig.list.batch.action.statechange=Change States
|
||||
sebserver.examconfig.list.batch.progress=Bulk State Change in Progress: {0}%
|
||||
sebserver.examconfig.list.batch.finished=Bulk State Change finished. Successful: {0}.Failed: {1}
|
||||
sebserver.examconfig.list.batch.action.statechange.info=This action changes all selected exam configurations to a defined target state.<br/>Please note that this will be done only for those that can be changed to the defined target state in regard to its current state.
|
||||
sebserver.examconfig.list.batch.action.status=Target Status
|
||||
|
||||
sebserver.examconfig.list.action.reset=Reset To Template Settings
|
||||
sebserver.examconfig.list.batch.reset.title=Reset To Template Settings
|
||||
sebserver.examconfig.list.batch.action.reset=Reset All
|
||||
sebserver.examconfig.list.batch.action.reset.info=This action process a reset of the SEB settings to its template settings for all selected configurations.<br/>Please note this will be done only for those that has an origin template and are not in state "Used". <br/>Please note also that this action tries to directly publish the changes and if not possible (active SEB client connections) it will report an error even if the settings has been reseted.
|
||||
|
||||
sebserver.examconfig.action.list.modify.properties=Edit Exam Configuration
|
||||
sebserver.examconfig.action.delete=Delete Exam Configuration
|
||||
|
|
|
@ -15,6 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -248,11 +249,12 @@ public abstract class AdministrationAPIIntegrationTester {
|
|||
}
|
||||
|
||||
protected String getOrderedUUIDs(final Collection<? extends Entity> list) {
|
||||
return list
|
||||
final List<String> l = list
|
||||
.stream()
|
||||
.map(userInfo -> userInfo.getModelId())
|
||||
.collect(Collectors.toList())
|
||||
.toString();
|
||||
.collect(Collectors.toList());
|
||||
l.sort((s1, s2) -> s1.compareTo(s2));
|
||||
return l.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -268,7 +268,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
|
|||
assertTrue(userInfos.numberOfPages == 1);
|
||||
assertNotNull(userInfos.content);
|
||||
assertTrue(userInfos.content.size() == 3);
|
||||
assertEquals("[user5, user2, user1]", getOrderedUUIDs(userInfos.content));
|
||||
assertEquals("[user1, user2, user5]", getOrderedUUIDs(userInfos.content));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -347,7 +347,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
|
|||
assertTrue(userInfos.numberOfPages == 2);
|
||||
assertNotNull(userInfos.content);
|
||||
assertTrue(userInfos.content.size() == 3);
|
||||
assertEquals("[user7, user6, user4]", getOrderedUUIDs(userInfos.content));
|
||||
assertEquals("[user3, user4, user6]", getOrderedUUIDs(userInfos.content));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue