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 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";

View file

@ -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 */
<T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final Set<? extends T> 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<LocTextKey> confirmDeactivation(final Set<EntityKey> 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 <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final T entity) {
return confirmDeactivation(new HashSet<>(Arrays.asList(entity)));
}
<T extends Entity & Activatable> Supplier<LocTextKey> 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 <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final EntityTable<T> 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<EntityKey> 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.

View file

@ -167,24 +167,25 @@ public final class PageAction {
void applyAction(final Consumer<Result<PageAction>> 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());

View file

@ -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 <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();
return () -> {
if (entities == null || entities.isEmpty()) {
return null;
}
try {
final int dependencies = (int) entities.stream()
.flatMap(entity -> {
final RestCall<Set<EntityKey>>.RestCallBuilder builder =
restService.<Set<EntityKey>> getBuilder(
entity.entityType(),
CallType.GET_DEPENDENCIES);
final int dependencies = restService.<Set<EntityKey>> 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<PageAction, PageAction> testBeforeActivation) {
return action -> {
final Set<T> selectedROWData = table.getPageSelectionData();
if (selectedROWData == null || selectedROWData.isEmpty()) {
final List<EntityKey> 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<Exception> 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<T>.RestCallBuilder restCallBuilder = restService.<T> 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<T>.RestCallBuilder restCallBuilder = restService.<T> 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.<T> getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE)
.withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
restCallBuilder
.call()
.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()) {

View file

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

View file

@ -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,

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.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 <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) */
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> {
public ActivatableEntityController(
@ -57,7 +58,6 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
beanValidationService);
}
// TODO use also the getAll method
@RequestMapping(
path = API.ACTIVE_PATH_SEGMENT,
method = RequestMethod.GET,
@ -120,7 +120,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public EntityProcessingReport activate(@PathVariable final String modelId) {
return setActive(modelId, true)
return setActiveSingle(modelId, true)
.getOrThrow();
}
@ -130,16 +130,61 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public EntityProcessingReport deactivate(@PathVariable final String modelId) {
return setActive(modelId, false)
return setActiveSingle(modelId, false)
.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();
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<EntityProcessingReport> createReport =
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) {
return Result.of(entity);
protected Result<T> 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.");
}
}
}

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.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