diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index bcde28c1..1516a3aa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -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) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDeletePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDeletePopup.java index efaa4166..030716d6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDeletePopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDeletePopup.java @@ -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 diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java index f5dc72e0..72deacb7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserActivityLogs.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserActivityLogs.java index 8b8229c7..70e43a5a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserActivityLogs.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserActivityLogs.java @@ -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 deleteActionActivation = this.pageService.getActionActiviationPublisher( + pageContext, + ActionDefinition.LOGS_USER_ACTIVITY_DELETE_ALL); + final Consumer detailsActionActivation = this.pageService.getActionActiviationPublisher( + pageContext, + ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS); + final Consumer contentChangeListener = contentSize -> { + deleteActionActivation.accept(contentSize > 0); + detailsActionActivation.accept(contentSize > 0); + }; + // table final EntityTable 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 filterCriteria) { + try { + final List 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) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserActivityLogsDeletePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserActivityLogsDeletePopup.java new file mode 100644 index 00000000..d3be54c3 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserActivityLogsDeletePopup.java @@ -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 deleteWizardFunction(final PageContext pageContext) { + return action -> { + + final ModelInputWizard wizard = + new ModelInputWizard( + action.pageContext().getParent().getShell(), + this.pageService.getWidgetFactory()) + .setVeryLargeDialogWidth(); + + final String page1Id = "DELETE_PAGE"; + final Predicate callback = pc -> doDelete(this.pageService, pc); + final BiFunction> composePage1 = + (prefPageContext, content) -> composeDeleteDialog(content, + (prefPageContext != null) ? prefPageContext : pageContext); + + final WizardPage 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.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 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; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 955c8787..d90bcce3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -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), ; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java index 431e0460..049a7d1a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java @@ -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 { @@ -77,6 +76,12 @@ public final class TextFieldBuilder extends FieldBuilder { 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 { 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)); 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 83c80912..f3c95958 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 @@ -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 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. * diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/DeleteAllClientEvents.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/DeleteAllClientEvents.java similarity index 96% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/DeleteAllClientEvents.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/DeleteAllClientEvents.java index 239d7e30..bd50cdf2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/DeleteAllClientEvents.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/DeleteAllClientEvents.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/DeleteAllUserLogs.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/DeleteAllUserLogs.java new file mode 100644 index 00000000..eb4d4b96 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/DeleteAllUserLogs.java @@ -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 { + + public DeleteAllUserLogs() { + super(new TypeKey<>( + CallType.DELETE, + EntityType.USER_ACTIVITY_LOG, + new TypeReference() { + }), + HttpMethod.DELETE, + MediaType.APPLICATION_FORM_URLENCODED, + API.USER_ACTIVITY_LOG_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetClientEventNames.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetClientEventNames.java similarity index 96% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetClientEventNames.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetClientEventNames.java index 8b20e440..0922448a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetClientEventNames.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetClientEventNames.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetClientEventPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetClientEventPage.java similarity index 96% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetClientEventPage.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetClientEventPage.java index a89c1a56..46df86f0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetClientEventPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetClientEventPage.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetUserLogNames.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetUserLogNames.java new file mode 100644 index 00000000..1aa91ce4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/logs/GetUserLogNames.java @@ -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> { + + public GetUserLogNames() { + super(new TypeKey<>( + CallType.GET_NAMES, + EntityType.USER_ACTIVITY_LOG, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.USER_ACTIVITY_LOG_ENDPOINT + API.NAMES_PATH_SEGMENT); + } + +} 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 720a71e2..5d121040 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 @@ -95,6 +95,7 @@ public class EntityTable { private final MultiValueMap staticQueryParams; private final BiConsumer rowDecorator; private final Consumer> selectionListener; + private final Consumer contentChangeListener; int pageNumber; int pageSize; @@ -118,7 +119,8 @@ public class EntityTable { final boolean hideNavigation, final MultiValueMap staticQueryParams, final BiConsumer rowDecorator, - final Consumer> selectionListener) { + final Consumer> selectionListener, + final Consumer contentChangeListener) { this.name = name; this.filterAttrName = name + "_filter"; @@ -149,6 +151,7 @@ public class EntityTable { 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 { this.composite.getParent().layout(true, true); PageService.updateScrolledComposite(this.composite); + this.notifyContentChange(); this.notifySelectionChange(); } @@ -753,4 +757,10 @@ public class EntityTable { } } + private void notifyContentChange() { + if (this.contentChangeListener != null) { + this.contentChangeListener.accept(this.table.getItemCount()); + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java index ebec8eea..a5b4dd46 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java @@ -47,6 +47,7 @@ public class TableBuilder { private Function, PageSupplier.Builder> restCallAdapter; private BiConsumer rowDecorator; private Consumer> selectionListener; + private Consumer contentChangeListener; private boolean markupEnabled = false; public TableBuilder( @@ -128,6 +129,11 @@ public class TableBuilder { return this; } + public TableBuilder withContentChangeListener(final Consumer contentChangeListener) { + this.contentChangeListener = contentChangeListener; + return this; + } + public TableBuilder withStaticFilter(final String name, final String value) { this.staticQueryParams.add(name, value); return this; @@ -171,7 +177,7 @@ public class TableBuilder { } public EntityTable compose(final PageContext pageContext) { - return new EntityTable( + return new EntityTable<>( this.name, this.markupEnabled, this.type, @@ -188,7 +194,8 @@ public class TableBuilder { this.hideNavigation, this.staticQueryParams, this.rowDecorator, - this.selectionListener); + this.selectionListener, + this.contentChangeListener); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java index 4ffc8db5..1bafde3a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserActivityLogController.java @@ -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 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 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 sources = ids.stream() + .map(id -> new EntityKey(id, EntityType.USER_ACTIVITY_LOG)) + .collect(Collectors.toSet()); + + final Result> 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); diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 6df2185d..e9ea1cea 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -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.
Please check carefully if all user activity logs from the list shall be deleted.

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.

And there where {1} errors. sebserver.seblogs.list.title=SEB Client Logs sebserver.seblogs.list.actions=