From 6a0948f8d81770f8973081f08439eff5f65a09a8 Mon Sep 17 00:00:00 2001 From: Boubaker Khanfir Date: Tue, 21 Nov 2023 16:02:05 +0100 Subject: [PATCH] fix: Fix Recent Spaces Loading - MEED-2951 - Meeds-io/meeds#1022 (#3212) Prior to this change, the recent spaces menu performances offers a bad UX since it takes a lot of time and didn't display loading effect. This change will add a loading effect and a placeholder when empty spaces are retrieved even while searching. In addition, the backend services was taking lot of time to compute unread activities per space. To fix the performances issue, the query has been modified to make sure that the unread activities retrieval is more performant. In addition, the eTag computing has been improved to rely on REST input queries, cache time of results and spaces list order. This will ensure to not having to compute unread flags each time the last visited spaces list are updated. --- .../social/core/space/SpaceListAccess.java | 4 +- .../storage/entity/MetadataItemEntity.java | 4 +- .../core/space/impl/SpaceAccessHandler.java | 7 +- .../storage/cache/CachedSpaceStorage.java | 9 ++- .../rest/impl/space/SpaceRestResourcesV1.java | 68 ++++++++++++------- .../locale/portal/HamburgerMenu_en.properties | 4 ++ .../RecentSpacesHamburgerNavigation.vue | 37 +++++++--- .../recent-spaces/SpaceNavigationItem.vue | 13 +++- .../recent-spaces/SpacesNavigationContent.vue | 12 +++- .../recent-spaces/SpacesNavigationEmpty.vue | 58 ++++++++++++++++ .../vue-apps/hamburger-menu/initComponents.js | 2 + 11 files changed, 171 insertions(+), 47 deletions(-) create mode 100644 webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/SpacesNavigationEmpty.vue diff --git a/component/api/src/main/java/org/exoplatform/social/core/space/SpaceListAccess.java b/component/api/src/main/java/org/exoplatform/social/core/space/SpaceListAccess.java index 08ecc8a3b2a..64846232db5 100644 --- a/component/api/src/main/java/org/exoplatform/social/core/space/SpaceListAccess.java +++ b/component/api/src/main/java/org/exoplatform/social/core/space/SpaceListAccess.java @@ -294,11 +294,11 @@ public Space[] load(int offset, int limit) throws Exception, IllegalArgumentExce Space storedSpace = spaceStorage.getSpaceById(space.getId()); storedSpace.setPendingUsers(space.getPendingUsers()); return storedSpace; - }).collect(Collectors.toList()); + }).toList(); } break; } - return listSpaces.toArray(new Space[listSpaces.size()]); + return listSpaces == null ? new Space[0] : listSpaces.toArray(new Space[listSpaces.size()]); } /** diff --git a/component/core/src/main/java/org/exoplatform/social/core/jpa/storage/entity/MetadataItemEntity.java b/component/core/src/main/java/org/exoplatform/social/core/jpa/storage/entity/MetadataItemEntity.java index fc29ccf480f..2a7a38767ce 100644 --- a/component/core/src/main/java/org/exoplatform/social/core/jpa/storage/entity/MetadataItemEntity.java +++ b/component/core/src/main/java/org/exoplatform/social/core/jpa/storage/entity/MetadataItemEntity.java @@ -175,8 +175,8 @@ + " INNER JOIN SOC_METADATAS sm" + " ON item.metadata_id = sm.metadata_id " + " AND sm.type = :metadataType " - + " AND sm.audience_id = :creatorId " - + " WHERE item.space_id = :spaceId " + + " WHERE item.creator_id = :creatorId" + + " AND item.space_id = :spaceId" + " GROUP BY item.object_type" ) @NamedNativeQuery( diff --git a/component/core/src/main/java/org/exoplatform/social/core/space/impl/SpaceAccessHandler.java b/component/core/src/main/java/org/exoplatform/social/core/space/impl/SpaceAccessHandler.java index 9ced88556b5..9585ba3b6b0 100644 --- a/component/core/src/main/java/org/exoplatform/social/core/space/impl/SpaceAccessHandler.java +++ b/component/core/src/main/java/org/exoplatform/social/core/space/impl/SpaceAccessHandler.java @@ -99,8 +99,13 @@ public boolean execute(ControllerContext controllerContext) throws Exception { && requestSiteName.startsWith(SPACES_GROUP_PREFIX)) { Space space = spaceService.getSpaceByGroupId(requestSiteName); if (space != null && canAccessSpace(remoteId, space)) { - spaceService.updateSpaceAccessed(remoteId, space); + HttpSession session = controllerContext.getRequest().getSession(); + String lastAccessedSpaceId = (String) session.getAttribute(SpaceAccessType.ACCESSED_SPACE_ID_KEY); + if (!StringUtils.equals(lastAccessedSpaceId, space.getId())) { + spaceService.updateSpaceAccessed(remoteId, space); + } cleanupSession(controllerContext); + session.setAttribute(SpaceAccessType.ACCESSED_SPACE_ID_KEY, space.getId()); } else { processSpaceAccess(controllerContext, remoteId, space); return true; diff --git a/component/core/src/main/java/org/exoplatform/social/core/storage/cache/CachedSpaceStorage.java b/component/core/src/main/java/org/exoplatform/social/core/storage/cache/CachedSpaceStorage.java index 443e6ecfdf7..3817da75697 100644 --- a/component/core/src/main/java/org/exoplatform/social/core/storage/cache/CachedSpaceStorage.java +++ b/component/core/src/main/java/org/exoplatform/social/core/storage/cache/CachedSpaceStorage.java @@ -943,16 +943,15 @@ public void updateSpaceAccessed(String remoteId, Space space) throws SpaceStorag // we remove all cache entries for the given userId and for space type LATEST_ACCESSED LastAccessedSpacesCacheSelector selector = new LastAccessedSpacesCacheSelector(remoteId, space, cacheService); try { + // Update the storage only if the user has accessed a different space + if (selector.isUpdateStore()) { + super.updateSpaceAccessed(remoteId, space); + } exoSpacesCache.select(selector); } catch (Exception e) { LOG.error("Error while removing cache entries for remoteId=" + remoteId + ", space=" + space.getDisplayName() + " and type=" + SpaceType.LATEST_ACCESSED.name() + " or type=" + SpaceType.VISITED, e); } - - // Update the storage only if the user has accessed a different space - if (selector.isUpdateStore()) { - super.updateSpaceAccessed(remoteId, space); - } } @Override diff --git a/component/service/src/main/java/org/exoplatform/social/rest/impl/space/SpaceRestResourcesV1.java b/component/service/src/main/java/org/exoplatform/social/rest/impl/space/SpaceRestResourcesV1.java index 625cab924cf..6b7b97ee7f0 100644 --- a/component/service/src/main/java/org/exoplatform/social/rest/impl/space/SpaceRestResourcesV1.java +++ b/component/service/src/main/java/org/exoplatform/social/rest/impl/space/SpaceRestResourcesV1.java @@ -141,6 +141,8 @@ public class SpaceRestResourcesV1 implements SpaceRestResources { private static final CacheControl CACHE_CONTROL = new CacheControl(); + private static final CacheControl CACHE_REVALIDATE_CONTROL = new CacheControl(); + private static final Date DEFAULT_IMAGES_LAST_MODIFED = new Date(); // 7 days @@ -176,6 +178,8 @@ public SpaceRestResourcesV1(ActivityRestResourcesV1 activityRestResourcesV1, this.securitySettingService = securitySettingService; CACHE_CONTROL.setMaxAge(CACHE_IN_SECONDS); + CACHE_REVALIDATE_CONTROL.setMaxAge(CACHE_IN_SECONDS); + CACHE_REVALIDATE_CONTROL.setMustRevalidate(true); } /** @@ -270,40 +274,56 @@ public Response getSpaces(@Context return Response.status(400).entity("Unrecognized space filter type").build(); } - List spaceInfos = new ArrayList<>(); + List spaces; if (limit > 0) { - for (Space space : listAccess.load(offset, limit)) { + spaces = Arrays.asList(listAccess.load(offset, limit)); + } else { + spaces = Collections.emptyList(); + } + String eTagValue = String.valueOf(Objects.hash(spaces.stream() + .map(Space::getCacheTime) + .reduce(Long::sum) + .orElse(0l), + Objects.hash(spaces.stream() + .map(Space::getId) + .map(Long::parseLong) + .toArray()), + spaceFilter, + filterType, + offset, + limit, + returnSize, + expand, + authenticatedUser)); + EntityTag eTag = new EntityTag(eTagValue); + Response.ResponseBuilder builder = request.evaluatePreconditions(eTag); + if (builder == null) { + List spaceInfos = new ArrayList<>(); + for (Space space : spaces) { SpaceEntity spaceInfo = EntityBuilder.buildEntityFromSpace(space, authenticatedUser, uriInfo.getPath(), expand); spaceInfos.add(spaceInfo.getDataEntity()); } - } - CollectionEntity collectionSpace = new CollectionEntity(spaceInfos, EntityBuilder.SPACES_TYPE, offset, limit); - if (returnSize) { - collectionSpace.setSize(listAccess.getSize()); - } - - if (StringUtils.isNotBlank(expand) && Arrays.asList(StringUtils.split(expand, ",")).contains(RestProperties.UNREAD)) { - SpaceWebNotificationService spaceWebNotificationService = ExoContainerContext.getService(SpaceWebNotificationService.class); - Map unreadItemsPerSpace = spaceWebNotificationService.countUnreadItemsBySpace(authenticatedUser); - if (MapUtils.isNotEmpty(unreadItemsPerSpace)) { - collectionSpace.setUnreadPerSpace(unreadItemsPerSpace.entrySet() - .stream() - .collect(Collectors.toMap(e -> e.getKey().toString(), - e -> e.getValue()))); + CollectionEntity collectionSpace = new CollectionEntity(spaceInfos, EntityBuilder.SPACES_TYPE, offset, limit); + if (returnSize) { + collectionSpace.setSize(listAccess.getSize()); } - } - String eTagValue = String.valueOf(Objects.hash(collectionSpace.hashCode(), authenticatedUser, expand)); - EntityTag eTag = new EntityTag(eTagValue); + if (StringUtils.isNotBlank(expand) && Arrays.asList(StringUtils.split(expand, ",")).contains(RestProperties.UNREAD)) { + SpaceWebNotificationService spaceWebNotificationService = ExoContainerContext.getService(SpaceWebNotificationService.class); + Map unreadItemsPerSpace = spaceWebNotificationService.countUnreadItemsBySpace(authenticatedUser); + if (MapUtils.isNotEmpty(unreadItemsPerSpace)) { + collectionSpace.setUnreadPerSpace(unreadItemsPerSpace.entrySet() + .stream() + .collect(Collectors.toMap(e -> e.getKey().toString(), + e -> e.getValue()))); + } + } - Response.ResponseBuilder builder = request.evaluatePreconditions(eTag); - if (builder == null) { builder = EntityBuilder.getResponseBuilder(collectionSpace, uriInfo, RestUtils.getJsonMediaType(), Response.Status.OK); builder.tag(eTag); - Date date = new Date(System.currentTimeMillis()); - builder.lastModified(date); - builder.expires(date); + builder.lastModified(new Date()); + builder.cacheControl(CACHE_REVALIDATE_CONTROL); } return builder.build(); diff --git a/extension/war/src/main/resources/locale/portal/HamburgerMenu_en.properties b/extension/war/src/main/resources/locale/portal/HamburgerMenu_en.properties index 3494ab195d8..19adf259f3f 100644 --- a/extension/war/src/main/resources/locale/portal/HamburgerMenu_en.properties +++ b/extension/war/src/main/resources/locale/portal/HamburgerMenu_en.properties @@ -16,3 +16,7 @@ menu.expand=Keep the menu opened menu.collapse=Collapse the menu menu.createNewSpace=Create a space menu.seeMySpaces=See my spaces +menu.spaces.joinOrCreateSpace=Join or create a space +menu.spaces.joinSpace=Join a space +menu.spaces.exploreSpaces=Explore Spaces +menu.spaces.noSpacesFound=No spaces found diff --git a/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/RecentSpacesHamburgerNavigation.vue b/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/RecentSpacesHamburgerNavigation.vue index 12a34a3726b..847b1f6cb06 100644 --- a/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/RecentSpacesHamburgerNavigation.vue +++ b/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/RecentSpacesHamburgerNavigation.vue @@ -20,7 +20,7 @@ --> - + \ No newline at end of file diff --git a/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/SpacesNavigationEmpty.vue b/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/SpacesNavigationEmpty.vue new file mode 100644 index 00000000000..d150b60712d --- /dev/null +++ b/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/components/recent-spaces/SpacesNavigationEmpty.vue @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/initComponents.js b/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/initComponents.js index 5aa62e86461..18af39d7def 100644 --- a/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/initComponents.js +++ b/webapp/portlet/src/main/webapp/vue-apps/hamburger-menu/initComponents.js @@ -33,6 +33,7 @@ import SpacePanelHamburgerNavigation from './components/recent-spaces/SpacePanel import SpacePanelHamburgerNavigationItem from './components/recent-spaces/SpacePanelHamburgerNavigationItem.vue'; import SpacesHamburgerNavigation from './components/recent-spaces/SpacesHamburgerNavigation.vue'; import SpacesNavigationContent from './components/recent-spaces/SpacesNavigationContent.vue'; +import SpacesNavigationEmpty from './components/recent-spaces/SpacesNavigationEmpty.vue'; import SiteHamburgerNavigation from './components/site/SiteHamburgerNavigation.vue'; import UserHamburgerNavigation from './components/user/UserHamburgerNavigation.vue'; import SitesHamburger from './components/site/SitesHamburger.vue'; @@ -56,6 +57,7 @@ const components = { 'space-panel-hamburger-navigation': SpacePanelHamburgerNavigation, 'space-panel-hamburger-navigation-item': SpacePanelHamburgerNavigationItem, 'spaces-navigation-content': SpacesNavigationContent, + 'spaces-navigation-empty': SpacesNavigationEmpty, 'site-hamburger-navigation': SiteHamburgerNavigation, 'user-hamburger-navigation': UserHamburgerNavigation, 'sites-hamburger': SitesHamburger,