From e15ad419d093f8a416e355d512c1dfc9bc9df235 Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Sat, 27 Apr 2024 17:02:38 +0200 Subject: [PATCH 01/11] Add base paginated game query --- .../java/com/wegas/core/ejb/GameFacade.java | 194 +++++++++++------- .../com/wegas/core/rest/GameController.java | 37 +++- 2 files changed, 146 insertions(+), 85 deletions(-) diff --git a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java index d5d4cc7f59..b4b9ffff7f 100644 --- a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java +++ b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java @@ -1,7 +1,7 @@ /** * Wegas * http://wegas.albasim.ch - * + *

* Copyright (c) 2013-2021 School of Management and Engineering Vaud, Comem, MEI * Licensed under the MIT License */ @@ -25,6 +25,8 @@ import com.wegas.core.persistence.game.Populatable.Status; import com.wegas.core.persistence.game.Script; import com.wegas.core.persistence.game.Team; +import com.wegas.core.rest.util.pagination.Page; +import com.wegas.core.rest.util.pagination.Pageable; import com.wegas.core.security.ejb.AccountFacade; import com.wegas.core.security.ejb.UserFacade; import com.wegas.core.security.guest.GuestJpaAccount; @@ -33,23 +35,22 @@ import com.wegas.core.security.persistence.token.SurveyToken; import com.wegas.core.security.util.ScriptExecutionContext; import com.wegas.survey.persistence.SurveyDescriptor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; + +import java.util.*; import java.util.stream.Collectors; + import jakarta.ejb.LocalBean; import jakarta.ejb.Stateless; import jakarta.ejb.TransactionAttribute; import jakarta.ejb.TransactionAttributeType; import jakarta.enterprise.event.Event; import jakarta.inject.Inject; + import javax.naming.NamingException; + import jakarta.persistence.NoResultException; import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.*; import jakarta.servlet.http.HttpServletRequest; import org.openjdk.nashorn.api.scripting.ScriptObjectMirror; import org.apache.poi.ss.usermodel.Cell; @@ -148,9 +149,7 @@ public boolean isPersisted(final Long gameId) { * * @param gameModelId id of the gameModel to create a new game for * @param game the game to persist - * * @throws java.lang.CloneNotSupportedException - * */ public void publishAndCreate(final Long gameModelId, final Game game) throws CloneNotSupportedException { GameModel gm = gameModelFacade.createPlayGameModel(gameModelId); @@ -190,11 +189,11 @@ private void create(final GameModel gameModel, final Game game) { final User currentUser = userFacade.getCurrentUser(); - if (Helper.isNullOrEmpty(game.getToken())){ + if (Helper.isNullOrEmpty(game.getToken())) { game.setToken(this.createUniqueToken(game)); } else if (this.findLiveOrBinByToken(game.getToken()) != null) { throw WegasErrorMessage.error("This access key is already in use", - "COMMONS-SESSIONS-TAKEN-TOKEN-ERROR"); + "COMMONS-SESSIONS-TAKEN-TOKEN-ERROR"); } getEntityManager().persist(game); @@ -220,7 +219,6 @@ private void create(final GameModel gameModel, final Game game) { * Add a debugteam within the game, unless such a team already exists * * @param game the game - * * @return true if the debug game has been added, false if it was already here */ public boolean addDebugTeam(Game game) { @@ -247,7 +245,6 @@ public boolean addDebugTeam(Game game) { /** * @param game - * * @return a unique token based on the game name, suffixed with some random characters */ public String createUniqueToken(Game game) { @@ -303,7 +300,7 @@ public void remove(final Game entity) { // This is for retrocompatibility w/ game models that do not habe DebugGame if (entity.getGameModel().getGames().size() <= 1 - && !(entity.getGameModel().getGames().get(0) instanceof DebugGame)) {// This is for retrocompatibility w/ game models that do not habe DebugGame + && !(entity.getGameModel().getGames().get(0) instanceof DebugGame)) {// This is for retrocompatibility w/ game models that do not habe DebugGame gameModelFacade.remove(entity.getGameModel()); } else { getEntityManager().remove(entity); @@ -327,7 +324,6 @@ public void removeTX(Long gameId) { * Search for a LIVE game with token * * @param token - * * @return first game found or null */ public Game findByToken(final String token) { @@ -344,9 +340,9 @@ public Game findLiveOrBinByToken(final String token) { public Game findByStatusAndToken(final String token, Game.Status status) { final TypedQuery tq = getEntityManager() - .createNamedQuery("Game.findByToken", Game.class) - .setParameter("token", token) - .setParameter("status", status); + .createNamedQuery("Game.findByToken", Game.class) + .setParameter("token", token) + .setParameter("status", status); try { return tq.getSingleResult(); } catch (NoResultException ex) { @@ -356,7 +352,6 @@ public Game findByStatusAndToken(final String token, Game.Status status) { /** * @param search - * * @return all game matching the search token */ public List findByName(final String search) { @@ -368,37 +363,103 @@ public List findByName(final String search) { /** * @param gameModelId * @param orderBy not used... - * * @return all games belonging to the gameModel identified by gameModelId but DebugGames, - * ordered by creation time + * ordered by creation time */ public List findByGameModelId(final Long gameModelId, final String orderBy) { return getEntityManager().createQuery("SELECT g FROM Game g " - + "WHERE TYPE(g) != DebugGame AND g.gameModel.id = :gameModelId ORDER BY g.createdTime DESC", Game.class) - .setParameter("gameModelId", gameModelId) - .getResultList(); + + "WHERE TYPE(g) != DebugGame AND g.gameModel.id = :gameModelId ORDER BY g.createdTime DESC", Game.class) + .setParameter("gameModelId", gameModelId) + .getResultList(); } /** * @param status - * * @return all games which match the given status */ public List findAll(final Game.Status status) { return getEntityManager().createNamedQuery("Game.findByStatus", Game.class) - .setParameter("status", status).getResultList(); + .setParameter("status", status).getResultList(); + } + +// /** +// * Find all games with given ids and status (was used for faster findByStatusAndUser fetch) +// * +// * @param ids ids of games to fetch +// * @param status status of games to fetch +// * @return all games which match given ids and status +// */ +// public List findByIdsAndStatus(final List ids, final Game.Status status) { +// final CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder(); +// final CriteriaQuery query = criteriaBuilder.createQuery(Game.class); +// Root gameRoot = query.from(Game.class); +// +// query.where( +// criteriaBuilder.and( +// gameRoot.get("id").in(ids), +// criteriaBuilder.equal(gameRoot.get("status"), status) +// ) +// ); +// +// TypedQuery typedQuery = getEntityManager().createQuery(query); +// +// return typedQuery.getResultList(); +// } + + /** + * Get all paginated games with the given status which are accessible to the current user + * + * @param status status {@link Game.Status#LIVE} {@link Game.Status#BIN} {@link Game.Status#DELETE} + * @param pageable + * @return all games paginated + */ + public Page findByStatusAndUserPaginated(Game.Status status, Pageable pageable) { + + List gStatuses = new ArrayList<>(); + gStatuses.add(status); + + Map> gMatrix = this.getPermissionMatrix(gStatuses); + Map> filteredGMatrix = gMatrix.entrySet().stream() + .filter(l -> l.getValue().contains("Edit") || l.getValue().contains("*")) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + final CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder(); + final CriteriaQuery query = criteriaBuilder.createQuery(Game.class); + Root gameRoot = query.from(Game.class); + query.select(gameRoot); + + + Predicate whereClause = criteriaBuilder.and( + criteriaBuilder.equal(gameRoot.get("status"), status), + gameRoot.get("id").in(new ArrayList<>(filteredGMatrix.keySet())) + ); + + + if (pageable.getQuery() != null) { + String lowerCaseQuery = pageable.getQuery().toLowerCase(); + Join gameModelJoin = gameRoot.join("gameModel"); + whereClause = criteriaBuilder.and(whereClause, criteriaBuilder.or( + criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + lowerCaseQuery + "%"), + criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + lowerCaseQuery + "%") + )); + } + query.where(whereClause); + + int total = getEntityManager().createQuery(query).getResultList().size(); + TypedQuery listQuery = pageable.paginateQuery(getEntityManager().createQuery(query)); + + return new Page(total, pageable.getPage(), pageable.getSize(), listQuery.getResultList()); } /** * Find all game by status. * * @param statuses statuses to search - * * @return all games which match any of the given status */ public List findAll(final List statuses) { return getEntityManager().createNamedQuery("Game.findByStatuses", Game.class) - .setParameter("statuses", statuses).getResultList(); + .setParameter("statuses", statuses).getResultList(); } @@ -409,7 +470,7 @@ public List findAll(final List statuses) { */ public List getAllGameIdByLogId(String logId) { TypedQuery query = this.getEntityManager() - .createNamedQuery("Game.findAllIdByLogId", Long.class); + .createNamedQuery("Game.findAllIdByLogId", Long.class); query.setParameter("logId", logId); return query.getResultList(); } @@ -418,15 +479,12 @@ public List getAllGameIdByLogId(String logId) { * Get all games with the given status which are accessible to the current user * * @param status {@link Game.Status#LIVE} {@link Game.Status#BIN} {@link Game.Status#DELETE} - * * @return the list of all games which given status the current use has access to */ public Collection findByStatusAndUser(Game.Status status) { List gStatuses = new ArrayList<>(); gStatuses.add(status); - Map> gMatrix = this.getPermissionMatrix(gStatuses); - ArrayList games = new ArrayList<>(); for (Map.Entry> entry : gMatrix.entrySet()) { @@ -447,7 +505,6 @@ public Collection findByStatusAndUser(Game.Status status) { * Fetch all game ids that current user has access to which match any of the given statuses. * * @param statuses statuses of game to look for - * * @return list of gameId mapped to the permission the user has */ public Map> getPermissionMatrix(List statuses) { @@ -457,9 +514,9 @@ public Map> getPermissionMatrix(List statuses) { // it was time consuming // New way is to fetch permissions first and extract games from this list String roleQuery = "SELECT p FROM Permission p WHERE " - + "(p.role.id in " - + " (SELECT r.id FROM User u JOIN u.roles r WHERE u.id = :userId)" - + ")"; + + "(p.role.id in " + + " (SELECT r.id FROM User u JOIN u.roles r WHERE u.id = :userId)" + + ")"; String userQuery = "SELECT p FROM Permission p WHERE p.user.id = :userId"; @@ -475,7 +532,6 @@ public Map> getPermissionMatrix(List statuses) { * * @param game the game to join * @param languages list of user preferred languages - * * @return the brand new player */ public Player joinIndividually(Game game, List languages) { @@ -496,7 +552,6 @@ public Player joinIndividually(Game game, List languages) { * * @param game the game to join * @param languages list of user preferred languages - * * @return the brand new player */ public Player joinForSurvey(Game game, List languages) { @@ -526,25 +581,22 @@ public Player joinForSurvey(Game game, List languages) { * Return all accounts with valid email address linked to LIVE players of the given game. * * @param game - * * @return */ private List getPlayerAccountsWithEmail(Game game) { return game.getLivePlayers().stream() - .map(p -> p.getUser()) - .filter(u -> u != null) - .map(u -> u.getMainAccount()) - .filter(account -> account != null && !Helper.isNullOrEmpty(account.getEmail())) - .collect(Collectors.toList()); + .map(p -> p.getUser()) + .filter(u -> u != null) + .map(u -> u.getMainAccount()) + .filter(account -> account != null && !Helper.isNullOrEmpty(account.getEmail())) + .collect(Collectors.toList()); } /** * Get the one game from a list of surveys. * * @param surveys - * * @return the game - * * @throws WegasErrorMessage surveys belong to different games or no game found */ public Game getGameFromSurveys(List surveys) { @@ -572,15 +624,13 @@ public Game getGameFromSurveys(List surveys) { * @param surveys surveys * @param request need request to generate the link * @param email structure with attributes recipients (ignored here), sender, subject and body. - * * @return list of emails to which an invitation has been sent - * * @throws WegasErrorMessage if 1) surveys belong to different GameModels; 2) no game; 3) no * account */ public List sendSurveysInvitation(List surveys, - EmailAttributes email, - HttpServletRequest request) { + EmailAttributes email, + HttpServletRequest request) { Game game = getGameFromSurveys(surveys); List accounts = getPlayerAccountsWithEmail(game); List invitedEmails = new ArrayList<>(); @@ -599,12 +649,12 @@ public List sendSurveysInvitation(List surveys, private List loadSurveys(String ids) { return Arrays.stream(ids.split(",")) - .map(sId -> (SurveyDescriptor) variableDescriptorFacade.find(Long.parseLong(sId.trim()))) - .collect(Collectors.toList()); + .map(sId -> (SurveyDescriptor) variableDescriptorFacade.find(Long.parseLong(sId.trim()))) + .collect(Collectors.toList()); } public List sendSurveysInvitationToPlayers(HttpServletRequest request, - String surveyIds, EmailAttributes email) { + String surveyIds, EmailAttributes email) { List surveys = this.loadSurveys(surveyIds); @@ -612,7 +662,7 @@ public List sendSurveysInvitationToPlayers(HttpServletRequest request, } public List sendSurveysInvitationAnonymouslyToPlayers(HttpServletRequest request, - String surveyIds, EmailAttributes email) { + String surveyIds, EmailAttributes email) { List surveys = this.loadSurveys(surveyIds); @@ -630,15 +680,13 @@ public List sendSurveysInvitationAnonymouslyToPlayers(HttpServletRequest * @param surveys surveys * @param email structure with attributes recipients (ignored here), sender, subject and body. * @param request need request to generate the link - * * @return list of emails to which an invitation has been sent - * * @throws WegasErrorMessage if 1) surveys belong to different GameModel; 2) no game; 3) no * account */ public List sendSurveysInvitationAnonymouslyToPlayers(List surveys, - EmailAttributes email, - HttpServletRequest request + EmailAttributes email, + HttpServletRequest request ) { Game game = getGameFromSurveys(surveys); List accounts = getPlayerAccountsWithEmail(game); @@ -656,8 +704,8 @@ public List sendSurveysInvitationAnonymouslyToPlayers(List surveys = this.loadSurveys(surveyIds); @@ -672,13 +720,12 @@ public void sendSurveysInvitationAnonymouslyToList(HttpServletRequest request, * @param surveys surveys * @param email structure with attributes recipients, sender, subject and body. * @param request need request to generate the link - * * @throws WegasErrorMessage if 1) surveys belong to different GameModel; 2) no game; 3) no * account */ public void sendSurveysInvitationAnonymouslyToList(List surveys, - EmailAttributes email, - HttpServletRequest request) { + EmailAttributes email, + HttpServletRequest request) { if (!email.getRecipients().isEmpty()) { accountFacade.sendSurveyAnonymousTokens(email, surveys, request); @@ -694,7 +741,6 @@ public void sendSurveysInvitationAnonymouslyToList(List survey * @param userId id of the user to create a player for, may be null to create an anonymous * player * @param languages - * * @return a new player, linked to a user, who just joined the team */ public Player joinTeam(Long teamId, Long userId, List languages) { @@ -715,7 +761,6 @@ public Player joinTeam(Long teamId, Long userId, List languages) { * (for testing purpose) * * @param teamId id of the team to join - * * @return a new player anonymous player who just joined the team */ public Player joinTeam(Long teamId, List languages) { @@ -774,7 +819,6 @@ public void reset(Long gameId) { * Find all users with a trainer access to a game * * @param id id of the game - * * @return */ public List findTrainers(Long id) { @@ -790,7 +834,7 @@ public List findTrainers(Long id) { * @return GameFacade instance */ public static GameFacade - lookup() { + lookup() { try { return Helper.lookupBy(GameFacade.class ); @@ -805,8 +849,6 @@ public List findTrainers(Long id) { * * @param gameId * @param t - * - * */ @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Long createAndCommit(Long gameId, Team t) { @@ -880,18 +922,18 @@ public XlsxSpreadsheet getXlsxOverview(Long gameId) { private void loadOverviews(XlsxSpreadsheet xlsx, Game game, boolean includeTestPlayer) { Player p = game.getTestPlayer(); String script - = "var result= [];" - + "if (WegasDashboard){" - + " result = WegasDashboard.getAllOverviews(true);" - + "}" - + "result;"; + = "var result= [];" + + "if (WegasDashboard){" + + " result = WegasDashboard.getAllOverviews(true);" + + "}" + + "result;"; CellStyle titleStyle = xlsx.createHeaderStyle(); titleStyle.setAlignment(HorizontalAlignment.CENTER); CellStyle subtitleStyle = xlsx.createSmallerHeaderStyle(); - try ( ScriptExecutionContext ctx = requestManager.switchToInternalExecContext(true)) { + try (ScriptExecutionContext ctx = requestManager.switchToInternalExecContext(true)) { ScriptObjectMirror overviews = (ScriptObjectMirror) scriptFacade.eval(p, new Script(script), null); for (Object oSheet : overviews.values()) { diff --git a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java index 6b4c371a3e..aedb3e0344 100644 --- a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java +++ b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java @@ -19,6 +19,8 @@ import com.wegas.core.persistence.game.Game.Status; import com.wegas.core.persistence.game.Player; import com.wegas.core.persistence.game.Team; +import com.wegas.core.rest.util.pagination.Page; +import com.wegas.core.rest.util.pagination.Pageable; import com.wegas.core.security.ejb.UserFacade; import com.wegas.core.security.persistence.User; import java.io.IOException; @@ -35,15 +37,7 @@ import jakarta.ejb.Stateless; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -219,6 +213,31 @@ public Collection findByStatus(@PathParam("status") final Game.Status stat return gameFacade.findByStatusAndUser(status); } + /** + * Get all games with given status paginated + * + */ + @GET + @Path("status/{status: [A-Z]*}/Paginated") + public Page paginatedGames(@PathParam("status") final Game.Status status, + @QueryParam("page") int page, + @QueryParam("size") int size, + @QueryParam("query") String query) { + return gameFacade.findByStatusAndUserPaginated(status, new Pageable(page, size, query)); + } + /** + * Get all games with given status paginated + * + */ + @GET + @Path("status/{status: [A-Z]*}/Paginated2") + public Page paginatedGames2(@PathParam("status") final Game.Status status, + @QueryParam("page") int page, + @QueryParam("size") int size, + @QueryParam("query") String query) { + return gameFacade.findByStatusAndUserPaginated2(status, new Pageable(page, size, query)); + } + /** * Count games by status * From 4060fce360995486e4ef5fa938b5128778c22e2a Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:28:22 +0200 Subject: [PATCH 02/11] Restore old query, add paginated query params --- .../java/com/wegas/core/ejb/GameFacade.java | 23 ++++++++++++------- .../com/wegas/core/rest/GameController.java | 12 ---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java index b4b9ffff7f..2fd0d53ac9 100644 --- a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java +++ b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java @@ -434,15 +434,20 @@ public Page findByStatusAndUserPaginated(Game.Status status, Pageable page gameRoot.get("id").in(new ArrayList<>(filteredGMatrix.keySet())) ); - - if (pageable.getQuery() != null) { - String lowerCaseQuery = pageable.getQuery().toLowerCase(); - Join gameModelJoin = gameRoot.join("gameModel"); - whereClause = criteriaBuilder.and(whereClause, criteriaBuilder.or( - criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + lowerCaseQuery + "%"), - criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + lowerCaseQuery + "%") - )); + ListIterator queryParamsIterator = pageable.getSplitQuery().listIterator(); + + while (queryParamsIterator.hasNext()) { + String param = queryParamsIterator.next(); + ParameterExpression queryParameter = criteriaBuilder.parameter(String.class); + if (!param.isEmpty()) { + Join gameModelJoin = gameRoot.join("gameModel"); + whereClause = criteriaBuilder.and(whereClause, criteriaBuilder.or( + criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + param.toLowerCase() + "%"), + criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + param.toLowerCase() + "%") + )); + } } + query.where(whereClause); int total = getEntityManager().createQuery(query).getResultList().size(); @@ -485,6 +490,8 @@ public Collection findByStatusAndUser(Game.Status status) { List gStatuses = new ArrayList<>(); gStatuses.add(status); + Map> gMatrix = this.getPermissionMatrix(gStatuses); + ArrayList games = new ArrayList<>(); for (Map.Entry> entry : gMatrix.entrySet()) { diff --git a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java index aedb3e0344..92adcb7579 100644 --- a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java +++ b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java @@ -225,18 +225,6 @@ public Page paginatedGames(@PathParam("status") final Game.Status status, @QueryParam("query") String query) { return gameFacade.findByStatusAndUserPaginated(status, new Pageable(page, size, query)); } - /** - * Get all games with given status paginated - * - */ - @GET - @Path("status/{status: [A-Z]*}/Paginated2") - public Page paginatedGames2(@PathParam("status") final Game.Status status, - @QueryParam("page") int page, - @QueryParam("size") int size, - @QueryParam("query") String query) { - return gameFacade.findByStatusAndUserPaginated2(status, new Pageable(page, size, query)); - } /** * Count games by status From f3fc52bd08a3f568e34bcae67a8c77984afbc22d Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Fri, 3 May 2024 17:56:29 +0200 Subject: [PATCH 03/11] Uncomplete pagination for Dev tests --- .github/workflows/maven.yml | 1 + .../src/main/node/wegas-lobby/src/API/api.ts | 23 +++--- .../node/wegas-lobby/src/API/restClient.ts | 37 ++++++++-- .../src/components/trainer/TrainerTab.tsx | 71 +++++++++++++++++-- .../src/selectors/wegasSelector.ts | 2 + .../node/wegas-lobby/src/store/slices/game.ts | 22 ++++++ .../wegas-lobby/src/store/slices/gameModel.ts | 10 +++ .../java/com/wegas/core/ejb/GameFacade.java | 8 +-- 8 files changed, 146 insertions(+), 28 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index eb8aa7cf55..89683e69c2 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -12,6 +12,7 @@ on: branches: - master - dev + - WEG-11 tags: - "v*" diff --git a/wegas-app/src/main/node/wegas-lobby/src/API/api.ts b/wegas-app/src/main/node/wegas-lobby/src/API/api.ts index 780561f236..8111218624 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/API/api.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/API/api.ts @@ -27,15 +27,14 @@ import { getStore, WegasLobbyState } from '../store/store'; import { CHANNEL_PREFIX, getPusherClient, initPusherSocket } from '../websocket/websocket'; import { entityIs, entityIsException } from './entityHelper'; import { - IAccountWithPerm, - IAuthenticationInformation, - IGameAdminWithTeams, - IJpaAuthentication, - IRoleWithPermissions, - IUserPage, - IUserWithAccounts, - PlayerToGameModel, - WegasLobbyRestClient, + IAccountWithPerm, + IAuthenticationInformation, + IGameAdminWithTeams, + IJpaAuthentication, IPage, + IRoleWithPermissions, + IUserWithAccounts, + PlayerToGameModel, + WegasLobbyRestClient, } from './restClient'; const logger = getLogger('api'); @@ -363,7 +362,7 @@ export const getAllUsers = createAsyncThunk( export const getPaginatedUsers = createAsyncThunk( 'user/getPaginated', - async (payload: {page: number, size: number, query: string}): Promise => { + async (payload: {page: number, size: number, query: string}): Promise> => { return await restClient.UserController.getPaginatedUsers(payload.page, payload.size, payload.query); }, ); @@ -608,6 +607,10 @@ export const getGames = createAsyncThunk('game/getGames', async (status: IGameWi return await restClient.GameController.getGames(status); }); +export const getGamesPaginated = createAsyncThunk('game/getGamesPaginated', async (payload: {status: IGameWithId['status'], page: number, size: number, query: string}) => { + return await restClient.GameController.getGamesPaginated(payload.status, payload.page, payload.size, payload.query); +}); + export const changeGameStatus = createAsyncThunk( 'game/changeStatus', async ({ gameId, status }: { gameId: number; status: IGameWithId['status'] }) => { diff --git a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts index af0f92b797..151c780cc3 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts @@ -35,6 +35,13 @@ export interface WegasErrorMessage { export type WegasRuntimeException = WegasErrorMessage; +export type IPage = { + total: number; + page: number; + pageSize: number; + pageContent: T[]; +} + export interface OnlineUser { fullname: string; email: string; @@ -57,18 +64,19 @@ export type IRoleWithPermissions = IRoleWithId & { permissions?: IPermissionWithId[]; }; -export type IUserPage = { - total: number; - page: number; - pageSize: number; - pageContent:IUserWithAccounts[]; -}; +// export type IUserPage = { +// total: number; +// page: number; +// pageSize: number; +// pageContent: IUserWithAccounts[]; +// }; export type IUserWithAccounts = IUserWithId & { accounts?: IAccountWithPerm[]; permissions?: IPermissionWithId[]; roles?: IRoleWithPermissions[]; }; + export interface IJpaAuthentication { '@class': 'JpaAuthentication'; mandatoryMethod: HashMethod; @@ -182,6 +190,7 @@ export interface IGameAdminTeam { declaredSize: number; players?: IGameAdminPlayer[]; } + export interface IGameAdminWithTeams extends IGameAdminWithId { teams?: IGameAdminTeam[]; effectiveCount: number; @@ -558,7 +567,7 @@ export const WegasLobbyRestClient = function ( }, getPaginatedUsers: (page: number, size: number, query: string) => { const path = `${baseUrl}/Shadow/User/Paginated?page=${page}&size=${size}&query=${query}`; - return sendJsonRequest('GET', path, undefined, errorHandler); + return sendJsonRequest>('GET', path, undefined, errorHandler); }, getUser: (userId: number) => { const path = `${baseUrl}/User/${userId}`; @@ -726,6 +735,20 @@ export const WegasLobbyRestClient = function ( errorHandler, ); }, + getGamesPaginated: ( + status: IGameWithId['status'], + page: number, + size: number, + query: string, + ) => { + const path = `${baseUrl}/Lobby/GameModel/Game/status/${status}/Paginated?page=${page}&size=${size}&query=${query}`; + return sendJsonRequest>( + 'GET', + path, + undefined, + errorHandler + ) + }, changeStatus: (gameId: number, status: IGameWithId['status']) => { const path = `${baseUrl}/Lobby/GameModel/Game/${gameId}/status/${status}`; return sendJsonRequest('PUT', path, undefined, errorHandler); diff --git a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx index f6a5b106c0..b8d2a3067a 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx +++ b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx @@ -10,15 +10,15 @@ import { css } from '@emotion/css'; import { faPlusCircle } from '@fortawesome/free-solid-svg-icons'; import { uniq } from 'lodash'; import * as React from 'react'; -import {useMatch, useResolvedPath} from 'react-router-dom'; +import { useMatch, useResolvedPath } from 'react-router-dom'; import { IAbstractAccount, IGameModelWithId, IGameWithId } from 'wegas-ts-api'; -import { getGames, getShadowUserByIds } from '../../API/api'; +import { getGamesPaginated, getShadowUserByIds } from '../../API/api'; import { getDisplayName, mapByKey, match } from '../../helper'; import useTranslations from '../../i18n/I18nContext'; import { useLocalStorageState } from '../../preferences'; import { useAccountsByUserIds, useCurrentUser } from '../../selectors/userSelector'; import { MINE_OR_ALL, useGames } from '../../selectors/wegasSelector'; -import { useAppDispatch } from '../../store/hooks'; +import {useAppDispatch} from '../../store/hooks'; import { WindowedContainer } from '../common/CardContainer'; import DebouncedInput from '../common/DebouncedInput'; import DropDownMenu, { itemStyle } from '../common/DropDownMenu'; @@ -32,6 +32,7 @@ import { successColor } from '../styling/color'; import { panelPadding } from '../styling/style'; import CreateGame from './CreateGame'; import GameCard from './GameCard'; +import Checkbox from "../common/Checkbox"; interface SortBy { createdByName: string; @@ -94,6 +95,11 @@ export default function TrainerTab(): JSX.Element { // }, []); const [filter, setFilter] = React.useState(''); + const [page, setPage] = React.useState(1); + const [pageSize, setPageSize] = React.useState(20); + + const onNextPage = () => setPage(page setPage(page>1?page - 1:1); const onFilterChange = React.useCallback((filter: string) => { setFilter(filter); @@ -111,10 +117,36 @@ export default function TrainerTab(): JSX.Element { React.useEffect(() => { if (status === 'NOT_INITIALIZED') { - dispatch(getGames(statusFilter)); + dispatch( + getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), + ); + console.log(games.totalResults) } }, [status, dispatch, statusFilter]); + React.useEffect(() => { + if (page !== 1) { + setPage(1); + } else { + dispatch( + getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), + ); + console.log(games.totalResults) + } + }, [filter, pageSize]); + + React.useEffect(() => { + dispatch( + getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), + ); + }, [page]); + + // React.useEffect(() => { + // if (status === 'NOT_INITIALIZED') { + // dispatch(getGames(statusFilter)); + // } + // }, [status, dispatch, statusFilter]); + const userIds = uniq( games.gamesAndGameModels.flatMap(data => data.game.createdById != null ? [data.game.createdById] : [], @@ -142,7 +174,7 @@ export default function TrainerTab(): JSX.Element { }, [isAdmin, accountsState, dispatch]); // Detect any gameModel id in URL - const resolvedPath = useResolvedPath("./"); + const resolvedPath = useResolvedPath('./'); const match = useMatch<'id', string>(`${resolvedPath.pathname}:id/*`); const selectedId = Number(match?.params.id) || undefined; @@ -264,6 +296,35 @@ export default function TrainerTab(): JSX.Element { /> + +

+

+ + {page}/{games.totalResults > 0 ? Math.ceil(games.totalResults/pageSize) : 1} + +

+
+
+ setPageSize(newValue?20:pageSize)} /> + setPageSize(newValue?50:pageSize)} /> + setPageSize(newValue?100:pageSize)} /> +
+ + {status === 'READY' ? ( <> ; teams: Record; joinStatus: Record; + totalResults: number; } const initialState: GameState = { @@ -31,6 +32,7 @@ const initialState: GameState = { games: {}, teams: {}, joinStatus: {}, + totalResults: 0, }; const slice = createSlice({ @@ -106,6 +108,26 @@ const slice = createSlice({ ), }; }) + .addCase(API.getGamesPaginated.pending, (state, action) => { + const status = action.meta.arg.status; + state.status[status] = 'LOADING'; + }) + .addCase(API.getGamesPaginated.fulfilled, (state, action) => { + const status = action.meta.arg.status; + state.status[status] = 'READY'; + + state.totalResults = action.payload.total; + + state.games = { + ...mapById( + action.payload.pageContent.map(game => { + const g = { ...game }; + delete g.gameModel; + return g; + }), + ), + }; + }) .addCase(API.getAllTeams.pending, (state, action) => { state.teams[action.meta.arg] = 'LOADING'; }) diff --git a/wegas-app/src/main/node/wegas-lobby/src/store/slices/gameModel.ts b/wegas-app/src/main/node/wegas-lobby/src/store/slices/gameModel.ts index c56d498bc0..64702b9923 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/store/slices/gameModel.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/store/slices/gameModel.ts @@ -112,6 +112,16 @@ const slice = createSlice({ } }); }) + .addCase(API.getGamesPaginated.fulfilled, (state, action) => { + state.games = []; + state.gameModels = []; + action.payload.pageContent.forEach(game => { + if (game.gameModel != null) { + state.gameModels[game.gameModel.id] = game.gameModel; + updateParent(state, game.gameModel.id, game.id); + } + }); + }) .addCase(API.getPlayers.fulfilled, (state, action) => { action.payload.forEach(data => { if (data.gameModel != null) { diff --git a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java index 2fd0d53ac9..8b67d0c79e 100644 --- a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java +++ b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java @@ -428,19 +428,15 @@ public Page findByStatusAndUserPaginated(Game.Status status, Pageable page Root gameRoot = query.from(Game.class); query.select(gameRoot); - Predicate whereClause = criteriaBuilder.and( criteriaBuilder.equal(gameRoot.get("status"), status), gameRoot.get("id").in(new ArrayList<>(filteredGMatrix.keySet())) ); - ListIterator queryParamsIterator = pageable.getSplitQuery().listIterator(); - - while (queryParamsIterator.hasNext()) { - String param = queryParamsIterator.next(); + for (String param : pageable.getSplitQuery()) { ParameterExpression queryParameter = criteriaBuilder.parameter(String.class); if (!param.isEmpty()) { - Join gameModelJoin = gameRoot.join("gameModel"); + Join gameModelJoin = gameRoot.join("gameModel", JoinType.INNER); whereClause = criteriaBuilder.and(whereClause, criteriaBuilder.or( criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + param.toLowerCase() + "%"), criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + param.toLowerCase() + "%") From 03f0a2e9085c9ae5afed116ddc3e28ddd9a9fc13 Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Fri, 24 May 2024 16:36:42 +0200 Subject: [PATCH 04/11] Add author query, clean TrainerTab --- .../node/wegas-lobby/src/API/restClient.ts | 2 +- .../src/components/trainer/TrainerTab.tsx | 135 +++++++----------- .../src/main/node/wegas-lobby/src/i18n/fr.ts | 2 +- .../java/com/wegas/core/ejb/GameFacade.java | 42 ++---- .../com/wegas/core/rest/GameController.java | 6 +- 5 files changed, 67 insertions(+), 120 deletions(-) diff --git a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts index 151c780cc3..de55d304f6 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts @@ -739,7 +739,7 @@ export const WegasLobbyRestClient = function ( status: IGameWithId['status'], page: number, size: number, - query: string, + query: string ) => { const path = `${baseUrl}/Lobby/GameModel/Game/status/${status}/Paginated?page=${page}&size=${size}&query=${query}`; return sendJsonRequest>( diff --git a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx index b8d2a3067a..b8cdbb0139 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx +++ b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx @@ -11,14 +11,13 @@ import { faPlusCircle } from '@fortawesome/free-solid-svg-icons'; import { uniq } from 'lodash'; import * as React from 'react'; import { useMatch, useResolvedPath } from 'react-router-dom'; -import { IAbstractAccount, IGameModelWithId, IGameWithId } from 'wegas-ts-api'; +import { IGameModelWithId, IGameWithId } from 'wegas-ts-api'; import { getGamesPaginated, getShadowUserByIds } from '../../API/api'; -import { getDisplayName, mapByKey, match } from '../../helper'; import useTranslations from '../../i18n/I18nContext'; import { useLocalStorageState } from '../../preferences'; import { useAccountsByUserIds, useCurrentUser } from '../../selectors/userSelector'; import { MINE_OR_ALL, useGames } from '../../selectors/wegasSelector'; -import {useAppDispatch} from '../../store/hooks'; +import { useAppDispatch } from '../../store/hooks'; import { WindowedContainer } from '../common/CardContainer'; import DebouncedInput from '../common/DebouncedInput'; import DropDownMenu, { itemStyle } from '../common/DropDownMenu'; @@ -27,32 +26,11 @@ import FitSpace from '../common/FitSpace'; import Flex from '../common/Flex'; import IconButton from '../common/IconButton'; import InlineLoading from '../common/InlineLoading'; -import SortBy, { SortByOption } from '../common/SortBy'; import { successColor } from '../styling/color'; import { panelPadding } from '../styling/style'; import CreateGame from './CreateGame'; import GameCard from './GameCard'; -import Checkbox from "../common/Checkbox"; - -interface SortBy { - createdByName: string; - name: string; - createdTime: number; -} - -const matchSearch = - (accountMap: Record, search: string) => - ({ game, gameModel }: { game: IGameWithId; gameModel: IGameModelWithId }) => { - return match(search, regex => { - const username = game.createdById != null ? getDisplayName(accountMap[game.createdById]) : ''; - return ( - (gameModel.name && gameModel.name.match(regex) != null) || - (game.name && game.name.match(regex) != null) || - (game.token && game.token.match(regex) != null) || - username.match(regex) != null - ); - }); - }; +import Checkbox from '../common/Checkbox'; export default function TrainerTab(): JSX.Element { const i18n = useTranslations(); @@ -82,14 +60,6 @@ export default function TrainerTab(): JSX.Element { const [viewMode, setViewMode] = React.useState<'EXPANDED' | 'COLLAPSED'>('COLLAPSED'); - const [sortBy, setSortBy] = useLocalStorageState<{ key: keyof SortBy; asc: boolean }>( - 'trainer-sortby', - { - key: 'createdTime', - asc: false, - }, - ); - // const onSortChange = React.useCallback(({ key, asc }: { key: keyof SortBy; asc: boolean }) => { // setSortBy({ key, asc }); // }, []); @@ -98,21 +68,13 @@ export default function TrainerTab(): JSX.Element { const [page, setPage] = React.useState(1); const [pageSize, setPageSize] = React.useState(20); - const onNextPage = () => setPage(page setPage(page>1?page - 1:1); + const onNextPage = () => setPage(page < games.totalResults / pageSize ? page + 1 : page); + const onPreviousPage = () => setPage(page > 1 ? page - 1 : 1); const onFilterChange = React.useCallback((filter: string) => { setFilter(filter); }, []); - const sortOptions: SortByOption[] = [ - { key: 'createdTime', label: i18n.date }, - { key: 'name', label: i18n.name }, - ]; - if (isAdmin) { - sortOptions.push({ key: 'createdByName', label: i18n.createdBy }); - } - const status = games.status[statusFilter]; React.useEffect(() => { @@ -120,7 +82,6 @@ export default function TrainerTab(): JSX.Element { dispatch( getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), ); - console.log(games.totalResults) } }, [status, dispatch, statusFilter]); @@ -131,13 +92,12 @@ export default function TrainerTab(): JSX.Element { dispatch( getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), ); - console.log(games.totalResults) } }, [filter, pageSize]); React.useEffect(() => { dispatch( - getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), + getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), ); }, [page]); @@ -165,7 +125,6 @@ export default function TrainerTab(): JSX.Element { ); const accountsState = useAccountsByUserIds(userIds); - const accounts = mapByKey(accountsState.accounts, 'parentId'); React.useEffect(() => { if (isAdmin && accountsState.unknownUsers.length > 0) { @@ -184,24 +143,6 @@ export default function TrainerTab(): JSX.Element { } else { const selected = games.gamesAndGameModels.find(ggm => ggm.gameModel.id === selectedId); - const filtered = filter - ? games.gamesAndGameModels.filter(matchSearch(accounts, filter)) - : games.gamesAndGameModels; - const sorted = filtered.sort((a, b) => { - const reverse = sortBy.asc ? 1 : -1; - if (sortBy.key === 'createdTime') { - return reverse * (a.game.createdTime! - b.game.createdTime!); - } else if (sortBy.key === 'name') { - const aName = - a.game.createdById != null ? getDisplayName(accounts[a.game.createdById]) : ''; - const bName = - b.game.createdById != null ? getDisplayName(accounts[b.game.createdById]) : ''; - - return reverse * aName.localeCompare(bName); - } - return 0; - }); - const statusFilterEntries: { value: IGameWithId['status']; label: React.ReactNode }[] = [ { value: 'LIVE', @@ -283,7 +224,6 @@ export default function TrainerTab(): JSX.Element { > {i18n.createGame} - {dropDownStatus} {dropDownMine} @@ -297,38 +237,61 @@ export default function TrainerTab(): JSX.Element { +
-
-

+ > +

{`${games.totalResults} ${i18n.games}`}

+
+
+

- {page}/{games.totalResults > 0 ? Math.ceil(games.totalResults/pageSize) : 1} + {page}/{games.totalResults > 0 ? Math.ceil(games.totalResults / pageSize) : 1}

-
- setPageSize(newValue?20:pageSize)} /> - setPageSize(newValue?50:pageSize)} /> - setPageSize(newValue?100:pageSize)} /> +
+ setPageSize(newValue ? 20 : pageSize)} + /> + setPageSize(newValue ? 50 : pageSize)} + /> + setPageSize(newValue ? 100 : pageSize)} + />
{status === 'READY' ? ( <> {filter ? i18n.noGamesFound : i18n.noGames}} > diff --git a/wegas-app/src/main/node/wegas-lobby/src/i18n/fr.ts b/wegas-app/src/main/node/wegas-lobby/src/i18n/fr.ts index 4f061aa4f1..0512a1fc36 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/i18n/fr.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/i18n/fr.ts @@ -95,7 +95,7 @@ export const fr: WegasTranslations = { gameNotFound: "clé d'accès invalide", // Game: 'Partie', - games: 'parites', + games: 'parties', allGames: 'Toutes les parties', archive: 'achiver', restore: 'restaurer', diff --git a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java index 8b67d0c79e..5e1046dfed 100644 --- a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java +++ b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java @@ -382,30 +382,6 @@ public List findAll(final Game.Status status) { .setParameter("status", status).getResultList(); } -// /** -// * Find all games with given ids and status (was used for faster findByStatusAndUser fetch) -// * -// * @param ids ids of games to fetch -// * @param status status of games to fetch -// * @return all games which match given ids and status -// */ -// public List findByIdsAndStatus(final List ids, final Game.Status status) { -// final CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder(); -// final CriteriaQuery query = criteriaBuilder.createQuery(Game.class); -// Root gameRoot = query.from(Game.class); -// -// query.where( -// criteriaBuilder.and( -// gameRoot.get("id").in(ids), -// criteriaBuilder.equal(gameRoot.get("status"), status) -// ) -// ); -// -// TypedQuery typedQuery = getEntityManager().createQuery(query); -// -// return typedQuery.getResultList(); -// } - /** * Get all paginated games with the given status which are accessible to the current user * @@ -430,21 +406,29 @@ public Page findByStatusAndUserPaginated(Game.Status status, Pageable page Predicate whereClause = criteriaBuilder.and( criteriaBuilder.equal(gameRoot.get("status"), status), + // Maximum in values for psql: 32767 gameRoot.get("id").in(new ArrayList<>(filteredGMatrix.keySet())) ); for (String param : pageable.getSplitQuery()) { ParameterExpression queryParameter = criteriaBuilder.parameter(String.class); if (!param.isEmpty()) { - Join gameModelJoin = gameRoot.join("gameModel", JoinType.INNER); + Join gameModelJoin = gameRoot.join("gameModel", JoinType.INNER); + + // Only if admin + Join userJoin = gameRoot.join("createdBy", JoinType.INNER); + Join abstractAccountJoin = userJoin.join("accounts", JoinType.INNER); + Expression exp = criteriaBuilder.concat(criteriaBuilder.lower(criteriaBuilder.coalesce(abstractAccountJoin.get("firstname"), "")), " "); + exp = criteriaBuilder.concat(exp, criteriaBuilder.lower(criteriaBuilder.coalesce(abstractAccountJoin.get("lastname"), ""))); + whereClause = criteriaBuilder.and(whereClause, criteriaBuilder.or( - criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + param.toLowerCase() + "%"), - criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + param.toLowerCase() + "%") - )); + criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + param.toLowerCase() + "%"), + criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + param.toLowerCase() + "%"), + criteriaBuilder.like(criteriaBuilder.lower(exp), "%" + param.toLowerCase() + "%"))); } } - query.where(whereClause); + query.orderBy(criteriaBuilder.desc(gameRoot.get("createdTime"))); int total = getEntityManager().createQuery(query).getResultList().size(); TypedQuery listQuery = pageable.paginateQuery(getEntityManager().createQuery(query)); diff --git a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java index 92adcb7579..c75864978a 100644 --- a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java +++ b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java @@ -220,9 +220,9 @@ public Collection findByStatus(@PathParam("status") final Game.Status stat @GET @Path("status/{status: [A-Z]*}/Paginated") public Page paginatedGames(@PathParam("status") final Game.Status status, - @QueryParam("page") int page, - @QueryParam("size") int size, - @QueryParam("query") String query) { + @QueryParam("page") int page, + @QueryParam("size") int size, + @QueryParam("query") String query) { return gameFacade.findByStatusAndUserPaginated(status, new Pageable(page, size, query)); } From 4d57dca831cfedcf9ba778b4431b9e0e1adb4dfc Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Fri, 24 May 2024 16:37:30 +0200 Subject: [PATCH 05/11] Remove branch from workflow --- .github/workflows/maven.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 89683e69c2..eb8aa7cf55 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -12,7 +12,6 @@ on: branches: - master - dev - - WEG-11 tags: - "v*" From 8457c86bb97976fb4bab8aab4f53f5664837287b Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Mon, 27 May 2024 11:01:22 +0200 Subject: [PATCH 06/11] add statusFilter to useEffect --- .../src/components/trainer/TrainerTab.tsx | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx index b8cdbb0139..69ef64e6fd 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx +++ b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx @@ -93,7 +93,7 @@ export default function TrainerTab(): JSX.Element { getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), ); } - }, [filter, pageSize]); + }, [filter, pageSize, statusFilter]); React.useEffect(() => { dispatch( @@ -101,11 +101,6 @@ export default function TrainerTab(): JSX.Element { ); }, [page]); - // React.useEffect(() => { - // if (status === 'NOT_INITIALIZED') { - // dispatch(getGames(statusFilter)); - // } - // }, [status, dispatch, statusFilter]); const userIds = uniq( games.gamesAndGameModels.flatMap(data => @@ -253,11 +248,7 @@ export default function TrainerTab(): JSX.Element {

{`${games.totalResults} ${i18n.games}`}

-

+

{page}/{games.totalResults > 0 ? Math.ceil(games.totalResults / pageSize) : 1} From 58c9bd174e789b6ab5b98dd8dff6e37bd36d5640 Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Sun, 9 Jun 2024 14:43:24 +0200 Subject: [PATCH 07/11] Fix mine/all count --- .../src/components/trainer/TrainerTab.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx index 69ef64e6fd..5aa7264bf4 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx +++ b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx @@ -67,8 +67,9 @@ export default function TrainerTab(): JSX.Element { const [filter, setFilter] = React.useState(''); const [page, setPage] = React.useState(1); const [pageSize, setPageSize] = React.useState(20); + const [totalResults, settotalResults] = React.useState(0); - const onNextPage = () => setPage(page < games.totalResults / pageSize ? page + 1 : page); + const onNextPage = () => setPage(page < totalResults / pageSize ? page + 1 : page); const onPreviousPage = () => setPage(page > 1 ? page - 1 : 1); const onFilterChange = React.useCallback((filter: string) => { @@ -83,6 +84,7 @@ export default function TrainerTab(): JSX.Element { getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), ); } + settotalResults(mineFilter === 'MINE' ? games.gamesAndGameModels.length : games.totalResults); }, [status, dispatch, statusFilter]); React.useEffect(() => { @@ -93,7 +95,8 @@ export default function TrainerTab(): JSX.Element { getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), ); } - }, [filter, pageSize, statusFilter]); + settotalResults(mineFilter === 'MINE' ? games.gamesAndGameModels.length : games.totalResults); + }, [filter, pageSize, statusFilter, mineFilter]); React.useEffect(() => { dispatch( @@ -101,7 +104,6 @@ export default function TrainerTab(): JSX.Element { ); }, [page]); - const userIds = uniq( games.gamesAndGameModels.flatMap(data => data.game.createdById != null ? [data.game.createdById] : [], @@ -245,12 +247,12 @@ export default function TrainerTab(): JSX.Element { alignContent: 'flex-start', })} > -

{`${games.totalResults} ${i18n.games}`}

+

{`${totalResults} ${i18n.games}`}

- {page}/{games.totalResults > 0 ? Math.ceil(games.totalResults / pageSize) : 1} + {page}/{games.totalResults > 0 ? Math.ceil(totalResults / pageSize) : 1}

From 8c4694d95f65018a40d90f6dffe5f38425dfe6e4 Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Sun, 9 Jun 2024 14:46:02 +0200 Subject: [PATCH 08/11] Small cleanup --- wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts index de55d304f6..59476fef77 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts @@ -64,13 +64,6 @@ export type IRoleWithPermissions = IRoleWithId & { permissions?: IPermissionWithId[]; }; -// export type IUserPage = { -// total: number; -// page: number; -// pageSize: number; -// pageContent: IUserWithAccounts[]; -// }; - export type IUserWithAccounts = IUserWithId & { accounts?: IAccountWithPerm[]; permissions?: IPermissionWithId[]; From d1f7c80382b0f35d6ec73d9f360af5fa8c7baf96 Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:53:51 +0200 Subject: [PATCH 09/11] Add pagination tests --- .../com/wegas/core/ejb/GameFacadeTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java b/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java index 26a5e831cd..c95293d6ac 100644 --- a/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java +++ b/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java @@ -14,10 +14,14 @@ import com.wegas.core.persistence.variable.primitive.BooleanInstance; import com.wegas.core.rest.GameController; import com.wegas.core.rest.TeamController; +import com.wegas.core.rest.util.pagination.Page; +import com.wegas.core.rest.util.pagination.Pageable; import com.wegas.test.arquillian.AbstractArquillianTest; import jakarta.inject.Inject; import org.junit.Assert; import static org.junit.Assert.*; + +import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +34,14 @@ public class GameFacadeTest extends AbstractArquillianTest { private static final Logger logger = LoggerFactory.getLogger(GameFacadeTest.class); + private Game game1; + + private Game game2; + + private static final String GAME_1 = "Game_1"; + + private static final String GAME_2 = "Game_2"; + @Inject private GameController gameController; @@ -299,4 +311,46 @@ public void leavingTeamNotPossible() throws CloneNotSupportedException { created = teamFacade.find(created.getId()); Assert.assertEquals(1, created.getPlayers().size()); } + + @Before + public void setupPaginated() throws Exception { + login(admin); + + game1 = new Game(GAME_1); + game1.setGameModel(scenario); + game2 = new Game(GAME_2); + game2.setGameModel(scenario); + + gameFacade.create(game1); + gameFacade.create(game2); + + game1.setStatus(Game.Status.LIVE); + game2.setStatus(Game.Status.LIVE); + + requestManager.clearEntities(); + } + + @Test + public void testFindAllGamesPaginated() { + Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE ,new Pageable(1, 10, "")); + // We expect 3 as the superclass creates one too + Assert.assertEquals(3L, paginatedGames.getTotal()); + Assert.assertTrue(paginatedGames.getPageContent().contains(game1)); + Assert.assertTrue(paginatedGames.getPageContent().contains(game2)); + } + + @Test + public void testFindAllGamesPaginatedFiltered() { + Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, new Pageable(1, 10, GAME_1)); + Assert.assertEquals(1L, paginatedGames.getTotal()); + Assert.assertTrue(paginatedGames.getPageContent().contains(game1)); + } + + @Test + public void testFindAllGamesPaginatedNone() { + Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, new Pageable(1, 10, "æøå")); + + Assert.assertEquals(0L, paginatedGames.getTotal()); + } + } From f774b9ba473ba4f25b80aaf5d642acd8a99c5aaf Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Wed, 19 Jun 2024 15:21:36 +0200 Subject: [PATCH 10/11] Fix client side sortBy and add callback on session creation --- .../src/components/trainer/CreateGame.tsx | 4 +++- .../src/components/trainer/TrainerTab.tsx | 12 +++++++++++- .../node/wegas-lobby/src/selectors/wegasSelector.ts | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/CreateGame.tsx b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/CreateGame.tsx index 63c9647564..3db8101cb4 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/CreateGame.tsx +++ b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/CreateGame.tsx @@ -25,9 +25,10 @@ import { defaultSelectStyles, mainButtonStyle } from '../styling/style'; interface CreateGameProps { close: () => void; + callback: () => void; } -export default function CreateGame({ close }: CreateGameProps): JSX.Element { +export default function CreateGame({ close, callback }: CreateGameProps): JSX.Element { const dispatch = useAppDispatch(); const i18n = useTranslations(); const { currentUser } = useCurrentUser(); @@ -48,6 +49,7 @@ export default function CreateGame({ close }: CreateGameProps): JSX.Element { if (a.meta.requestStatus === 'fulfilled') { close(); } + callback(); }); } }, [dispatch, gameModelId, name, close]); diff --git a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx index 5aa7264bf4..5c56668cdd 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx +++ b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx @@ -53,7 +53,7 @@ export default function TrainerTab(): JSX.Element { }, [isAdmin, statusFilter, setStatusFilter]); const games = useGames( - !isAdmin && statusFilter === 'DELETE' ? 'BIN' : statusFilter, //non-admin should never sees deleteds + !isAdmin && statusFilter === 'DELETE' ? 'BIN' : statusFilter, //non-admin should never see deleted currentUser != null ? currentUser.id : undefined, isAdmin ? mineFilter : 'MINE', // non-admin only see theirs ); @@ -199,6 +199,16 @@ export default function TrainerTab(): JSX.Element { close={() => { setViewMode('COLLAPSED'); }} + callback={() => + dispatch( + getGamesPaginated({ + status: statusFilter, + page: page, + size: pageSize, + query: filter, + }), + ) + } /> ) : null} diff --git a/wegas-app/src/main/node/wegas-lobby/src/selectors/wegasSelector.ts b/wegas-app/src/main/node/wegas-lobby/src/selectors/wegasSelector.ts index 1da6182b7d..a12dcdaa83 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/selectors/wegasSelector.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/selectors/wegasSelector.ts @@ -434,7 +434,7 @@ export const useGames = ( } } return []; - }); + }).sort((a, b) => b.game.createdTime - a.game.createdTime); return { gamesAndGameModels: gamesAndGameModels, From 5349ad9a4353df7985d871b942fa4dc0b10ba45f Mon Sep 17 00:00:00 2001 From: Mikkel Vestergaard <56039404+TehAwol@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:50:38 +0200 Subject: [PATCH 11/11] Add server side mine filter for admins --- .../src/main/node/wegas-lobby/src/API/api.ts | 4 ++-- .../node/wegas-lobby/src/API/restClient.ts | 5 +++-- .../src/components/trainer/TrainerTab.tsx | 20 +++++++------------ .../java/com/wegas/core/ejb/GameFacade.java | 20 ++++++++++++++----- .../com/wegas/core/rest/GameController.java | 5 +++-- .../com/wegas/core/ejb/GameFacadeTest.java | 15 +++++++++++--- 6 files changed, 42 insertions(+), 27 deletions(-) diff --git a/wegas-app/src/main/node/wegas-lobby/src/API/api.ts b/wegas-app/src/main/node/wegas-lobby/src/API/api.ts index 8111218624..e76a8e5210 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/API/api.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/API/api.ts @@ -607,8 +607,8 @@ export const getGames = createAsyncThunk('game/getGames', async (status: IGameWi return await restClient.GameController.getGames(status); }); -export const getGamesPaginated = createAsyncThunk('game/getGamesPaginated', async (payload: {status: IGameWithId['status'], page: number, size: number, query: string}) => { - return await restClient.GameController.getGamesPaginated(payload.status, payload.page, payload.size, payload.query); +export const getGamesPaginated = createAsyncThunk('game/getGamesPaginated', async (payload: {status: IGameWithId['status'], page: number, size: number, query: string, mine: boolean}) => { + return await restClient.GameController.getGamesPaginated(payload.status, payload.page, payload.size, payload.query, payload.mine); }); export const changeGameStatus = createAsyncThunk( diff --git a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts index 59476fef77..d1d2a0540a 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts +++ b/wegas-app/src/main/node/wegas-lobby/src/API/restClient.ts @@ -732,9 +732,10 @@ export const WegasLobbyRestClient = function ( status: IGameWithId['status'], page: number, size: number, - query: string + query: string, + mine: boolean, ) => { - const path = `${baseUrl}/Lobby/GameModel/Game/status/${status}/Paginated?page=${page}&size=${size}&query=${query}`; + const path = `${baseUrl}/Lobby/GameModel/Game/status/${status}/Paginated?page=${page}&size=${size}&query=${query}&mine=${mine}`; return sendJsonRequest>( 'GET', path, diff --git a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx index 5c56668cdd..8a12cebba8 100644 --- a/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx +++ b/wegas-app/src/main/node/wegas-lobby/src/components/trainer/TrainerTab.tsx @@ -60,16 +60,11 @@ export default function TrainerTab(): JSX.Element { const [viewMode, setViewMode] = React.useState<'EXPANDED' | 'COLLAPSED'>('COLLAPSED'); - // const onSortChange = React.useCallback(({ key, asc }: { key: keyof SortBy; asc: boolean }) => { - // setSortBy({ key, asc }); - // }, []); - const [filter, setFilter] = React.useState(''); const [page, setPage] = React.useState(1); const [pageSize, setPageSize] = React.useState(20); - const [totalResults, settotalResults] = React.useState(0); - const onNextPage = () => setPage(page < totalResults / pageSize ? page + 1 : page); + const onNextPage = () => setPage(page < games.totalResults / pageSize ? page + 1 : page); const onPreviousPage = () => setPage(page > 1 ? page - 1 : 1); const onFilterChange = React.useCallback((filter: string) => { @@ -81,10 +76,9 @@ export default function TrainerTab(): JSX.Element { React.useEffect(() => { if (status === 'NOT_INITIALIZED') { dispatch( - getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), + getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter, mine: mineFilter === 'MINE' }), ); } - settotalResults(mineFilter === 'MINE' ? games.gamesAndGameModels.length : games.totalResults); }, [status, dispatch, statusFilter]); React.useEffect(() => { @@ -92,15 +86,14 @@ export default function TrainerTab(): JSX.Element { setPage(1); } else { dispatch( - getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), + getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter, mine: mineFilter === 'MINE' }), ); } - settotalResults(mineFilter === 'MINE' ? games.gamesAndGameModels.length : games.totalResults); }, [filter, pageSize, statusFilter, mineFilter]); React.useEffect(() => { dispatch( - getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter }), + getGamesPaginated({ status: statusFilter, page: page, size: pageSize, query: filter, mine: mineFilter === 'MINE' }), ); }, [page]); @@ -206,6 +199,7 @@ export default function TrainerTab(): JSX.Element { page: page, size: pageSize, query: filter, + mine: mineFilter === 'MINE', }), ) } @@ -257,12 +251,12 @@ export default function TrainerTab(): JSX.Element { alignContent: 'flex-start', })} > -

{`${totalResults} ${i18n.games}`}

+

{`${games.totalResults} ${i18n.games}`}

- {page}/{games.totalResults > 0 ? Math.ceil(totalResults / pageSize) : 1} + {page}/{games.totalResults > 0 ? Math.ceil(games.totalResults / pageSize) : 1}

diff --git a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java index 5e1046dfed..aa0cda8635 100644 --- a/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java +++ b/wegas-core/src/main/java/com/wegas/core/ejb/GameFacade.java @@ -300,7 +300,7 @@ public void remove(final Game entity) { // This is for retrocompatibility w/ game models that do not habe DebugGame if (entity.getGameModel().getGames().size() <= 1 - && !(entity.getGameModel().getGames().get(0) instanceof DebugGame)) {// This is for retrocompatibility w/ game models that do not habe DebugGame + && !(entity.getGameModel().getGames().get(0) instanceof DebugGame)) { gameModelFacade.remove(entity.getGameModel()); } else { getEntityManager().remove(entity); @@ -386,10 +386,11 @@ public List findAll(final Game.Status status) { * Get all paginated games with the given status which are accessible to the current user * * @param status status {@link Game.Status#LIVE} {@link Game.Status#BIN} {@link Game.Status#DELETE} + * @param mine boolean return currentUser's or all games (admin only) * @param pageable * @return all games paginated */ - public Page findByStatusAndUserPaginated(Game.Status status, Pageable pageable) { + public Page findByStatusAndUserPaginated(Game.Status status, boolean mine, Pageable pageable) { List gStatuses = new ArrayList<>(); gStatuses.add(status); @@ -410,23 +411,32 @@ public Page findByStatusAndUserPaginated(Game.Status status, Pageable page gameRoot.get("id").in(new ArrayList<>(filteredGMatrix.keySet())) ); + for (String param : pageable.getSplitQuery()) { ParameterExpression queryParameter = criteriaBuilder.parameter(String.class); if (!param.isEmpty()) { Join gameModelJoin = gameRoot.join("gameModel", JoinType.INNER); - // Only if admin Join userJoin = gameRoot.join("createdBy", JoinType.INNER); Join abstractAccountJoin = userJoin.join("accounts", JoinType.INNER); Expression exp = criteriaBuilder.concat(criteriaBuilder.lower(criteriaBuilder.coalesce(abstractAccountJoin.get("firstname"), "")), " "); exp = criteriaBuilder.concat(exp, criteriaBuilder.lower(criteriaBuilder.coalesce(abstractAccountJoin.get("lastname"), ""))); whereClause = criteriaBuilder.and(whereClause, criteriaBuilder.or( - criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + param.toLowerCase() + "%"), - criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + param.toLowerCase() + "%"), + criteriaBuilder.like(criteriaBuilder.lower(gameRoot.get("name")), "%" + param.toLowerCase() + "%"), + criteriaBuilder.like(criteriaBuilder.lower(gameModelJoin.get("name")), "%" + param.toLowerCase() + "%"), criteriaBuilder.like(criteriaBuilder.lower(exp), "%" + param.toLowerCase() + "%"))); } } + + // By default admins retrieve all, mine filter is reserved for them + if (mine && requestManager.isAdmin()) { + User user = userFacade.getCurrentUser(); + whereClause = criteriaBuilder.and(whereClause, criteriaBuilder.and( + criteriaBuilder.equal(gameRoot.get("createdBy"), user) + )); + } + query.where(whereClause); query.orderBy(criteriaBuilder.desc(gameRoot.get("createdTime"))); diff --git a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java index c75864978a..f520383096 100644 --- a/wegas-core/src/main/java/com/wegas/core/rest/GameController.java +++ b/wegas-core/src/main/java/com/wegas/core/rest/GameController.java @@ -222,8 +222,9 @@ public Collection findByStatus(@PathParam("status") final Game.Status stat public Page paginatedGames(@PathParam("status") final Game.Status status, @QueryParam("page") int page, @QueryParam("size") int size, - @QueryParam("query") String query) { - return gameFacade.findByStatusAndUserPaginated(status, new Pageable(page, size, query)); + @QueryParam("query") String query, + @QueryParam("mine") boolean mine) { + return gameFacade.findByStatusAndUserPaginated(status, mine, new Pageable(page, size, query)); } /** diff --git a/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java b/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java index c95293d6ac..7f857ede58 100644 --- a/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java +++ b/wegas-core/src/test/java/com/wegas/core/ejb/GameFacadeTest.java @@ -332,23 +332,32 @@ public void setupPaginated() throws Exception { @Test public void testFindAllGamesPaginated() { - Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE ,new Pageable(1, 10, "")); + Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, false ,new Pageable(1, 10, "")); // We expect 3 as the superclass creates one too Assert.assertEquals(3L, paginatedGames.getTotal()); Assert.assertTrue(paginatedGames.getPageContent().contains(game1)); Assert.assertTrue(paginatedGames.getPageContent().contains(game2)); } + @Test + public void testFindAllGamesPaginatedMine() { + Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, true ,new Pageable(1, 10, "")); + // We expect 2 as the superclass provided game isn't ours + Assert.assertEquals(2L, paginatedGames.getTotal()); + Assert.assertTrue(paginatedGames.getPageContent().contains(game1)); + Assert.assertTrue(paginatedGames.getPageContent().contains(game2)); + } + @Test public void testFindAllGamesPaginatedFiltered() { - Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, new Pageable(1, 10, GAME_1)); + Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, false, new Pageable(1, 10, GAME_1)); Assert.assertEquals(1L, paginatedGames.getTotal()); Assert.assertTrue(paginatedGames.getPageContent().contains(game1)); } @Test public void testFindAllGamesPaginatedNone() { - Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, new Pageable(1, 10, "æøå")); + Page paginatedGames = gameFacade.findByStatusAndUserPaginated(Game.Status.LIVE, false, new Pageable(1, 10, "æøå")); Assert.assertEquals(0L, paginatedGames.getTotal()); }