SEBSERV-160 adapt activation actions

This commit is contained in:
anhefti 2022-04-11 14:33:56 +02:00
parent 9d29a48151
commit 8b856edb70
8 changed files with 138 additions and 97 deletions

View file

@ -75,7 +75,7 @@ public final class API {
public static final String LIST_PATH_SEGMENT = "/list"; public static final String LIST_PATH_SEGMENT = "/list";
public static final String ACTIVE_PATH_SEGMENT = "/active"; 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 INACTIVE_PATH_SEGMENT = "/inactive";
public static final String DEPENDENCY_PATH_SEGMENT = "/dependency"; public static final String DEPENDENCY_PATH_SEGMENT = "/dependency";

View file

@ -8,16 +8,13 @@
package ch.ethz.seb.sebserver.gui.service.page; package ch.ethz.seb.sebserver.gui.service.page;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite; 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 */ * with forms and tables as well as dealing with page actions */
public interface PageService { public interface PageService {
LocTextKey MESSAGE_NO_MULTISELECTION =
new LocTextKey("sebserver.overall.action.toomanyselection");
enum FormTooltipMode { enum FormTooltipMode {
RIGHT, RIGHT,
INPUT, INPUT,
@ -154,31 +154,34 @@ public interface PageService {
return this.activationToggleActionFunction(table, noSelectionText, null); return this.activationToggleActionFunction(table, noSelectionText, null);
} }
/** Get a message supplier to notify deactivation dependencies to the user for all given 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 // * @param entities Set of entities to collect the dependencies for
* @return a message supplier to notify deactivation dependencies to the user */ // * @return a message supplier to notify deactivation dependencies to the user */
<T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final Set<? extends T> entities); // Supplier<LocTextKey> confirmDeactivation(final Set<EntityKey> keys);
/** Get a message supplier to notify deactivation dependencies to the user for given entity /** Get a message supplier to notify deactivation dependencies to the user for given entity
* *
* @param entity the entity instance * @param entity the entity instance
* @return a message supplier to notify deactivation dependencies to the user */ * @return a message supplier to notify deactivation dependencies to the user */
default <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final T entity) { <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final T entity);
return confirmDeactivation(new HashSet<>(Arrays.asList(entity)));
}
/** Get a message supplier to notify deactivation dependencies to the user for given entity table selection /** Get a message supplier to notify deactivation dependencies to the user for given entity table selection
* *
* @param table the entity table * @param table the entity table
* @return a message supplier to notify deactivation dependencies to the user */ * @return a message supplier to notify deactivation dependencies to the user */
default <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final EntityTable<T> table) { default <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final EntityTable<T> table) {
return () -> confirmDeactivation(table return () -> {
.getPageSelectionData() final List<EntityKey> multiSelection = table.getMultiSelection();
.stream() if (multiSelection.size() > 1) {
.filter(entity -> entity.isActive()) // NOTE: Activatable::isActive leads to an error here!? throw new PageMessageException(MESSAGE_NO_MULTISELECTION);
.collect(Collectors.toSet())) }
.get(); 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. /** Use this to get an action activation publisher that processes the action activation.

View file

@ -167,24 +167,25 @@ public final class PageAction {
void applyAction(final Consumer<Result<PageAction>> callback) { void applyAction(final Consumer<Result<PageAction>> callback) {
if (this.confirm != null) { if (this.confirm != null) {
// if selection is needed, check selection fist, before confirm dialog try {
if (this.selectionSupplier != null) { // if selection is needed, check selection fist, before confirm dialog
try { if (this.selectionSupplier != null) {
getMultiSelection(); 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, final LocTextKey confirmMessage = this.confirm.apply(this);
confirm -> callback.accept((confirm) if (confirmMessage != null) {
? exec().onError(error -> this.pageContext.notifyUnexpectedError(error)) this.pageContext.applyConfirmDialog(confirmMessage,
: Result.ofRuntimeError("Confirm denied"))); confirm -> callback.accept((confirm)
} else { ? exec().onError(error -> this.pageContext.notifyUnexpectedError(error))
callback.accept(exec()); : Result.ofRuntimeError("Confirm denied")));
} else {
callback.accept(exec());
}
} catch (final PageMessageException pme) {
PageAction.this.pageContext.publishPageMessage(pme);
return;
} }
} else { } else {
callback.accept(exec()); callback.accept(exec());

View file

@ -17,7 +17,6 @@ import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
@ -70,13 +69,11 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@GuiProfile @GuiProfile
public class PageServiceImpl implements PageService { 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 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 = private static final LocTextKey MSG_GO_AWAY_FROM_EDIT =
new LocTextKey("sebserver.overall.action.goAwayFromEditPageConfirm"); new LocTextKey("sebserver.overall.action.goAwayFromEditPageConfirm");
@ -168,7 +165,6 @@ public class PageServiceImpl implements PageService {
@Override @Override
public FormTooltipMode getFormTooltipMode() { public FormTooltipMode getFormTooltipMode() {
// TODO make this configurable
return FormTooltipMode.INPUT; return FormTooltipMode.INPUT;
} }
@ -220,31 +216,19 @@ public class PageServiceImpl implements PageService {
} }
@Override @Override
public <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final Set<? extends T> entities) { public <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final T entity) {
final RestService restService = this.resourceService.getRestService(); final RestService restService = this.resourceService.getRestService();
return () -> { return () -> {
if (entities == null || entities.isEmpty()) {
return null;
}
try { try {
final int dependencies = (int) entities.stream() final int dependencies = restService.<Set<EntityKey>> getBuilder(
.flatMap(entity -> { entity.entityType(),
final RestCall<Set<EntityKey>>.RestCallBuilder builder = CallType.GET_DEPENDENCIES)
restService.<Set<EntityKey>> getBuilder( .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId()))
entity.entityType(), .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name())
CallType.GET_DEPENDENCIES); .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) { if (dependencies > 0) {
return new LocTextKey(CONFIRM_DEACTIVATION_KEY, String.valueOf(dependencies)); return new LocTextKey(CONFIRM_DEACTIVATION_KEY, String.valueOf(dependencies));
} else { } else {
@ -265,43 +249,47 @@ public class PageServiceImpl implements PageService {
final Function<PageAction, PageAction> testBeforeActivation) { final Function<PageAction, PageAction> testBeforeActivation) {
return action -> { return action -> {
final Set<T> selectedROWData = table.getPageSelectionData(); final List<EntityKey> multiSelection = table.getMultiSelection();
if (selectedROWData == null || selectedROWData.isEmpty()) { if (multiSelection == null || multiSelection.isEmpty()) {
throw new PageMessageException(noSelectionText); throw new PageMessageException(noSelectionText);
} }
if (multiSelection.size() > 1) {
throw new PageMessageException(MESSAGE_NO_MULTISELECTION);
}
final RestService restService = this.resourceService.getRestService(); final RestService restService = this.resourceService.getRestService();
final EntityType entityType = table.getEntityType(); final EntityType entityType = table.getEntityType();
final Collection<Exception> errors = new ArrayList<>(); final Collection<Exception> errors = new ArrayList<>();
for (final T entity : selectedROWData) { final T singleSelection = table.getSingleSelectedROWData();
if (singleSelection == null) {
throw new PageMessageException(noSelectionText);
}
if (!entity.isActive()) { if (!singleSelection.isActive()) {
final RestCall<T>.RestCallBuilder restCallBuilder = restService.<T> getBuilder( final RestCall<T>.RestCallBuilder restCallBuilder = restService.<T> getBuilder(
entityType, entityType,
CallType.ACTIVATION_ACTIVATE) CallType.ACTIVATION_ACTIVATE)
.withURIVariable(API.PARAM_MODEL_ID, entity.getModelId()); .withURIVariable(API.PARAM_MODEL_ID, singleSelection.getModelId());
if (testBeforeActivation != null) { if (testBeforeActivation != null) {
try { try {
action.withEntityKey(entity.getEntityKey()); action.withEntityKey(singleSelection.getEntityKey());
testBeforeActivation.apply(action); testBeforeActivation.apply(action);
restCallBuilder
.call()
.onError(errors::add);
} catch (final Exception e) {
errors.add(e);
}
} else {
restCallBuilder restCallBuilder
.call() .call()
.onError(errors::add); .onError(errors::add);
} catch (final Exception e) {
errors.add(e);
} }
} else { } else {
restService.<T> getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE) restCallBuilder
.withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
.call() .call()
.onError(errors::add); .onError(errors::add);
} }
} else {
restService.<T> getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE)
.withURIVariable(API.PARAM_MODEL_ID, singleSelection.getModelId())
.call()
.onError(errors::add);
} }
if (!errors.isEmpty()) { if (!errors.isEmpty()) {

View file

@ -396,8 +396,7 @@ public class EntityTable<ROW extends ModelIdAware> {
return getRowData(selection[0]); return getRowData(selection[0]);
} }
@Deprecated private Set<ROW> getPageSelectionData() {
public Set<ROW> getPageSelectionData() {
final TableItem[] selection = this.table.getSelection(); final TableItem[] selection = this.table.getSelection();
if (selection == null || selection.length == 0) { if (selection == null || selection.length == 0) {
return Collections.emptySet(); return Collections.emptySet();

View file

@ -34,14 +34,14 @@ import io.micrometer.core.instrument.util.StringUtils;
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
public class ExamConfigStateChange implements BatchActionExec { public class SingleExamConfigStateChange implements BatchActionExec {
private final ExamConfigService sebExamConfigService; private final ExamConfigService sebExamConfigService;
private final ConfigurationNodeDAO configurationNodeDAO; private final ConfigurationNodeDAO configurationNodeDAO;
private final AuthorizationService authorizationService; private final AuthorizationService authorizationService;
private final UserDAO userDAO; private final UserDAO userDAO;
public ExamConfigStateChange( public SingleExamConfigStateChange(
final ExamConfigService sebExamConfigService, final ExamConfigService sebExamConfigService,
final ConfigurationNodeDAO configurationNodeDAO, final ConfigurationNodeDAO configurationNodeDAO,
final AuthorizationService authorizationService, final AuthorizationService authorizationService,

View file

@ -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;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType; 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.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityName; import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
@ -38,7 +39,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
* *
* @param <T> The concrete Entity domain-model type used on all GET, PUT * @param <T> The concrete Entity domain-model type used on all GET, PUT
* @param <M> The concrete Entity domain-model type used for POST methods (new) */ * @param <M> The concrete Entity domain-model type used for POST methods (new) */
public abstract class ActivatableEntityController<T extends GrantEntity, M extends GrantEntity> public abstract class ActivatableEntityController<T extends GrantEntity & Activatable, M extends GrantEntity>
extends EntityController<T, M> { extends EntityController<T, M> {
public ActivatableEntityController( public ActivatableEntityController(
@ -57,7 +58,6 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
beanValidationService); beanValidationService);
} }
// TODO use also the getAll method
@RequestMapping( @RequestMapping(
path = API.ACTIVE_PATH_SEGMENT, path = API.ACTIVE_PATH_SEGMENT,
method = RequestMethod.GET, method = RequestMethod.GET,
@ -120,7 +120,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
public EntityProcessingReport activate(@PathVariable final String modelId) { public EntityProcessingReport activate(@PathVariable final String modelId) {
return setActive(modelId, true) return setActiveSingle(modelId, true)
.getOrThrow(); .getOrThrow();
} }
@ -130,16 +130,61 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
public EntityProcessingReport deactivate(@PathVariable final String modelId) { public EntityProcessingReport deactivate(@PathVariable final String modelId) {
return setActive(modelId, false) return setActiveSingle(modelId, false)
.getOrThrow(); .getOrThrow();
} }
private Result<EntityProcessingReport> 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<EntityKey> entities = new ArrayList<>();
// final Set<ErrorEntry> 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<EntityProcessingReport> setActiveSingle(final String modelId, final boolean active) {
final EntityType entityType = this.entityDAO.entityType(); final EntityType entityType = this.entityDAO.entityType();
return this.entityDAO.byModelId(modelId) return this.entityDAO
.byModelId(modelId)
.flatMap(this.authorization::checkWrite) .flatMap(this.authorization::checkWrite)
.flatMap(this::validForActivation) .flatMap(entity -> validForActivation(entity, active))
.flatMap(entity -> { .flatMap(entity -> {
final Result<EntityProcessingReport> createReport = final Result<EntityProcessingReport> createReport =
this.bulkActionService.createReport(new BulkAction( this.bulkActionService.createReport(new BulkAction(
@ -151,8 +196,12 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
}); });
} }
protected Result<T> validForActivation(final T entity) { protected Result<T> validForActivation(final T entity, final boolean activation) {
return Result.of(entity); if ((entity.isActive() && !activation) || (!entity.isActive() && activation)) {
return Result.of(entity);
} else {
throw new IllegalArgumentException("Activation argument mismatch.");
}
} }
} }

View file

@ -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.goAwayFromEditPageConfirm=Are you sure you want to leave this page? Unsaved data will be lost.
sebserver.overall.action.category.varia= sebserver.overall.action.category.varia=
sebserver.overall.action.category.filter= 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 sebserver.overall.action.showPassword.tooltip=Show / hide password in plain text