diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 066619d9..100c975e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -75,7 +75,7 @@ public final class API { public static final String LIST_PATH_SEGMENT = "/list"; public static final String ACTIVE_PATH_SEGMENT = "/active"; - + public static final String TOGGLE_ACTIVITY_PATH_SEGMENT = "/toggle-activity"; public static final String INACTIVE_PATH_SEGMENT = "/inactive"; public static final String DEPENDENCY_PATH_SEGMENT = "/dependency"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java index 28644841..52d688a5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java @@ -8,16 +8,13 @@ package ch.ethz.seb.sebserver.gui.service.page; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; @@ -62,6 +59,9 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; * with forms and tables as well as dealing with page actions */ public interface PageService { + LocTextKey MESSAGE_NO_MULTISELECTION = + new LocTextKey("sebserver.overall.action.toomanyselection"); + enum FormTooltipMode { RIGHT, INPUT, @@ -154,31 +154,34 @@ public interface PageService { return this.activationToggleActionFunction(table, noSelectionText, null); } - /** Get a message supplier to notify deactivation dependencies to the user for all given entities - * - * @param entities Set of entities to collect the dependencies for - * @return a message supplier to notify deactivation dependencies to the user */ - Supplier confirmDeactivation(final Set entities); +// /** Get a message supplier to notify deactivation dependencies to the user for all given entities +// * +// * @param entities Set of entities to collect the dependencies for +// * @return a message supplier to notify deactivation dependencies to the user */ +// Supplier confirmDeactivation(final Set keys); /** Get a message supplier to notify deactivation dependencies to the user for given entity * * @param entity the entity instance * @return a message supplier to notify deactivation dependencies to the user */ - default Supplier confirmDeactivation(final T entity) { - return confirmDeactivation(new HashSet<>(Arrays.asList(entity))); - } + Supplier confirmDeactivation(final T entity); /** Get a message supplier to notify deactivation dependencies to the user for given entity table selection * * @param table the entity table * @return a message supplier to notify deactivation dependencies to the user */ default Supplier confirmDeactivation(final EntityTable table) { - return () -> confirmDeactivation(table - .getPageSelectionData() - .stream() - .filter(entity -> entity.isActive()) // NOTE: Activatable::isActive leads to an error here!? - .collect(Collectors.toSet())) - .get(); + return () -> { + final List multiSelection = table.getMultiSelection(); + if (multiSelection.size() > 1) { + throw new PageMessageException(MESSAGE_NO_MULTISELECTION); + } + final T entity = table.getSingleSelectedROWData(); + if (!entity.isActive()) { + return null; + } + return confirmDeactivation(entity).get(); + }; } /** Use this to get an action activation publisher that processes the action activation. diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java index 76cd4929..f86826bb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageAction.java @@ -167,24 +167,25 @@ public final class PageAction { void applyAction(final Consumer> callback) { if (this.confirm != null) { - // if selection is needed, check selection fist, before confirm dialog - if (this.selectionSupplier != null) { - try { + try { + // if selection is needed, check selection fist, before confirm dialog + if (this.selectionSupplier != null) { getMultiSelection(); - } catch (final PageMessageException pme) { - PageAction.this.pageContext.publishPageMessage(pme); - return; - } - } - final LocTextKey confirmMessage = this.confirm.apply(this); - if (confirmMessage != null) { - this.pageContext.applyConfirmDialog(confirmMessage, - confirm -> callback.accept((confirm) - ? exec().onError(error -> this.pageContext.notifyUnexpectedError(error)) - : Result.ofRuntimeError("Confirm denied"))); - } else { - callback.accept(exec()); + } + + final LocTextKey confirmMessage = this.confirm.apply(this); + if (confirmMessage != null) { + this.pageContext.applyConfirmDialog(confirmMessage, + confirm -> callback.accept((confirm) + ? exec().onError(error -> this.pageContext.notifyUnexpectedError(error)) + : Result.ofRuntimeError("Confirm denied"))); + } else { + callback.accept(exec()); + } + } catch (final PageMessageException pme) { + PageAction.this.pageContext.publishPageMessage(pme); + return; } } else { callback.accept(exec()); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java index 3164a40e..3c9fe53f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java @@ -17,7 +17,6 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Stream; import javax.servlet.http.HttpSession; @@ -70,13 +69,11 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @GuiProfile public class PageServiceImpl implements PageService { - private static final LocTextKey CONFIRM_DEACTIVATION_NO_DEP_KEY = - new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies"); - - private static final String CONFIRM_DEACTIVATION_KEY = "sebserver.dialog.confirm.deactivation"; - private static final Logger log = LoggerFactory.getLogger(PageServiceImpl.class); + private static final LocTextKey CONFIRM_DEACTIVATION_NO_DEP_KEY = + new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies"); + private static final String CONFIRM_DEACTIVATION_KEY = "sebserver.dialog.confirm.deactivation"; private static final LocTextKey MSG_GO_AWAY_FROM_EDIT = new LocTextKey("sebserver.overall.action.goAwayFromEditPageConfirm"); @@ -168,7 +165,6 @@ public class PageServiceImpl implements PageService { @Override public FormTooltipMode getFormTooltipMode() { - // TODO make this configurable return FormTooltipMode.INPUT; } @@ -220,31 +216,19 @@ public class PageServiceImpl implements PageService { } @Override - public Supplier confirmDeactivation(final Set entities) { + public Supplier confirmDeactivation(final T entity) { final RestService restService = this.resourceService.getRestService(); return () -> { - if (entities == null || entities.isEmpty()) { - return null; - } - try { - final int dependencies = (int) entities.stream() - .flatMap(entity -> { - final RestCall>.RestCallBuilder builder = - restService.> getBuilder( - entity.entityType(), - CallType.GET_DEPENDENCIES); + final int dependencies = restService.> getBuilder( + entity.entityType(), + CallType.GET_DEPENDENCIES) + .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId())) + .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name()) + .call() + .getOrThrow() + .size(); - if (builder != null) { - return builder - .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId())) - .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name()) - .call() - .getOrThrow().stream(); - } else { - return Stream.empty(); - } - }).count(); if (dependencies > 0) { return new LocTextKey(CONFIRM_DEACTIVATION_KEY, String.valueOf(dependencies)); } else { @@ -265,43 +249,47 @@ public class PageServiceImpl implements PageService { final Function testBeforeActivation) { return action -> { - final Set selectedROWData = table.getPageSelectionData(); - if (selectedROWData == null || selectedROWData.isEmpty()) { + final List multiSelection = table.getMultiSelection(); + if (multiSelection == null || multiSelection.isEmpty()) { throw new PageMessageException(noSelectionText); } + if (multiSelection.size() > 1) { + throw new PageMessageException(MESSAGE_NO_MULTISELECTION); + } final RestService restService = this.resourceService.getRestService(); final EntityType entityType = table.getEntityType(); - final Collection errors = new ArrayList<>(); - for (final T entity : selectedROWData) { + final T singleSelection = table.getSingleSelectedROWData(); + if (singleSelection == null) { + throw new PageMessageException(noSelectionText); + } - if (!entity.isActive()) { - final RestCall.RestCallBuilder restCallBuilder = restService. getBuilder( - entityType, - CallType.ACTIVATION_ACTIVATE) - .withURIVariable(API.PARAM_MODEL_ID, entity.getModelId()); - if (testBeforeActivation != null) { - try { - action.withEntityKey(entity.getEntityKey()); - testBeforeActivation.apply(action); - restCallBuilder - .call() - .onError(errors::add); - } catch (final Exception e) { - errors.add(e); - } - } else { + if (!singleSelection.isActive()) { + final RestCall.RestCallBuilder restCallBuilder = restService. getBuilder( + entityType, + CallType.ACTIVATION_ACTIVATE) + .withURIVariable(API.PARAM_MODEL_ID, singleSelection.getModelId()); + if (testBeforeActivation != null) { + try { + action.withEntityKey(singleSelection.getEntityKey()); + testBeforeActivation.apply(action); restCallBuilder .call() .onError(errors::add); + } catch (final Exception e) { + errors.add(e); } } else { - restService. getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE) - .withURIVariable(API.PARAM_MODEL_ID, entity.getModelId()) + restCallBuilder .call() .onError(errors::add); } + } else { + restService. getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE) + .withURIVariable(API.PARAM_MODEL_ID, singleSelection.getModelId()) + .call() + .onError(errors::add); } if (!errors.isEmpty()) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java index 004a05d4..da86273c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java @@ -396,8 +396,7 @@ public class EntityTable { return getRowData(selection[0]); } - @Deprecated - public Set getPageSelectionData() { + private Set getPageSelectionData() { final TableItem[] selection = this.table.getSelection(); if (selection == null || selection.length == 0) { return Collections.emptySet(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigStateChange.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/SingleExamConfigStateChange.java similarity index 94% rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigStateChange.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/SingleExamConfigStateChange.java index cf6e4817..b4f07cc9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/ExamConfigStateChange.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/SingleExamConfigStateChange.java @@ -34,14 +34,14 @@ import io.micrometer.core.instrument.util.StringUtils; @Lazy @Component @WebServiceProfile -public class ExamConfigStateChange implements BatchActionExec { +public class SingleExamConfigStateChange implements BatchActionExec { private final ExamConfigService sebExamConfigService; private final ConfigurationNodeDAO configurationNodeDAO; private final AuthorizationService authorizationService; private final UserDAO userDAO; - public ExamConfigStateChange( + public SingleExamConfigStateChange( final ExamConfigService sebExamConfigService, final ConfigurationNodeDAO configurationNodeDAO, final AuthorizationService authorizationService, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java index ca941abe..0878aeb0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java @@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestParam; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityName; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; @@ -38,7 +39,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe * * @param The concrete Entity domain-model type used on all GET, PUT * @param The concrete Entity domain-model type used for POST methods (new) */ -public abstract class ActivatableEntityController +public abstract class ActivatableEntityController extends EntityController { public ActivatableEntityController( @@ -57,7 +58,6 @@ public abstract class ActivatableEntityController setActive(final String modelId, final boolean active) { + @RequestMapping( + path = API.TOGGLE_ACTIVITY_PATH_SEGMENT, + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public EntityProcessingReport toggleActivity( + @RequestParam(name = API.PARAM_MODEL_ID_LIST, required = true) final String ids) { + // TODO + throw new UnsupportedOperationException(); + +// final EntityType entityType = this.entityDAO.entityType(); +// final List entities = new ArrayList<>(); +// final Set errors = new HashSet<>(); +// final BulkAction bulkAction = new BulkAction( +// (active) ? BulkActionType.ACTIVATE : BulkActionType.DEACTIVATE, +// entityType, +// entities); +// +// Arrays.asList(StringUtils.split(ids, Constants.LIST_SEPARATOR)) +// .stream() +// .forEach(modelId -> { +// this.entityDAO +// .byModelId(modelId) +// .flatMap(this.authorization::checkWrite) +// .flatMap(entity -> validForActivation(entity, active)) +// .map(Entity::getEntityKey) +// .onSuccess(entities::add) +// .onError(error -> errors.add(new ErrorEntry( +// new EntityKey(modelId, entityType), +// APIMessage.ErrorMessage.UNAUTHORIZED.of(error)))); +// }); +// +// return this.bulkActionService +// .createReport(bulkAction) +// .map(report -> { +// if (!errors.isEmpty()) { +// errors.addAll(report.errors); +// return new EntityProcessingReport(report.source, report.results, errors, report.bulkActionType); +// } else { +// return report; +// } +// }); + } + + private Result setActiveSingle(final String modelId, final boolean active) { final EntityType entityType = this.entityDAO.entityType(); - return this.entityDAO.byModelId(modelId) + return this.entityDAO + .byModelId(modelId) .flatMap(this.authorization::checkWrite) - .flatMap(this::validForActivation) + .flatMap(entity -> validForActivation(entity, active)) .flatMap(entity -> { final Result createReport = this.bulkActionService.createReport(new BulkAction( @@ -151,8 +196,12 @@ public abstract class ActivatableEntityController validForActivation(final T entity) { - return Result.of(entity); + protected Result validForActivation(final T entity, final boolean activation) { + if ((entity.isActive() && !activation) || (!entity.isActive() && activation)) { + return Result.of(entity); + } else { + throw new IllegalArgumentException("Activation argument mismatch."); + } } } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 0a447fec..43c3e1c1 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -22,6 +22,7 @@ sebserver.overall.action.close=Close sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leave this page? Unsaved data will be lost. sebserver.overall.action.category.varia= sebserver.overall.action.category.filter= +sebserver.overall.action.toomanyselection=There is only one selection allowed for this action. Please select only one entry sebserver.overall.action.showPassword.tooltip=Show / hide password in plain text