diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java index f1d0706871..4e3d1269be 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java @@ -93,6 +93,16 @@ public class ApplicationController extends BaseController new ResponseDTO<>(HttpStatus.OK.value(), updatedApplication, null)); } + @PutMapping("/{applicationId}/page/{pageId}/reorder") + public Mono> reorderPage( + @PathVariable String applicationId, + @PathVariable String pageId, + @RequestParam Integer order + ) { + return applicationPageService.reorderPage(applicationId, pageId, order) + .map(updatedApplication -> new ResponseDTO<>(HttpStatus.OK.value(), updatedApplication, null)); + } + @DeleteMapping("/{id}") public Mono> delete(@PathVariable String id) { log.debug("Going to delete application with id: {}", id); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationPage.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationPage.java index b304224291..e6ab71aae1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationPage.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationPage.java @@ -20,6 +20,8 @@ public class ApplicationPage { Boolean isDefault; + Integer order; + @JsonIgnore public boolean isDefault() { return Boolean.TRUE.equals(isDefault); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java index 55dd43e5b4..55207d1830 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java @@ -42,6 +42,7 @@ import com.appsmith.server.domains.Sequence; import com.appsmith.server.domains.User; import com.appsmith.server.domains.UserData; import com.appsmith.server.domains.UserRole; +import com.appsmith.server.domains.ApplicationPage; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.DslActionDTO; import com.appsmith.server.dtos.OrganizationPluginStatus; @@ -2454,7 +2455,7 @@ public class DatabaseChangelog { .users(adminUsernames).build(); application.getPolicies().add(newExportAppPolicy); } - + mongoTemplate.save(application); } } @@ -2494,7 +2495,6 @@ public class DatabaseChangelog { } - @ChangeSet(order = "073", id = "mongo-form-merge-update-commands", author = "") public void migrateUpdateOneToUpdateManyMongoFormCommand(MongockTemplate mongockTemplate) { @@ -2601,4 +2601,33 @@ public class DatabaseChangelog { mongoTemplate.save(user); } } + + /** + * - Older order file where not present for the pages created within the application because page reordering with in + * the application was not supported. + * - New Form order field will be added to the Page object and is used to order the pages with in the application + * Since the previously created pages doesnt have the order, we will be updating/adding order to all the previously + * created pages of all the application present. + * - [] + */ + @ChangeSet(order = "075", id = "add-and-update-order-for-all-pages", author = "") + public void addOrderToAllPagesOfApplication(MongoTemplate mongoTemplate) { + for (Application application : mongoTemplate.findAll(Application.class)) { + if(application.getPages() != null) { + int i = 0; + for (ApplicationPage page : application.getPages()) { + page.setOrder(i); + i++; + } + if(application.getPublishedPages() != null) { + i = 0; + for (ApplicationPage page : application.getPublishedPages()) { + page.setOrder(i); + i++; + } + } + mongoTemplate.save(application); + } + } + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepository.java index 435c489799..80d1aa0761 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepository.java @@ -2,10 +2,12 @@ package com.appsmith.server.repositories; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationPage; import com.mongodb.client.result.UpdateResult; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; import java.util.Set; public interface CustomApplicationRepository extends AppsmithRepository { @@ -20,7 +22,9 @@ public interface CustomApplicationRepository extends AppsmithRepository findByClonedFromApplicationId(String applicationId, AclPermission permission); - Mono addPageToApplication(String applicationId, String pageId, boolean isDefault); + Mono addPageToApplication(String applicationId, String pageId, boolean isDefault, Integer order); + + Mono setPages(String applicationId, List pages); Mono setDefaultPage(String applicationId, String pageId); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepositoryImpl.java index c89afbed96..93ed1e9f84 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomApplicationRepositoryImpl.java @@ -73,11 +73,20 @@ public class CustomApplicationRepositoryImpl extends BaseAppsmithRepositoryImpl< } @Override - public Mono addPageToApplication(String applicationId, String pageId, boolean isDefault) { - final ApplicationPage applicationPage = new ApplicationPage(pageId, isDefault); + public Mono addPageToApplication(String applicationId, String pageId, boolean isDefault, Integer order) { + final ApplicationPage applicationPage = new ApplicationPage(pageId, isDefault, order); return mongoOperations.updateFirst( Query.query(getIdCriteria(applicationId)), - new Update().addToSet(FieldName.PAGES, applicationPage), + new Update().addToSet(fieldName(QApplication.application.pages), applicationPage), + Application.class + ); + } + + @Override + public Mono setPages(String applicationId, List pages) { + return mongoOperations.updateFirst( + Query.query(getIdCriteria(applicationId)), + new Update().set(fieldName(QApplication.application.pages), pages), Application.class ); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageService.java index ca6665b9fc..09ca039955 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageService.java @@ -38,4 +38,6 @@ public interface ApplicationPageService { void generateAndSetPagePolicies(Application application, PageDTO page); Mono sendApplicationPublishedEvent(Application application); + + Mono reorderPage(String applicationId, String pageId, Integer order); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java index bc96a723bd..c3c8c5eb41 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java @@ -139,7 +139,8 @@ public class ApplicationPageServiceImpl implements ApplicationPageService { */ @Override public Mono addPageToApplication(Application application, PageDTO page, Boolean isDefault) { - return applicationRepository.addPageToApplication(application.getId(), page.getId(), isDefault) + Integer order = application.getPages() != null ? application.getPages().size() : 0; + return applicationRepository.addPageToApplication(application.getId(), page.getId(), isDefault, order) .doOnSuccess(result -> { if (result.getModifiedCount() != 1) { log.error("Add page to application didn't update anything, probably because application wasn't found."); @@ -667,4 +668,55 @@ public class ApplicationPageServiceImpl implements ApplicationPageService { }); } + /** This function walks through all the pages and reorders them and updates the order as per the user preference. + * A page can be moved up or down from the current position and accordingly the order of the remaining page changes. + * @param applicationId The id of the Application + * @param pageId Targetted page id + * @param order New order for the selected page + * @return Application object with the latest order + **/ + @Override + public Mono reorderPage(String applicationId, String pageId, Integer order) { + return applicationService.findById(applicationId, MANAGE_APPLICATIONS) + .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId))) + .flatMap(application -> { + // Update the order in unpublished pages here, since this should only ever happen in edit mode. + final List pages = application.getPages(); + + ApplicationPage foundPage = null; + for (final ApplicationPage page : pages) { + if (pageId.equals(page.getId())) { + foundPage = page; + } + } + + /* there are two cases where page is re-ordered. Lets assume there are five pages 1,2,3,4,5 + * Case 1(isMovingUp == true): p5 to p2, order of p2,p3,p4 increases by 1. + * + * Case 2(isMovingUp == false): p2 to p5, order of p3,p4,p5 decreases by 1. + **/ + if(foundPage != null) { + boolean isMovingUp = order < foundPage.getOrder(); + if(isMovingUp) { + for (final ApplicationPage page : pages) { + if (page.getOrder() < foundPage.getOrder() && page.getOrder() >= order) { + page.setOrder(page.getOrder()+1); + } + } + } else { + for (final ApplicationPage page : pages) { + if (page.getOrder() > foundPage.getOrder() && page.getOrder() <= order) { + page.setOrder(page.getOrder()-1); + } + } + } + //set the selected page order to the given order + foundPage.setOrder(order); + } + return applicationRepository + .setPages(applicationId, pages) + .then(applicationService.getById(applicationId)); + }); + } + } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java index 4bcf8ebe5c..12986ed1fc 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java @@ -766,6 +766,7 @@ public class ApplicationServiceTest { ApplicationPage applicationPage = new ApplicationPage(); applicationPage.setId(newPage.getId()); applicationPage.setIsDefault(false); + applicationPage.setOrder(1); StepVerifier .create(applicationService.findById(newPage.getApplicationId(), MANAGE_APPLICATIONS)) @@ -811,25 +812,37 @@ public class ApplicationServiceTest { Mono updatedDefaultPageApplicationMono = applicationMono .flatMap(application -> applicationPageService.makePageDefault(application.getId(), newPage.getId())); - ApplicationPage unpublishedEditedPage = new ApplicationPage(); - unpublishedEditedPage.setId(newPage.getId()); - unpublishedEditedPage.setIsDefault(true); - ApplicationPage publishedEditedPage = new ApplicationPage(); publishedEditedPage.setId(newPage.getId()); publishedEditedPage.setIsDefault(false); + ApplicationPage unpublishedEditedPage = new ApplicationPage(); + unpublishedEditedPage.setId(newPage.getId()); + unpublishedEditedPage.setIsDefault(true); + StepVerifier .create(updatedDefaultPageApplicationMono) .assertNext(editedApplication -> { List publishedPages = editedApplication.getPublishedPages(); assertThat(publishedPages).size().isEqualTo(2); - assertThat(publishedPages).containsAnyOf(publishedEditedPage); + boolean isFound = false; + for( ApplicationPage page: publishedPages) { + if(page.getId().equals(publishedEditedPage.getId()) && page.getIsDefault().equals(publishedEditedPage.getIsDefault())) { + isFound = true; + } + } + assertThat(isFound).isTrue(); List editedApplicationPages = editedApplication.getPages(); assertThat(editedApplicationPages.size()).isEqualTo(2); - assertThat(editedApplicationPages).containsAnyOf(unpublishedEditedPage); + isFound = false; + for( ApplicationPage page: editedApplicationPages) { + if(page.getId().equals(unpublishedEditedPage.getId()) && page.getIsDefault().equals(unpublishedEditedPage.getIsDefault())) { + isFound = true; + } + } + assertThat(isFound).isTrue(); }) .verifyComplete(); } @@ -873,7 +886,13 @@ public class ApplicationServiceTest { .assertNext(viewApplication -> { List editedApplicationPages = viewApplication.getPages(); assertThat(editedApplicationPages.size()).isEqualTo(2); - assertThat(editedApplicationPages).containsAnyOf(applicationPage); + boolean isFound = false; + for( ApplicationPage page: editedApplicationPages) { + if(page.getId().equals(applicationPage.getId()) && page.getIsDefault().equals(applicationPage.getIsDefault())) { + isFound = true; + } + } + assertThat(isFound).isTrue(); }) .verifyComplete(); } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java index da49dcf673..2560f4c393 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java @@ -10,6 +10,7 @@ import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.User; +import com.appsmith.server.domains.ApplicationPage; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.PageDTO; @@ -44,6 +45,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; import static com.appsmith.server.acl.AclPermission.READ_ACTIONS; @@ -385,6 +387,127 @@ public class PageServiceTest { .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void reOrderPageFromHighOrderToLowOrder() { + + User apiUser = userService.findByEmail("api_user").block(); + orgId = apiUser.getOrganizationIds().iterator().next(); + Application newApp = new Application(); + newApp.setName(UUID.randomUUID().toString()); + + application = applicationPageService.createApplication(newApp, orgId).block(); + applicationId = application.getId(); + final String[] pageIds = new String[4]; + + PageDTO testPage1 = new PageDTO(); + testPage1.setName("Page2"); + testPage1.setApplicationId(applicationId); + Mono applicationPageReOrdered = applicationPageService.createPage(testPage1) + .flatMap(pageDTO -> { + PageDTO testPage = new PageDTO(); + testPage.setName("Page3"); + testPage.setApplicationId(applicationId); + return applicationPageService.createPage(testPage); + }) + .flatMap(pageDTO -> { + PageDTO testPage = new PageDTO(); + testPage.setName("Page4"); + testPage.setApplicationId(applicationId); + return applicationPageService.createPage(testPage); + }) + .flatMap(pageDTO -> applicationService.getById(pageDTO.getApplicationId())) + .flatMap( application -> { + pageIds[0] = application.getPages().get(0).getId(); + pageIds[1] = application.getPages().get(1).getId(); + pageIds[2] = application.getPages().get(2).getId(); + pageIds[3] = application.getPages().get(3).getId(); + return applicationPageService.reorderPage(application.getId(), application.getPages().get(3).getId(), 1); + }); + + StepVerifier + .create(applicationPageReOrdered) + .assertNext(application -> { + final List pages = application.getPages(); + assertThat(application.getPages().size()).isEqualTo(4); + for(ApplicationPage page : pages) { + if(pageIds[0].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(0); + } + if(pageIds[1].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(2); + } + if(pageIds[2].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(3); + } + if(pageIds[3].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(1); + } + } + } ) + .verifyComplete(); + } + + @Test + @WithUserDetails(value ="api_user") + public void reOrderPageFromLowOrderToHighOrder() { + + User apiUser = userService.findByEmail("api_user").block(); + orgId = apiUser.getOrganizationIds().iterator().next(); + Application newApp = new Application(); + newApp.setName(UUID.randomUUID().toString()); + + application = applicationPageService.createApplication(newApp, orgId).block(); + applicationId = application.getId(); + final String[] pageIds = new String[4]; + + PageDTO testPage1 = new PageDTO(); + testPage1.setName("Page2"); + testPage1.setApplicationId(applicationId); + Mono applicationPageReOrdered = applicationPageService.createPage(testPage1) + .flatMap(pageDTO -> { + PageDTO testPage = new PageDTO(); + testPage.setName("Page3"); + testPage.setApplicationId(applicationId); + return applicationPageService.createPage(testPage); + }) + .flatMap(pageDTO -> { + PageDTO testPage = new PageDTO(); + testPage.setName("Page4"); + testPage.setApplicationId(applicationId); + return applicationPageService.createPage(testPage); + }) + .flatMap(pageDTO -> applicationService.getById(pageDTO.getApplicationId())) + .flatMap( application -> { + pageIds[0] = application.getPages().get(0).getId(); + pageIds[1] = application.getPages().get(1).getId(); + pageIds[2] = application.getPages().get(2).getId(); + pageIds[3] = application.getPages().get(3).getId(); + return applicationPageService.reorderPage(application.getId(), application.getPages().get(0).getId(), 3); + }); + + StepVerifier + .create(applicationPageReOrdered) + .assertNext(application -> { + final List pages = application.getPages(); + assertThat(application.getPages().size()).isEqualTo(4); + for(ApplicationPage page : pages) { + if(pageIds[0].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(3); + } + if(pageIds[1].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(0); + } + if(pageIds[2].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(1); + } + if(pageIds[3].equals(page.getId())) { + assertThat(page.getOrder()).isEqualTo(2); + } + } + } ) + .verifyComplete(); + } @After public void purgeAllPages() {