SEBSERV-14 #added pagination to user activity log API

This commit is contained in:
anhefti 2019-01-09 17:03:54 +01:00
parent 83b3d190bb
commit 20a9f8e705
4 changed files with 197 additions and 65 deletions

View file

@ -23,7 +23,7 @@ public final class Page<T> {
}
public static final String ATTR_NUMBER_OF_PAGES = "numberOfPages";
public static final String ATTR_PAGE_NUMBER = "pageNum";
public static final String ATTR_PAGE_NUMBER = "pageNumber";
public static final String ATTR_PAGE_SIZE = "pageSize";
public static final String ATTR_SORT_BY = "sortBy";
public static final String ATTR_SORT_ORDER = "sortOrder";

View file

@ -9,20 +9,22 @@
package ch.ethz.seb.sebserver.webservice.servicelayer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport.UserRecord;
@Service
public class PaginationService {
@ -30,12 +32,16 @@ public class PaginationService {
private final int defaultPageSize;
private final int maxPageSize;
private final Map<String, Map<String, String>> sortColumnMapping;
public PaginationService(
@Value("${sebserver.webservice.api.pagination.defaultPageSize:10}") final int defaultPageSize,
@Value("${sebserver.webservice.api.pagination.maxPageSize:500}") final int maxPageSize) {
this.defaultPageSize = defaultPageSize;
this.maxPageSize = maxPageSize;
this.sortColumnMapping = new HashMap<>();
initSortColumnMapping();
}
public void setOnePageLimit(final SqlTable table) {
@ -70,12 +76,12 @@ public class PaginationService {
final com.github.pagehelper.Page<Object> startPage =
PageHelper.startPage(_pageNumber, _pageSize, true, true, false);
final SqlColumn<?> sortColumn = verifySortColumn(sortBy, table);
if (sortColumn != null) {
final String sortColumnName = verifySortColumnName(sortBy, table);
if (StringUtils.isNoneBlank(sortColumnName)) {
if (sortOrder == Page.SortOrder.DESCENDING) {
PageHelper.orderBy(sortColumn.name() + " DESC");
PageHelper.orderBy(sortColumnName + " DESC");
} else {
PageHelper.orderBy(sortColumn.name());
PageHelper.orderBy(sortColumnName);
}
}
@ -95,33 +101,51 @@ public class PaginationService {
return new Page<>(page.getPages(), pageNumber, sortBy, sortOrder, pageList);
}
private SqlColumn<?> verifySortColumn(final String sortBy, final SqlTable table) {
if (table == UserRecordDynamicSqlSupport.userRecord) {
return verifySortColumn(sortBy, UserRecordDynamicSqlSupport.userRecord);
private String verifySortColumnName(final String sortBy, final SqlTable table) {
if (StringUtils.isBlank(sortBy)) {
return null;
}
final Map<String, String> mapping = this.sortColumnMapping.get(table.name());
if (mapping != null) {
return mapping.get(sortBy);
}
return null;
}
private SqlColumn<?> verifySortColumn(final String sortBy, final UserRecord table) {
// TODO is it possible to generate this within MyBatis generator?
private void initSortColumnMapping() {
// User Table
final Map<String, String> userTableMap = new HashMap<>();
userTableMap.put(Domain.USER.ATTR_NAME, UserRecordDynamicSqlSupport.name.name());
userTableMap.put(Domain.USER.ATTR_USERNAME, UserRecordDynamicSqlSupport.username.name());
userTableMap.put(Domain.USER.ATTR_EMAIL, UserRecordDynamicSqlSupport.email.name());
userTableMap.put(Domain.USER.ATTR_LOCALE, UserRecordDynamicSqlSupport.locale.name());
this.sortColumnMapping.put(UserRecordDynamicSqlSupport.userRecord.name(), userTableMap);
if (StringUtils.isBlank(sortBy)) {
return UserRecordDynamicSqlSupport.id;
}
// User Activity Log Table
final Map<String, String> userActivityLogTableMap = new HashMap<>();
userActivityLogTableMap.put(
Domain.USER_ACTIVITY_LOG.ATTR_USER_UUID,
UserActivityLogRecordDynamicSqlSupport.userUuid.name());
userActivityLogTableMap.put(
Domain.USER_ACTIVITY_LOG.ATTR_ACTIVITY_TYPE,
UserActivityLogRecordDynamicSqlSupport.activityType.name());
userActivityLogTableMap.put(
Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID,
UserActivityLogRecordDynamicSqlSupport.entityId.name());
userActivityLogTableMap.put(
Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_TYPE,
UserActivityLogRecordDynamicSqlSupport.entityType.name());
userActivityLogTableMap.put(
Domain.USER_ACTIVITY_LOG.ATTR_TIMESTAMP,
UserActivityLogRecordDynamicSqlSupport.timestamp.name());
this.sortColumnMapping.put(
UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord.name(),
userActivityLogTableMap);
if (sortBy.equals(UserRecordDynamicSqlSupport.name.name())) {
return UserRecordDynamicSqlSupport.name;
}
if (sortBy.equals(UserRecordDynamicSqlSupport.username.name())) {
return UserRecordDynamicSqlSupport.username;
}
if (sortBy.equals(UserRecordDynamicSqlSupport.email.name())) {
return UserRecordDynamicSqlSupport.email;
}
return null;
}
}

View file

@ -8,7 +8,6 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -24,8 +23,12 @@ import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@ -37,13 +40,16 @@ public class UserActivityLogController {
private final UserActivityLogDAO userActivityLogDAO;
private final AuthorizationGrantService authorizationGrantService;
private final PaginationService paginationService;
public UserActivityLogController(
final UserActivityLogDAO userActivityLogDAO,
final AuthorizationGrantService authorizationGrantService) {
final AuthorizationGrantService authorizationGrantService,
final PaginationService paginationService) {
this.userActivityLogDAO = userActivityLogDAO;
this.authorizationGrantService = authorizationGrantService;
this.paginationService = paginationService;
}
@RequestMapping(method = RequestMethod.GET)
@ -51,10 +57,11 @@ public class UserActivityLogController {
@RequestParam(required = false) final Long from,
@RequestParam(required = false) final Long to,
@RequestParam(required = false) final String activityTypes,
@RequestParam(required = false) final String entityTypes,
final Principal principal) {
@RequestParam(required = false) final String entityTypes) {
return _getAll(null, from, to, activityTypes, entityTypes, principal);
checkBaseReadPrivilege();
this.paginationService.setOnePageLimit(UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord);
return _getAll(null, from, to, activityTypes, entityTypes);
}
@RequestMapping(path = "/{userId}", method = RequestMethod.GET)
@ -63,10 +70,60 @@ public class UserActivityLogController {
@RequestParam(required = false) final Long from,
@RequestParam(required = false) final Long to,
@RequestParam(required = false) final String activityTypes,
@RequestParam(required = false) final String entityTypes,
final Principal principal) {
@RequestParam(required = false) final String entityTypes) {
return _getAll(userId, from, to, activityTypes, entityTypes, principal);
checkBaseReadPrivilege();
this.paginationService.setOnePageLimit(UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord);
return _getAll(userId, from, to, activityTypes, entityTypes);
}
@RequestMapping(path = "/page", method = RequestMethod.GET)
public Page<UserActivityLog> getPage(
@RequestParam(required = false) final Long from,
@RequestParam(required = false) final Long to,
@RequestParam(required = false) final String activityTypes,
@RequestParam(required = false) final String entityTypes,
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT_BY, required = false) final String sortBy,
@RequestParam(name = Page.ATTR_SORT_ORDER, required = false) final Page.SortOrder sortOrder) {
checkBaseReadPrivilege();
return this.paginationService.getPage(
pageNumber,
pageSize,
sortBy,
sortOrder,
UserRecordDynamicSqlSupport.userRecord,
() -> _getAll(null, from, to, activityTypes, entityTypes));
}
@RequestMapping(path = "/page/{userId}", method = RequestMethod.GET)
public Page<UserActivityLog> getPageForUser(
@PathVariable final String userId,
@RequestParam(required = false) final Long from,
@RequestParam(required = false) final Long to,
@RequestParam(required = false) final String activityTypes,
@RequestParam(required = false) final String entityTypes,
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT_BY, required = false) final String sortBy,
@RequestParam(name = Page.ATTR_SORT_ORDER, required = false) final Page.SortOrder sortOrder) {
checkBaseReadPrivilege();
return this.paginationService.getPage(
pageNumber,
pageSize,
sortBy,
sortOrder,
UserRecordDynamicSqlSupport.userRecord,
() -> _getAll(userId, from, to, activityTypes, entityTypes));
}
private void checkBaseReadPrivilege() {
this.authorizationGrantService.checkHasAnyPrivilege(
EntityType.USER_ACTIVITY_LOG,
PrivilegeType.READ_ONLY);
}
private Collection<UserActivityLog> _getAll(
@ -74,13 +131,7 @@ public class UserActivityLogController {
final Long from,
final Long to,
final String activityTypes,
final String entityTypes,
final Principal principal) {
// fist check if current user has any privileges for this action
this.authorizationGrantService.checkHasAnyPrivilege(
EntityType.USER_ACTIVITY_LOG,
PrivilegeType.READ_ONLY);
final String entityTypes) {
final Set<String> _activityTypes = (activityTypes != null)
? Collections.unmodifiableSet(new HashSet<>(
@ -108,7 +159,6 @@ public class UserActivityLogController {
return this.userActivityLogDAO.all(userId, from, to, record -> true)
.getOrThrow();
}
}

View file

@ -163,6 +163,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
});
assertNotNull(userInfos);
assertTrue(userInfos.numberOfPages == 1);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 7);
assertEquals("[user1, user2, user3, user4, user5, user6, user7]", getOrderedUUIDs(userInfos.content));
@ -180,6 +181,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
});
assertNotNull(userInfos);
assertTrue(userInfos.numberOfPages == 1);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 7);
assertEquals("[user7, user6, user5, user4, user3, user2, user1]", getOrderedUUIDs(userInfos.content));
@ -200,10 +202,45 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
});
assertNotNull(userInfos);
assertTrue(userInfos.numberOfPages == 3);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 3);
assertEquals("[user1, user2, user3]", getOrderedUUIDs(userInfos.content));
// second page default sort order
userInfos = this.jsonMapper.readValue(
this.mockMvc
.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT
+ "/page?pageNumber=2&pageSize=3")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<Page<UserInfo>>() {
});
assertNotNull(userInfos);
assertTrue(userInfos.numberOfPages == 3);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 3);
assertEquals("[user4, user5, user6]", getOrderedUUIDs(userInfos.content));
// third page default sort order
userInfos = this.jsonMapper.readValue(
this.mockMvc
.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT
+ "/page?pageNumber=3&pageSize=3")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<Page<UserInfo>>() {
});
assertNotNull(userInfos);
assertTrue(userInfos.numberOfPages == 3);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 1);
assertEquals("[user7]", getOrderedUUIDs(userInfos.content));
// first page descending sort order
userInfos = this.jsonMapper.readValue(
this.mockMvc
@ -216,6 +253,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
});
assertNotNull(userInfos);
assertTrue(userInfos.numberOfPages == 3);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 3);
assertEquals("[user7, user6, user5]", getOrderedUUIDs(userInfos.content));
@ -230,7 +268,39 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
}
@Test
public void getAllUserInfoWithSearchInactive() throws Exception {
public void getAllUserInfo() throws Exception {
final String token = getSebAdminAccess();
final List<UserInfo> userInfos = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT)
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<List<UserInfo>>() {
});
assertNotNull(userInfos);
assertTrue(userInfos.size() == 7);
assertNotNull(getUserInfo("deactivatedUser", userInfos));
}
@Test
public void getAllUserInfoWithOnlyActice() throws Exception {
final String token = getSebAdminAccess();
final List<UserInfo> userInfos = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?active=true")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<List<UserInfo>>() {
});
assertNotNull(userInfos);
assertTrue(userInfos.size() == 6);
assertNull(getUserInfo("deactivatedUser", userInfos));
}
@Test
public void getAllUserInfoOnlyInactive() throws Exception {
final String token = getSebAdminAccess();
final List<UserInfo> userInfos = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?active=false")
@ -245,22 +315,6 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
assertNotNull(getUserInfo("deactivatedUser", userInfos));
}
// @Test
// public void getAllUserInfoWithFilterNameAndNoActiveFlagSet() throws Exception {
// final String token = getSebAdminAccess();
// final List<UserInfo> userInfos = this.jsonMapper.readValue(
// this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?active=false")
// .header("Authorization", "Bearer " + token))
// .andExpect(status().isOk())
// .andReturn().getResponse().getContentAsString(),
// new TypeReference<List<UserInfo>>() {
// });
//
// assertNotNull(userInfos);
// assertTrue(userInfos.size() == 1);
// assertNotNull(getUserInfo("deactivatedUser", userInfos));
// }
@Test
public void getAllUserInfoWithSearchUsernameLike() throws Exception {
final String token = getSebAdminAccess();
@ -688,11 +742,15 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
}
private UserInfo getUserInfo(final String name, final Collection<UserInfo> infos) {
return infos
.stream()
.filter(ui -> ui.username.equals(name))
.findFirst()
.orElseThrow(NoSuchElementException::new);
try {
return infos
.stream()
.filter(ui -> ui.username.equals(name))
.findFirst()
.orElseThrow(NoSuchElementException::new);
} catch (final Exception e) {
return null;
}
}
}