feat(backend): sort applications and workspaces alphabetically (#41253)
## 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 ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/17998282833> > Commit: ff76753e19106314d21cc4b9548177fe8f93339d > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=17998282833&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Workspace` > Spec: > <hr>Thu, 25 Sep 2025 06:09:23 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Abhijeet <abhi.nagarnaik@gmail.com>
This commit is contained in:
parent
e742df0bfa
commit
44d61529ea
|
|
@ -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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ public interface ApplicationServiceCE extends CrudService<Application, String> {
|
|||
|
||||
Flux<Application> findByWorkspaceIdAndBaseApplicationsInRecentlyUsedOrder(String workspaceId);
|
||||
|
||||
Flux<Application> findByWorkspaceIdAndBaseApplicationsInAlphabeticalOrder(String workspaceId);
|
||||
|
||||
Flux<Application> findByWorkspaceIdAndBaseApplicationsForHome(String workspaceId);
|
||||
|
||||
Mono<Application> save(Artifact application);
|
||||
|
||||
Mono<Application> updateApplicationWithPresets(String branchedApplicationId, Application application);
|
||||
|
|
|
|||
|
|
@ -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<ApplicationRepository,
|
|||
private final WorkspaceService workspaceService;
|
||||
private final WorkspacePermission workspacePermission;
|
||||
private final ObservationRegistry observationRegistry;
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
private static final Integer MAX_RETRIES = 5;
|
||||
|
||||
|
|
@ -108,7 +112,8 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
|
|||
UserDataService userDataService,
|
||||
WorkspaceService workspaceService,
|
||||
WorkspacePermission workspacePermission,
|
||||
ObservationRegistry observationRegistry) {
|
||||
ObservationRegistry observationRegistry,
|
||||
FeatureFlagService featureFlagService) {
|
||||
|
||||
super(validator, repository, analyticsService);
|
||||
this.policySolution = policySolution;
|
||||
|
|
@ -122,6 +127,7 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
|
|||
this.workspaceService = workspaceService;
|
||||
this.workspacePermission = workspacePermission;
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -214,6 +220,63 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
|
|||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to fetch all the applications for a given workspaceId. It sorts the applications in
|
||||
* alphabetical order by name.
|
||||
* 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 alphabetically
|
||||
*/
|
||||
@Override
|
||||
public Flux<Application> findByWorkspaceIdAndBaseApplicationsInAlphabeticalOrder(String workspaceId) {
|
||||
|
||||
if (!StringUtils.hasLength(workspaceId)) {
|
||||
return Flux.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID));
|
||||
}
|
||||
|
||||
// Read the workspace
|
||||
Mono<Workspace> 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<Application> findByWorkspaceIdAndBaseApplicationsForHome(String workspaceId) {
|
||||
Mono<Boolean> 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<Application> save(Artifact artifact) {
|
||||
Application application = (Application) artifact;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ public class ApplicationControllerCE {
|
|||
public Mono<ResponseDTO<List<Application>>> 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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public class WorkspaceControllerCE {
|
|||
@GetMapping("/home")
|
||||
public Mono<ResponseDTO<List<Workspace>>> workspacesForHome() {
|
||||
return userWorkspaceService
|
||||
.getUserWorkspacesByRecentlyUsedOrder()
|
||||
.getUserWorkspacesForHome()
|
||||
.map(workspaces -> new ResponseDTO<>(HttpStatus.OK, workspaces));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,4 +25,8 @@ public interface UserWorkspaceServiceCE {
|
|||
Mono<Boolean> isLastAdminRoleEntity(PermissionGroup permissionGroup);
|
||||
|
||||
Mono<List<Workspace>> getUserWorkspacesByRecentlyUsedOrder();
|
||||
|
||||
Mono<List<Workspace>> getUserWorkspacesInAlphabeticalOrder();
|
||||
|
||||
Mono<List<Workspace>> getUserWorkspacesForHome();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<List<Workspace>> 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<List<Workspace>> getUserWorkspacesForHome() {
|
||||
Mono<Boolean> 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> existingWorkspaceNames =
|
||||
workspaceService.getAll().map(Workspace::getName).collectList().block();
|
||||
|
||||
// Arrange: Create multiple workspaces with different names
|
||||
List<String> 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<List<Workspace>> 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<String> workspaceNamesList =
|
||||
workspaces.stream().map(Workspace::getName).collect(Collectors.toList());
|
||||
workspaceNames.addAll(existingWorkspaceNames);
|
||||
List<String> sortedExistingWorkspaceNames =
|
||||
workspaceNames.stream().sorted().toList();
|
||||
assertThat(workspaceNamesList).containsExactly(sortedExistingWorkspaceNames.toArray(new String[0]));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user