SEBSERV-133 added deletion for user activity logs for SEB server admins

This commit is contained in:
anhefti 2020-07-29 11:40:12 +02:00
parent 9438206c9d
commit a2d2ca6751
17 changed files with 439 additions and 28 deletions

View file

@ -323,28 +323,32 @@ public class ExamForm implements TemplateComposer {
.withInputSpan(3)
.withEmptyCellSeparation(false))
.addField(FormBuilder.text(
Domain.EXAM.ATTR_EXTERNAL_ID,
FORM_QUIZ_ID_TEXT_KEY,
exam.externalId)
.readonly(true)
.withEmptyCellSeparation(false))
.addField(FormBuilder.text(
QuizData.QUIZ_ATTR_START_URL,
FORM_QUIZ_URL_TEXT_KEY,
exam.startURL)
.readonly(true)
.withInputSpan(7))
.withInputSpan(7)
.withEmptyCellSeparation(false))
.addField(FormBuilder.text(
QuizData.QUIZ_ATTR_DESCRIPTION,
FORM_DESCRIPTION_TEXT_KEY,
exam.description)
.asHTML()
.asHTML(50)
.readonly(true)
.withInputSpan(6)
.withEmptyCellSeparation(false))
.addField(FormBuilder.text(
Domain.EXAM.ATTR_EXTERNAL_ID,
FORM_QUIZ_ID_TEXT_KEY,
exam.externalId)
.readonly(true)
.withLabelSpan(2)
.withInputSpan(6)
.withEmptyCellSeparation(false))
.addField(FormBuilder.text(
Domain.EXAM.ATTR_STATUS + "_display",
FORM_STATUS_TEXT_KEY,
@ -352,7 +356,8 @@ public class ExamForm implements TemplateComposer {
.readonly(true)
.withLabelSpan(2)
.withInputSpan(4)
.withEmptyCellSpan(1))
.withEmptyCellSeparation(false))
.addField(FormBuilder.singleSelection(
Domain.EXAM.ATTR_TYPE,
FORM_TYPE_TEXT_KEY,
@ -400,11 +405,6 @@ public class ExamForm implements TemplateComposer {
.withExec(this.cancelModifyFunction())
.publishIf(() -> !readonly)
.newAction(ActionDefinition.EXAM_DELETE)
.withEntityKey(entityKey)
.withExec(this.examDeletePopup.deleteWizardFunction(pageContext))
.publishIf(() -> writeGrant && readonly)
.newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS)
.withEntityKey(entityKey)
.withExec(this.examSEBRestrictionSettings.settingsFunction(this.pageService))
@ -428,7 +428,12 @@ public class ExamForm implements TemplateComposer {
.withEntityKey(entityKey)
.withExec(action -> this.examSEBRestrictionSettings.setSEBRestriction(action, false, this.restService))
.publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
&& BooleanUtils.isTrue(isRestricted));
&& BooleanUtils.isTrue(isRestricted))
.newAction(ActionDefinition.EXAM_DELETE)
.withEntityKey(entityKey)
.withExec(this.examDeletePopup.deleteWizardFunction(pageContext))
.publishIf(() -> writeGrant && readonly);
// additional data in read-only view
if (readonly && !importFromQuizData) {

View file

@ -38,7 +38,7 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.ModelInputWizard.WizardAction
import ch.ethz.seb.sebserver.gui.service.page.impl.ModelInputWizard.WizardPage;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.DeleteAllClientEvents;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.DeleteAllClientEvents;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy

View file

@ -42,8 +42,8 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetClientEventNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientEventNames;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.EntityTable;

View file

@ -8,18 +8,27 @@
package ch.ethz.seb.sebserver.gui.content;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.tomcat.util.buf.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
@ -37,6 +46,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetUserLogNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetUserLogPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccount;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
@ -51,6 +61,8 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@GuiProfile
public class UserActivityLogs implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(UserActivityLogs.class);
private static final LocTextKey DETAILS_TITLE_TEXT_KEY =
new LocTextKey("sebserver.userlogs.details.title");
private static final LocTextKey TITLE_TEXT_KEY =
@ -93,16 +105,19 @@ public class UserActivityLogs implements TemplateComposer {
private final ResourceService resourceService;
private final I18nSupport i18nSupport;
private final WidgetFactory widgetFactory;
private final UserActivityLogsDeletePopup userActivityLogsDeletePopup;
private final int pageSize;
public UserActivityLogs(
final PageService pageService,
final UserActivityLogsDeletePopup userActivityLogsDeletePopup,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.i18nSupport = this.resourceService.getI18nSupport();
this.widgetFactory = pageService.getWidgetFactory();
this.userActivityLogsDeletePopup = userActivityLogsDeletePopup;
this.pageSize = pageSize;
this.institutionFilter = new TableFilterAttribute(
@ -152,6 +167,17 @@ public class UserActivityLogs implements TemplateComposer {
}
});
final Consumer<Boolean> deleteActionActivation = this.pageService.getActionActiviationPublisher(
pageContext,
ActionDefinition.LOGS_USER_ACTIVITY_DELETE_ALL);
final Consumer<Boolean> detailsActionActivation = this.pageService.getActionActiviationPublisher(
pageContext,
ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS);
final Consumer<Integer> contentChangeListener = contentSize -> {
deleteActionActivation.accept(contentSize > 0);
detailsActionActivation.accept(contentSize > 0);
};
// table
final EntityTable<UserActivityLog> table = this.pageService.entityTableBuilder(
restService.getRestCall(GetUserLogPage.class))
@ -207,6 +233,8 @@ public class UserActivityLogs implements TemplateComposer {
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS))
.withContentChangeListener(contentChangeListener)
.compose(pageContext.copyOf(content));
actionBuilder
@ -216,8 +244,40 @@ public class UserActivityLogs implements TemplateComposer {
action -> this.showDetails(action, table.getSingleSelectedROWData()),
EMPTY_SELECTION_TEXT)
.noEventPropagation()
.publishIf(table::hasAnyContent, false);
.publish(false)
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_DELETE_ALL)
.withExec(action -> this.getOpenDelete(action, table.getFilterCriteria()))
.noEventPropagation()
.publishIf(isSEBAdmin, table.hasAnyContent());
}
private PageAction getOpenDelete(final PageAction pageAction, final MultiValueMap<String, String> filterCriteria) {
try {
final List<String> ids = this.pageService
.getRestService()
.getBuilder(GetUserLogNames.class)
.withQueryParams(filterCriteria)
.call()
.getOrThrow()
.stream()
.map(EntityName::getModelId)
.collect(Collectors.toList());
final PageAction deleteAction = pageAction.withAttribute(
PageContext.AttributeKeys.ENTITY_ID_LIST,
StringUtils.join(ids, Constants.COMMA))
.withAttribute(
PageContext.AttributeKeys.ENTITY_LIST_TYPE,
EntityType.CLIENT_EVENT.name());
return this.userActivityLogsDeletePopup
.deleteWizardFunction(deleteAction.pageContext())
.apply(deleteAction);
} catch (final Exception e) {
log.error("Unexpected error while try to open user activity log delete popup", e);
return pageAction;
}
}
private String getLogTime(final UserActivityLog log) {

View file

@ -0,0 +1,152 @@
/*
* Copyright (c) 2020 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;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
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.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModelInputWizard;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModelInputWizard.WizardAction;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModelInputWizard.WizardPage;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.DeleteAllUserLogs;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy
@Component
@GuiProfile
public class UserActivityLogsDeletePopup {
private static final Logger log = LoggerFactory.getLogger(UserActivityLogsDeletePopup.class);
private final static LocTextKey FORM_TITLE =
new LocTextKey("sebserver.userlogs.delete.form.title");
private final static LocTextKey ACTION_DELETE =
new LocTextKey("sebserver.userlogs.delete.action.delete");
private final static LocTextKey DELETE_CONFIRM_TITLE =
new LocTextKey("sebserver.userlogs.delete.confirm.title");
private final PageService pageService;
protected UserActivityLogsDeletePopup(final PageService pageService) {
this.pageService = pageService;
}
public Function<PageAction, PageAction> deleteWizardFunction(final PageContext pageContext) {
return action -> {
final ModelInputWizard<PageContext> wizard =
new ModelInputWizard<PageContext>(
action.pageContext().getParent().getShell(),
this.pageService.getWidgetFactory())
.setVeryLargeDialogWidth();
final String page1Id = "DELETE_PAGE";
final Predicate<PageContext> callback = pc -> doDelete(this.pageService, pc);
final BiFunction<PageContext, Composite, Supplier<PageContext>> composePage1 =
(prefPageContext, content) -> composeDeleteDialog(content,
(prefPageContext != null) ? prefPageContext : pageContext);
final WizardPage<PageContext> page1 = new WizardPage<>(
page1Id,
true,
composePage1,
new WizardAction<>(ACTION_DELETE, callback));
wizard.open(FORM_TITLE, Utils.EMPTY_EXECUTION, page1);
return action;
};
}
private boolean doDelete(
final PageService pageService,
final PageContext pageContext) {
try {
final String idsToDelete = pageContext.getAttribute(PageContext.AttributeKeys.ENTITY_ID_LIST);
final RestCall<EntityProcessingReport>.RestCallBuilder restCallBuilder = this.pageService.getRestService()
.getBuilder(DeleteAllUserLogs.class)
.withFormParam(API.PARAM_MODEL_ID_LIST, idsToDelete)
.withFormParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name());
final EntityProcessingReport report = restCallBuilder.call().getOrThrow();
final PageAction action = this.pageService.pageActionBuilder(pageContext)
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_LIST)
.create();
this.pageService.firePageEvent(
new ActionEvent(action),
action.pageContext());
pageContext.publishPageMessage(
DELETE_CONFIRM_TITLE,
new LocTextKey(
"sebserver.userlogs.delete.confirm.message",
report.results.size(),
(report.errors.isEmpty()) ? "no" : String.valueOf((report.errors.size()))));
return true;
} catch (final Exception e) {
log.error("Unexpected error while trying to delete user activity logs:", e);
pageContext.notifyUnexpectedError(e);
return false;
}
}
private Supplier<PageContext> composeDeleteDialog(
final Composite parent,
final PageContext pageContext) {
final String idsToDelete = pageContext.getAttribute(PageContext.AttributeKeys.ENTITY_ID_LIST);
final int number = (StringUtils.isNotBlank(idsToDelete))
? idsToDelete.split(Constants.LIST_SEPARATOR).length
: 0;
final Composite grid = this.pageService.getWidgetFactory()
.createPopupScrollComposite(parent);
final Label title = this.pageService.getWidgetFactory().labelLocalized(
grid,
CustomVariant.TEXT_H3,
new LocTextKey("sebserver.userlogs.delete.form.info", number));
final GridData gridData = new GridData();
gridData.horizontalIndent = 10;
gridData.verticalIndent = 10;
title.setLayoutData(gridData);
return () -> pageContext;
}
}

View file

@ -675,6 +675,11 @@ public enum ActionDefinition {
new LocTextKey("sebserver.logs.activity.userlogs.details"),
ImageIcon.SHOW,
ActionCategory.LOGS_USER_ACTIVITY_LIST),
LOGS_USER_ACTIVITY_DELETE_ALL(
new LocTextKey("sebserver.userlogs.action.delete"),
ImageIcon.DELETE,
PageStateDefinitionImpl.USER_ACTIVITY_LOGS,
ActionCategory.LOGS_USER_ACTIVITY_LIST),
LOGS_SEB_CLIENT(
new LocTextKey("sebserver.logs.activity.seblogs"),
@ -687,7 +692,7 @@ public enum ActionDefinition {
new LocTextKey("sebserver.seblogs.action.delete"),
ImageIcon.DELETE,
PageStateDefinitionImpl.SEB_CLIENT_LOGS,
ActionCategory.FORM),
ActionCategory.LOGS_SEB_CLIENT_LIST),
;

View file

@ -10,13 +10,11 @@ package ch.ethz.seb.sebserver.gui.form;
import java.util.function.Consumer;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
@ -25,6 +23,7 @@ import org.eclipse.swt.widgets.Text;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
public final class TextFieldBuilder extends FieldBuilder<String> {
@ -77,6 +76,12 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
return this;
}
public TextFieldBuilder asHTML(final int minHeight) {
this.isHTML = true;
this.areaMinHeight = minHeight;
return this;
}
public FieldBuilder<?> asHTML(final boolean html) {
this.isHTML = html;
return this;
@ -97,7 +102,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
final Browser browser = new Browser(fieldGrid, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
gridData.minimumHeight = this.areaMinHeight;
browser.setBackground(new Color(builder.formParent.getDisplay(), new RGB(250, 250, 250)));
browser.setBackground(new Color(builder.formParent.getDisplay(), 250, 250, 250));
browser.setLayoutData(gridData);
if (StringUtils.isNoneBlank(this.value)) {
browser.setText(createHTMLText(this.value));

View file

@ -178,6 +178,20 @@ public interface PageService {
.get();
}
/** Use this to get an action activation publisher that processes the action activation.
*
* @param pageContext the current PageContext
* @param actionDefinitions list of action definitions that activity should be toggled on table selection
* @return the action activation publisher that can be used to control the activity of an certain action */
default Consumer<Boolean> getActionActiviationPublisher(
final PageContext pageContext,
final ActionDefinition... actionDefinitions) {
return avtivate -> firePageEvent(
new ActionActivationEvent(avtivate, actionDefinitions),
pageContext);
}
/** Use this to get an table selection action publisher that processes the action
* activation on table selection.
*

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class DeleteAllUserLogs extends RestCall<EntityProcessingReport> {
public DeleteAllUserLogs() {
super(new TypeKey<>(
CallType.DELETE,
EntityType.USER_ACTIVITY_LOG,
new TypeReference<EntityProcessingReport>() {
}),
HttpMethod.DELETE,
MediaType.APPLICATION_FORM_URLENCODED,
API.USER_ACTIVITY_LOG_ENDPOINT);
}
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs;
import java.util.List;

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs;
import java.util.List;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetUserLogNames extends RestCall<List<EntityName>> {
public GetUserLogNames() {
super(new TypeKey<>(
CallType.GET_NAMES,
EntityType.USER_ACTIVITY_LOG,
new TypeReference<List<EntityName>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.USER_ACTIVITY_LOG_ENDPOINT + API.NAMES_PATH_SEGMENT);
}
}

View file

@ -95,6 +95,7 @@ public class EntityTable<ROW> {
private final MultiValueMap<String, String> staticQueryParams;
private final BiConsumer<TableItem, ROW> rowDecorator;
private final Consumer<Set<ROW>> selectionListener;
private final Consumer<Integer> contentChangeListener;
int pageNumber;
int pageSize;
@ -118,7 +119,8 @@ public class EntityTable<ROW> {
final boolean hideNavigation,
final MultiValueMap<String, String> staticQueryParams,
final BiConsumer<TableItem, ROW> rowDecorator,
final Consumer<Set<ROW>> selectionListener) {
final Consumer<Set<ROW>> selectionListener,
final Consumer<Integer> contentChangeListener) {
this.name = name;
this.filterAttrName = name + "_filter";
@ -149,6 +151,7 @@ public class EntityTable<ROW> {
this.staticQueryParams = staticQueryParams;
this.rowDecorator = rowDecorator;
this.selectionListener = selectionListener;
this.contentChangeListener = contentChangeListener;
this.pageSize = pageSize;
this.filter = columns
.stream()
@ -492,6 +495,7 @@ public class EntityTable<ROW> {
this.composite.getParent().layout(true, true);
PageService.updateScrolledComposite(this.composite);
this.notifyContentChange();
this.notifySelectionChange();
}
@ -753,4 +757,10 @@ public class EntityTable<ROW> {
}
}
private void notifyContentChange() {
if (this.contentChangeListener != null) {
this.contentChangeListener.accept(this.table.getItemCount());
}
}
}

View file

@ -47,6 +47,7 @@ public class TableBuilder<ROW> {
private Function<PageSupplier.Builder<ROW>, PageSupplier.Builder<ROW>> restCallAdapter;
private BiConsumer<TableItem, ROW> rowDecorator;
private Consumer<Set<ROW>> selectionListener;
private Consumer<Integer> contentChangeListener;
private boolean markupEnabled = false;
public TableBuilder(
@ -128,6 +129,11 @@ public class TableBuilder<ROW> {
return this;
}
public TableBuilder<ROW> withContentChangeListener(final Consumer<Integer> contentChangeListener) {
this.contentChangeListener = contentChangeListener;
return this;
}
public TableBuilder<ROW> withStaticFilter(final String name, final String value) {
this.staticQueryParams.add(name, value);
return this;
@ -171,7 +177,7 @@ public class TableBuilder<ROW> {
}
public EntityTable<ROW> compose(final PageContext pageContext) {
return new EntityTable<ROW>(
return new EntityTable<>(
this.name,
this.markupEnabled,
this.type,
@ -188,7 +194,8 @@ public class TableBuilder<ROW> {
this.hideNavigation,
this.staticQueryParams,
this.rowDecorator,
this.selectionListener);
this.selectionListener,
this.contentChangeListener);
}
}

View file

@ -8,19 +8,37 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport.ErrorEntry;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@ -48,6 +66,53 @@ public class UserActivityLogController extends ReadonlyEntityController<UserActi
beanValidationService);
}
@Override
@RequestMapping(
method = RequestMethod.DELETE,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public EntityProcessingReport hardDeleteAll(
@RequestParam(name = API.PARAM_MODEL_ID_LIST) final List<String> ids,
@RequestParam(name = API.PARAM_BULK_ACTION_ADD_INCLUDES, defaultValue = "false") final boolean addIncludes,
@RequestParam(name = API.PARAM_BULK_ACTION_INCLUDES, required = false) final List<String> includes,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
// check user has SEB Server administrator role
final SEBServerUser currentUser = this.authorization.getUserService()
.getCurrentUser();
if (!currentUser.getUserRoles().contains(UserRole.SEB_SERVER_ADMIN)) {
throw new PermissionDeniedException(
EntityType.USER_ACTIVITY_LOG,
PrivilegeType.WRITE,
currentUser.getUserInfo());
}
if (ids == null || ids.isEmpty()) {
return EntityProcessingReport.ofEmptyError();
}
final Set<EntityKey> sources = ids.stream()
.map(id -> new EntityKey(id, EntityType.USER_ACTIVITY_LOG))
.collect(Collectors.toSet());
final Result<Collection<EntityKey>> delete = this.entityDAO.delete(sources);
if (delete.hasError()) {
return new EntityProcessingReport(
Collections.emptyList(),
Collections.emptyList(),
Arrays.asList(new ErrorEntry(null, APIMessage.ErrorMessage.UNEXPECTED.of(delete.getError()))));
} else {
return new EntityProcessingReport(
sources,
delete.get(),
Collections.emptyList());
}
}
@Override
protected void checkReadPrivilege(final Long institutionId) {
checkRead(institutionId);

View file

@ -1448,6 +1448,12 @@ sebserver.userlogs.info.pleaseSelect=At first please select a User Log from the
sebserver.userlogs.list.actions=
sebserver.userlogs.list.empty=No User activity logs can be found. Please adapt or clear the filter
sebserver.userlogs.action.delete=Delete Logs
sebserver.userlogs.delete.form.title=Delete User Logs
sebserver.userlogs.delete.form.info=This will delete all user activity logs from the current filtered list.<br/>Please check carefully if all user activity logs from the list shall be deleted.<br/><br/>There are currently {0} logs within the list.
sebserver.userlogs.delete.action.delete=Delete All Logs
sebserver.userlogs.delete.confirm.title=Deletion Successful
sebserver.userlogs.delete.confirm.message={0} User activity logs where successfully deleted.<br/><br/>And there where {1} errors.
sebserver.seblogs.list.title=SEB Client Logs
sebserver.seblogs.list.actions=