From 44d61529eaaf276466c70a57077407bc8ad86eb4 Mon Sep 17 00:00:00 2001 From: tomjose92 Date: Thu, 25 Sep 2025 11:55:06 +0530 Subject: [PATCH] feat(backend): sort applications and workspaces alphabetically (#41253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Made changes in backend to sort applications and workspaces in alphabetic order Also added feature flag control to this functionality. Fixes #31108 ## Automation /test Workspace ### :mag: Cypress test results > [!TIP] > ๐ŸŸข ๐ŸŸข ๐ŸŸข All cypress tests have passed! ๐ŸŽ‰ ๐ŸŽ‰ ๐ŸŽ‰ > Workflow run: > Commit: ff76753e19106314d21cc4b9548177fe8f93339d > Cypress dashboard. > Tags: `@tag.Workspace` > Spec: >
Thu, 25 Sep 2025 06:09:23 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No ## Summary by CodeRabbit * **New Features** * Optional alphabetical ordering for workspaces and applications on the Home page, toggleable via a new feature flag. * Home view now chooses between case-insensitive alphabetical sorting and the existing โ€œrecently usedโ€ ordering based on that flag. * **Tests** * Added automated tests verifying alphabetical workspace ordering and exact name sequencing. --------- Co-authored-by: Abhijeet --- .../external/enums/FeatureFlagEnum.java | 4 ++ .../base/ApplicationServiceCE.java | 4 ++ .../base/ApplicationServiceCEImpl.java | 65 ++++++++++++++++++- .../base/ApplicationServiceImpl.java | 7 +- .../ce/ApplicationControllerCE.java | 2 +- .../controllers/ce/WorkspaceControllerCE.java | 2 +- .../services/UserWorkspaceServiceImpl.java | 6 +- .../services/ce/UserWorkspaceServiceCE.java | 4 ++ .../ce/UserWorkspaceServiceCEImpl.java | 43 +++++++++++- .../ApplicationServiceCECompatibleImpl.java | 7 +- .../services/UserWorkspaceServiceTest.java | 36 ++++++++++ 11 files changed, 170 insertions(+), 10 deletions(-) diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/enums/FeatureFlagEnum.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/enums/FeatureFlagEnum.java index 49309885d8..c6fe732899 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/enums/FeatureFlagEnum.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/enums/FeatureFlagEnum.java @@ -31,6 +31,10 @@ public enum FeatureFlagEnum { release_git_autocommit_eligibility_enabled, release_dynamodb_connection_time_to_live_enabled, release_reactive_actions_enabled, + /** + * Feature flag to enable alphabetical ordering for workspaces and applications + */ + release_alphabetical_ordering_enabled, // Add EE flags below this line, to avoid conflicts. } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCE.java index 6e6fd23d50..f555f40a99 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCE.java @@ -29,6 +29,10 @@ public interface ApplicationServiceCE extends CrudService { Flux findByWorkspaceIdAndBaseApplicationsInRecentlyUsedOrder(String workspaceId); + Flux findByWorkspaceIdAndBaseApplicationsInAlphabeticalOrder(String workspaceId); + + Flux findByWorkspaceIdAndBaseApplicationsForHome(String workspaceId); + Mono save(Artifact application); Mono updateApplicationWithPresets(String branchedApplicationId, Application application); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java index f089e2fe57..4645820bdd 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java @@ -1,5 +1,6 @@ package com.appsmith.server.applications.base; +import com.appsmith.external.enums.FeatureFlagEnum; import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.Policy; import com.appsmith.server.acl.AclPermission; @@ -36,6 +37,7 @@ import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.AssetService; import com.appsmith.server.services.BaseService; +import com.appsmith.server.services.FeatureFlagService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; @@ -59,6 +61,7 @@ import reactor.core.publisher.Mono; import java.time.Instant; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -90,6 +93,7 @@ public class ApplicationServiceCEImpl extends BaseService findByWorkspaceIdAndBaseApplicationsInAlphabeticalOrder(String workspaceId) { + + if (!StringUtils.hasLength(workspaceId)) { + return Flux.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID)); + } + + // Read the workspace + Mono workspaceMono = workspaceService + .findById(workspaceId, workspacePermission.getReadPermission()) + .switchIfEmpty(Mono.error( + new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.WORKSPACE, workspaceId))); + + return workspaceMono.thenMany(this.findByWorkspaceId(workspaceId, applicationPermission.getReadPermission()) + .sort(Comparator.comparing(application -> application.getName().toLowerCase())) + .filter(application -> { + /* + * Filter applications based on the following criteria: + * - Applications that are not connected to Git. + * - Applications that, when connected, revert with default branch only. + */ + return !GitUtils.isArtifactConnectedToGit(application.getGitArtifactMetadata()) + || GitUtils.isDefaultBranchedArtifact(application.getGitArtifactMetadata()); + })); + } + + /** + * This method is used to fetch all the applications for a given workspaceId. It sorts the applications based + * on feature flag - either alphabetically or by recently used order. + * For git connected applications only default branched application is returned. + * + * @param workspaceId workspaceId for which applications are to be fetched + * @return Flux of applications sorted based on feature flag + */ + @Override + public Flux findByWorkspaceIdAndBaseApplicationsForHome(String workspaceId) { + Mono isAlphabeticalOrderingEnabled = + featureFlagService.check(FeatureFlagEnum.release_alphabetical_ordering_enabled); + return isAlphabeticalOrderingEnabled.flatMapMany(isEnabled -> { + if (isEnabled) { + // If alphabetical ordering is enabled, then we need to sort the applications in alphabetical order + return findByWorkspaceIdAndBaseApplicationsInAlphabeticalOrder(workspaceId); + } else { + // If alphabetical ordering is disabled, then we need to sort the applications in recently used order + return findByWorkspaceIdAndBaseApplicationsInRecentlyUsedOrder(workspaceId); + } + }); + } + @Override public Mono save(Artifact artifact) { Application application = (Application) artifact; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceImpl.java index 4c2afe5846..fe04f60bae 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceImpl.java @@ -6,6 +6,7 @@ import com.appsmith.server.repositories.ApplicationRepository; import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.AssetService; +import com.appsmith.server.services.FeatureFlagService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; @@ -39,7 +40,8 @@ public class ApplicationServiceImpl extends ApplicationServiceCECompatibleImpl UserDataService userDataService, WorkspaceService workspaceService, WorkspacePermission workspacePermission, - ObservationRegistry observationRegistry) { + ObservationRegistry observationRegistry, + FeatureFlagService featureFlagService) { super( validator, repository, @@ -54,6 +56,7 @@ public class ApplicationServiceImpl extends ApplicationServiceCECompatibleImpl userDataService, workspaceService, workspacePermission, - observationRegistry); + observationRegistry, + featureFlagService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java index 11c32aa490..382ea25391 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java @@ -130,7 +130,7 @@ public class ApplicationControllerCE { public Mono>> findByWorkspaceIdAndRecentlyUsedOrder( @RequestParam(required = false) String workspaceId) { log.debug("Going to get all applications by workspace id {}", workspaceId); - return service.findByWorkspaceIdAndBaseApplicationsInRecentlyUsedOrder(workspaceId) + return service.findByWorkspaceIdAndBaseApplicationsForHome(workspaceId) .collectList() .map(applications -> new ResponseDTO<>(HttpStatus.OK, applications)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java index a7ad0f6967..c2a92ceb3c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/WorkspaceControllerCE.java @@ -107,7 +107,7 @@ public class WorkspaceControllerCE { @GetMapping("/home") public Mono>> workspacesForHome() { return userWorkspaceService - .getUserWorkspacesByRecentlyUsedOrder() + .getUserWorkspacesForHome() .map(workspaces -> new ResponseDTO<>(HttpStatus.OK, workspaces)); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserWorkspaceServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserWorkspaceServiceImpl.java index f3ed33931c..c080b0464c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserWorkspaceServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserWorkspaceServiceImpl.java @@ -19,7 +19,8 @@ public class UserWorkspaceServiceImpl extends UserWorkspaceServiceCEImpl impleme PermissionGroupService permissionGroupService, OrganizationService organizationService, WorkspacePermission workspacePermission, - PermissionGroupPermission permissionGroupPermission) { + PermissionGroupPermission permissionGroupPermission, + FeatureFlagService featureFlagService) { super( sessionUserService, @@ -29,6 +30,7 @@ public class UserWorkspaceServiceImpl extends UserWorkspaceServiceCEImpl impleme permissionGroupService, organizationService, workspacePermission, - permissionGroupPermission); + permissionGroupPermission, + featureFlagService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCE.java index f336b8d285..810c23bda0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCE.java @@ -25,4 +25,8 @@ public interface UserWorkspaceServiceCE { Mono isLastAdminRoleEntity(PermissionGroup permissionGroup); Mono> getUserWorkspacesByRecentlyUsedOrder(); + + Mono> getUserWorkspacesInAlphabeticalOrder(); + + Mono> getUserWorkspacesForHome(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCEImpl.java index 973b2ae250..851aca7494 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserWorkspaceServiceCEImpl.java @@ -1,5 +1,6 @@ package com.appsmith.server.services.ce; +import com.appsmith.external.enums.FeatureFlagEnum; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.PermissionGroup; import com.appsmith.server.domains.User; @@ -13,6 +14,7 @@ import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.AppsmithComparators; import com.appsmith.server.repositories.UserRepository; +import com.appsmith.server.services.FeatureFlagService; import com.appsmith.server.services.OrganizationService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; @@ -32,6 +34,7 @@ import reactor.util.function.Tuple2; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -54,6 +57,7 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE { private final OrganizationService organizationService; private final WorkspacePermission workspacePermission; private final PermissionGroupPermission permissionGroupPermission; + private final FeatureFlagService featureFlagService; @Autowired public UserWorkspaceServiceCEImpl( @@ -64,7 +68,8 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE { PermissionGroupService permissionGroupService, OrganizationService organizationService, WorkspacePermission workspacePermission, - PermissionGroupPermission permissionGroupPermission) { + PermissionGroupPermission permissionGroupPermission, + FeatureFlagService featureFlagService) { this.sessionUserService = sessionUserService; this.workspaceService = workspaceService; this.userRepository = userRepository; @@ -73,6 +78,7 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE { this.organizationService = organizationService; this.workspacePermission = workspacePermission; this.permissionGroupPermission = permissionGroupPermission; + this.featureFlagService = featureFlagService; } @Override @@ -419,4 +425,39 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE { // collect to list to keep the order of the workspaces .collectList()); } + + /* + * Returns a list of workspaces for the current user, sorted in alphabetical order. + * + * @return Mono containing the list of workspaces + */ + @Override + public Mono> getUserWorkspacesInAlphabeticalOrder() { + return workspaceService + .getAll(workspacePermission.getReadPermission()) + .sort(Comparator.comparing(workspace -> workspace.getName().toLowerCase())) + .collectList(); + } + + /** + * Returns a list of workspaces for the current user, sorted based on feature flag. + * If alphabetical ordering is enabled, returns workspaces in alphabetical order. + * Otherwise, returns workspaces in recently used order. + * + * @return Mono containing the list of workspaces + */ + @Override + public Mono> getUserWorkspacesForHome() { + Mono isAlphabeticalOrderingEnabled = + featureFlagService.check(FeatureFlagEnum.release_alphabetical_ordering_enabled); + return isAlphabeticalOrderingEnabled.flatMap(isEnabled -> { + if (isEnabled) { + // If alphabetical ordering is enabled, then we need to sort the workspaces in alphabetical order + return getUserWorkspacesInAlphabeticalOrder(); + } else { + // If alphabetical ordering is disabled, then we need to sort the workspaces in recently used order + return getUserWorkspacesByRecentlyUsedOrder(); + } + }); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ApplicationServiceCECompatibleImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ApplicationServiceCECompatibleImpl.java index 5b368903ff..c4c822daa5 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ApplicationServiceCECompatibleImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ApplicationServiceCECompatibleImpl.java @@ -5,6 +5,7 @@ import com.appsmith.server.repositories.ApplicationRepository; import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.AssetService; +import com.appsmith.server.services.FeatureFlagService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; @@ -34,7 +35,8 @@ public class ApplicationServiceCECompatibleImpl extends ApplicationServiceCEImpl UserDataService userDataService, WorkspaceService workspaceService, WorkspacePermission workspacePermission, - ObservationRegistry observationRegistry) { + ObservationRegistry observationRegistry, + FeatureFlagService featureFlagService) { super( validator, repository, @@ -49,6 +51,7 @@ public class ApplicationServiceCECompatibleImpl extends ApplicationServiceCEImpl userDataService, workspaceService, workspacePermission, - observationRegistry); + observationRegistry, + featureFlagService); } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserWorkspaceServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserWorkspaceServiceTest.java index afdc484d8d..a80c4ca205 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserWorkspaceServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserWorkspaceServiceTest.java @@ -377,4 +377,40 @@ public class UserWorkspaceServiceTest { .cleanPermissionGroupCacheForUsers(List.of(api_user.getId(), test_user.getId())) .block(); } + + @Test + @WithUserDetails(value = "api_user") + public void getUserWorkspacesInAlphabeticalOrder_WhenUserHasWorkspaces_ReturnsWorkspacesSortedAlphabetically() { + + List existingWorkspaceNames = + workspaceService.getAll().map(Workspace::getName).collectList().block(); + + // Arrange: Create multiple workspaces with different names + List workspaceNames = + new java.util.ArrayList<>(List.of("Zebra Workspace", "Alpha Workspace", "Beta Workspace")); + assert existingWorkspaceNames != null; + for (String name : workspaceNames) { + Workspace workspace = new Workspace(); + workspace.setName(name); + // Ensures default permission groups & current user access are created + workspaceService.create(workspace).block(); + } + + // Act: Call the method to get the user's workspaces in alphabetical order + Mono> workspacesMono = userWorkspaceService.getUserWorkspacesInAlphabeticalOrder(); + + // Assert: Verify the workspaces are returned in alphabetical order + StepVerifier.create(workspacesMono) + .assertNext(workspaces -> { + assertThat(workspaces).isNotNull(); + assertThat(workspaces).hasSize(3 + existingWorkspaceNames.size()); + List workspaceNamesList = + workspaces.stream().map(Workspace::getName).collect(Collectors.toList()); + workspaceNames.addAll(existingWorkspaceNames); + List sortedExistingWorkspaceNames = + workspaceNames.stream().sorted().toList(); + assertThat(workspaceNamesList).containsExactly(sortedExistingWorkspaceNames.toArray(new String[0])); + }) + .verifyComplete(); + } }