chore: fixing file lock issues on autocommit (#33496)

## Description 
- Refactoring consolidated Api to replace autocommit trigger.
- Reduced # db-calls for new pages.

Fixes #33384 

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results

## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added methods for retrieving and constructing application pages based
on branch and mode.
  - Improved logic and error handling for fetching application pages.
  - Enhanced schema migration for Git-connected apps.

- **Refactor**
- Replaced method calls in `ConsolidatedAPIServiceImpl` for better
performance.
- Modified method in `ApplicationServiceCE` for finding applications by
default ID, branch name, and mode.

- **Tests**
- Updated test cases in `ConsolidatedAPIServiceImplTest` for method call
modifications.
<!-- end of auto-generated comment: release notes by coderabbit.ai
--><!-- This is an auto-generated comment:


<!-- This is an auto-generated comment: Cypress test results  -->
> [!CAUTION]
> 🔴 🔴 🔴 Some tests have failed.
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/9110390548>
> Commit: a175879d86edbb9b221ac41570d76c0fc1ca754d
> Cypress dashboard: <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=9110390548&attempt=3&selectiontype=test&testsstatus=failed&specsstatus=fail"
target="_blank"> Click here!</a>
> The following are new failures, please fix them before merging the PR:
<ol>
>
<li>cypress/e2e/Regression/ClientSide/SettingsPane/EmbedSettings_spec.ts
</ol>
> To know the list of identified flaky tests - <a
href="https://internal.appsmith.com/app/cypress-dashboard/identified-flaky-tests-65890b3c81d7400d08fa9ee3?branch=master"
target="_blank">Refer here</a>

<!-- end of auto-generated comment: Cypress test results  -->
This commit is contained in:
Manish Kumar 2024-05-16 17:45:43 +05:30 committed by GitHub
parent c43a7c5645
commit f177b224c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 520 additions and 163 deletions

View File

@ -2,6 +2,7 @@ package com.appsmith.server.applications.base;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.dtos.ApplicationAccessDTO;
import com.appsmith.server.dtos.GitAuthDTO;
@ -91,4 +92,7 @@ public interface ApplicationServiceCE extends CrudService<Application, String> {
Mono<Boolean> isApplicationConnectedToGit(String applicationId);
Mono<Void> updateProtectedBranches(String applicationId, List<String> protectedBranches);
Mono<Application> findByDefaultIdBranchNameAndApplicationMode(
String defaultApplicationId, String branchName, ApplicationMode mode);
}

View File

@ -884,7 +884,7 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
AppsmithError.NO_RESOURCE_FOUND,
FieldName.APPLICATION,
defaultApplicationId + ", " + branchName)))
.map(Application::getId);
.map(application -> application.getId());
}
public Mono<String> findBranchedApplicationId(
@ -1050,4 +1050,21 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
}))
.then();
}
/**
* Gets branched application with the right permission set based on mode of application
* @param defaultApplicationId : default app id
* @param branchName : branch name of the application
* @param mode : is it edit mode or view mode
* @return : returns a publisher of branched application
*/
@Override
public Mono<Application> findByDefaultIdBranchNameAndApplicationMode(
String defaultApplicationId, String branchName, ApplicationMode mode) {
AclPermission permissionForApplication = ApplicationMode.PUBLISHED.equals(mode)
? applicationPermission.getReadPermission()
: applicationPermission.getEditPermission();
return findByBranchNameAndDefaultApplicationId(branchName, defaultApplicationId, permissionForApplication);
}
}

View File

@ -1,6 +1,7 @@
package com.appsmith.server.newpages.base;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewPage;
@ -90,4 +91,9 @@ public interface NewPageServiceCE extends CrudService<NewPage, String> {
Flux<NewPage> findPageSlugsByApplicationIds(List<String> applicationIds, AclPermission aclPermission);
Mono<Void> publishPages(Collection<String> pageIds, AclPermission permission);
ApplicationPagesDTO getApplicationPagesDTO(Application application, List<NewPage> newPages, boolean viewMode);
Mono<ApplicationPagesDTO> createApplicationPagesDTO(
Application branchedApplication, List<NewPage> newPages, boolean viewMode, boolean isRecentlyAccessed);
}

View File

@ -30,6 +30,7 @@ import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
import org.bson.types.ObjectId;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@ -38,7 +39,6 @@ import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@ -222,30 +222,31 @@ public class NewPageServiceCEImpl extends BaseService<NewPageRepository, NewPage
public Mono<ApplicationPagesDTO> findApplicationPagesByApplicationIdViewMode(
String applicationId, Boolean view, boolean markApplicationAsRecentlyAccessed) {
AclPermission permission;
if (view) {
permission = applicationPermission.getReadPermission();
} else {
permission = applicationPermission.getEditPermission();
}
AclPermission permission = Boolean.TRUE.equals(view)
? applicationPermission.getReadPermission()
: applicationPermission.getEditPermission();
Mono<Application> applicationMono = applicationService
.findById(applicationId, permission)
.switchIfEmpty(Mono.error(new AppsmithException(
AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId)))
// Throw a 404 error if the application has never been published
.flatMap(application -> {
if (Boolean.TRUE.equals(view)) {
if (application.getPublishedPages() == null
|| application.getPublishedPages().isEmpty()) {
// We are trying to fetch published pages but they don't exist because the application
// hasn't been published yet
return Mono.error(new AppsmithException(
AppsmithError.ACL_NO_RESOURCE_FOUND,
FieldName.PUBLISHED_APPLICATION,
application.getId()));
}
if (!Boolean.TRUE.equals(view)) {
return Mono.just(application);
}
if (application.getPublishedPages() == null
|| application.getPublishedPages().isEmpty()) {
// We are trying to fetch published pages but they don't exist because the application
// hasn't been published yet
// Throw a 404 error if the application has never been published
return Mono.error(new AppsmithException(
AppsmithError.ACL_NO_RESOURCE_FOUND,
FieldName.PUBLISHED_APPLICATION,
application.getId()));
}
return Mono.just(application);
})
.flatMap(application -> {
@ -264,121 +265,155 @@ public class NewPageServiceCEImpl extends BaseService<NewPageRepository, NewPage
})
.cache();
Mono<String> defaultPageIdMono = applicationMono.map(application -> {
String defaultPageId = null;
List<ApplicationPage> applicationPages;
if (Boolean.TRUE.equals(view)) {
applicationPages = application.getPublishedPages();
} else {
applicationPages = application.getPages();
}
for (ApplicationPage applicationPage : applicationPages) {
if (Boolean.TRUE.equals(applicationPage.getIsDefault())) {
defaultPageId = applicationPage.getId();
}
}
if (!StringUtils.hasLength(defaultPageId) && !CollectionUtils.isEmpty(applicationPages)) {
log.error("application {} has no default page, returning first page as default", application.getId());
defaultPageId = applicationPages.get(0).getId();
}
return defaultPageId;
});
Mono<List<PageNameIdDTO>> pagesListMono = applicationMono
return applicationMono
.map(application -> {
List<ApplicationPage> pages;
if (Boolean.TRUE.equals(view)) {
pages = application.getPublishedPages();
} else {
pages = application.getPages();
}
List<ApplicationPage> pages = getApplicationPages(application, view);
return pages.stream().map(page -> page.getId()).collect(Collectors.toList());
})
.flatMapMany(pageIds -> repository.findAllPageDTOsByIds(pageIds, pagePermission.getReadPermission()))
.collectList()
.flatMap(pagesFromDb -> Mono.zip(Mono.just(pagesFromDb), defaultPageIdMono, applicationMono))
.flatMap(tuple -> {
.zipWith(applicationMono)
.map(tuple -> {
log.debug("Retrieved Page DTOs from DB ...");
List<NewPage> pagesFromDb = tuple.getT1();
String defaultPageId = tuple.getT2();
List<PageNameIdDTO> pageNameIdDTOList = new ArrayList<>();
List<ApplicationPage> pages = tuple.getT3().getPages();
List<ApplicationPage> publishedPages = tuple.getT3().getPublishedPages();
Map<String, Integer> pagesOrder = new HashMap<>();
Map<String, Integer> publishedPagesOrder = new HashMap<>();
if (Boolean.TRUE.equals(view)) {
for (int i = 0; i < publishedPages.size(); i++) {
publishedPagesOrder.put(publishedPages.get(i).getId(), i);
}
} else {
for (int i = 0; i < pages.size(); i++) {
pagesOrder.put(pages.get(i).getId(), i);
}
}
for (NewPage pageFromDb : pagesFromDb) {
PageNameIdDTO pageNameIdDTO = new PageNameIdDTO();
pageNameIdDTO.setId(pageFromDb.getId());
if (pageFromDb.getDefaultResources() == null) {
return Mono.error(new AppsmithException(
AppsmithError.DEFAULT_RESOURCES_UNAVAILABLE, "page", pageFromDb.getId()));
}
pageNameIdDTO.setDefaultPageId(
pageFromDb.getDefaultResources().getPageId());
PageDTO pageDTO;
if (Boolean.TRUE.equals(view)) {
if (pageFromDb.getPublishedPage() == null) {
// We are trying to fetch published page but it doesnt exist because the page hasn't
// been published yet
return Mono.error(new AppsmithException(
AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.PAGE, pageFromDb.getId()));
}
pageDTO = pageFromDb.getPublishedPage();
} else {
pageDTO = pageFromDb.getUnpublishedPage();
}
pageNameIdDTO.setName(pageDTO.getName());
pageNameIdDTO.setIsHidden(pageDTO.getIsHidden());
pageNameIdDTO.setSlug(pageDTO.getSlug());
pageNameIdDTO.setIcon(pageDTO.getIcon());
pageNameIdDTO.setCustomSlug(pageDTO.getCustomSlug());
pageNameIdDTO.setUserPermissions(pageFromDb.getUserPermissions());
if (pageNameIdDTO.getId().equals(defaultPageId)) {
pageNameIdDTO.setIsDefault(true);
} else {
pageNameIdDTO.setIsDefault(false);
}
pageNameIdDTOList.add(pageNameIdDTO);
}
if (Boolean.TRUE.equals(view)) {
Collections.sort(
pageNameIdDTOList, Comparator.comparing(item -> publishedPagesOrder.get(item.getId())));
} else {
Collections.sort(pageNameIdDTOList, Comparator.comparing(item -> pagesOrder.get(item.getId())));
}
return Mono.just(pageNameIdDTOList);
Application application = tuple.getT2();
return getApplicationPagesDTO(application, pagesFromDb, view);
});
}
return Mono.zip(applicationMono, pagesListMono).map(tuple -> {
log.debug("Populating applicationPagesDTO ...");
Application application = tuple.getT1();
application.setPages(null);
application.setPublishedPages(null);
application.setViewMode(view);
List<PageNameIdDTO> nameIdDTOList = tuple.getT2();
ApplicationPagesDTO applicationPagesDTO = new ApplicationPagesDTO();
applicationPagesDTO.setWorkspaceId(application.getWorkspaceId());
applicationPagesDTO.setPages(nameIdDTOList);
applicationPagesDTO.setApplication(application);
return applicationPagesDTO;
});
/**
* Creates applicationPagesDTO and then updates the resources with default resources
*
* @param branchedApplication : branched application
* @param newPages : list of pages for the given mode
* @param viewMode : is application in viewMode
* @param markRecentlyAccessed : is
* @return : returns the getApplicationPagesDTO
*/
@Override
public Mono<ApplicationPagesDTO> createApplicationPagesDTO(
Application branchedApplication, List<NewPage> newPages, boolean viewMode, boolean markRecentlyAccessed) {
Mono<Void> markedRecentlyAccessedMono = Mono.empty();
if (Boolean.TRUE.equals(markRecentlyAccessed)) {
markedRecentlyAccessedMono = userDataService
.updateLastUsedResourceAndWorkspaceList(
branchedApplication.getId(),
branchedApplication.getWorkspaceId(),
WorkspaceResourceContext.APPLICATIONS)
.then();
}
return markedRecentlyAccessedMono
.then(Mono.fromCallable(() -> getApplicationPagesDTO(branchedApplication, newPages, viewMode)))
.map(responseUtils::updateApplicationPagesDTOWithDefaultResources);
}
private List<ApplicationPage> getApplicationPages(Application application, boolean viewMode) {
return Boolean.TRUE.equals(viewMode) ? application.getPublishedPages() : application.getPages();
}
private String getHomePageId(Application application, boolean viewMode) {
String homePageId = null;
List<ApplicationPage> applicationPages = getApplicationPages(application, viewMode);
for (ApplicationPage applicationPage : applicationPages) {
if (Boolean.TRUE.equals(applicationPage.getIsDefault())) {
homePageId = applicationPage.getId();
break;
}
}
if (!StringUtils.hasLength(homePageId) && !CollectionUtils.isEmpty(applicationPages)) {
log.error("application {} has no default page, returning first page as default", application.getId());
homePageId = applicationPages.get(0).getId();
}
return homePageId;
}
/**
* Creates ApplicationPagesDTO
* @param application : branched application
* @param newPages : list of pages for the given mode
* @param viewMode : is application in viewMode
* @return : returns the getApplicationPagesDTO
*/
public ApplicationPagesDTO getApplicationPagesDTO(
Application application, List<NewPage> newPages, boolean viewMode) {
String homePageId = getHomePageId(application, viewMode);
List<PageNameIdDTO> pageNameIdDTOList = new ArrayList<>();
List<ApplicationPage> applicationPages = application.getPages();
List<ApplicationPage> publishedApplicationPages = application.getPublishedPages();
Map<String, Integer> pagesOrder = new HashMap<>();
Map<String, Integer> publishedPagesOrder = new HashMap<>();
if (Boolean.TRUE.equals(viewMode)) {
for (int i = 0; i < publishedApplicationPages.size(); i++) {
publishedPagesOrder.put(publishedApplicationPages.get(i).getId(), i);
}
} else {
for (int i = 0; i < applicationPages.size(); i++) {
pagesOrder.put(applicationPages.get(i).getId(), i);
}
}
for (NewPage pageFromDb : newPages) {
PageNameIdDTO pageNameIdDTO = getPageNameIdDTO(pageFromDb, homePageId, viewMode);
pageNameIdDTOList.add(pageNameIdDTO);
}
if (Boolean.TRUE.equals(viewMode)) {
pageNameIdDTOList.sort(Comparator.comparing(item -> publishedPagesOrder.get(item.getId())));
} else {
pageNameIdDTOList.sort(Comparator.comparing(item -> pagesOrder.get(item.getId())));
}
application.setPages(null);
application.setPublishedPages(null);
application.setViewMode(viewMode);
ApplicationPagesDTO applicationPagesDTO = new ApplicationPagesDTO();
applicationPagesDTO.setWorkspaceId(application.getWorkspaceId());
applicationPagesDTO.setPages(pageNameIdDTOList);
applicationPagesDTO.setApplication(application);
return applicationPagesDTO;
}
private static @NotNull PageNameIdDTO getPageNameIdDTO(NewPage pageFromDb, String homePageId, boolean viewMode) {
PageNameIdDTO pageNameIdDTO = new PageNameIdDTO();
pageNameIdDTO.setId(pageFromDb.getId());
if (pageFromDb.getDefaultResources() == null) {
throw new AppsmithException(AppsmithError.DEFAULT_RESOURCES_UNAVAILABLE, "page", pageFromDb.getId());
}
pageNameIdDTO.setDefaultPageId(pageFromDb.getDefaultResources().getPageId());
PageDTO pageDTO;
if (Boolean.TRUE.equals(viewMode)) {
if (pageFromDb.getPublishedPage() == null) {
// We are trying to fetch published pages, however;
// it doesn't exist because the page hasn't been published yet
throw new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.PAGE, pageFromDb.getId());
}
pageDTO = pageFromDb.getPublishedPage();
} else {
pageDTO = pageFromDb.getUnpublishedPage();
}
pageNameIdDTO.setName(pageDTO.getName());
pageNameIdDTO.setIsHidden(pageDTO.getIsHidden());
pageNameIdDTO.setSlug(pageDTO.getSlug());
pageNameIdDTO.setIcon(pageDTO.getIcon());
pageNameIdDTO.setCustomSlug(pageDTO.getCustomSlug());
pageNameIdDTO.setUserPermissions(pageFromDb.getUserPermissions());
pageNameIdDTO.setIsDefault(pageNameIdDTO.getId().equals(homePageId));
return pageNameIdDTO;
}
public Mono<ApplicationPagesDTO> findApplicationPagesByApplicationIdViewModeAndBranch(

View File

@ -4,8 +4,11 @@ import com.appsmith.external.exceptions.ErrorDTO;
import com.appsmith.external.models.CreatorContextType;
import com.appsmith.external.models.Datasource;
import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.datasources.base.DatasourceService;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.dtos.ApplicationPagesDTO;
import com.appsmith.server.dtos.ConsolidatedAPIResponseDTO;
@ -84,6 +87,7 @@ public class ConsolidatedAPIServiceImpl implements ConsolidatedAPIService {
private final ActionCollectionService actionCollectionService;
private final ThemeService themeService;
private final ApplicationPageService applicationPageService;
private final ApplicationService applicationService;
private final CustomJSLibService customJSLibService;
private final PluginService pluginService;
private final DatasourceService datasourceService;
@ -199,14 +203,31 @@ public class ConsolidatedAPIServiceImpl implements ConsolidatedAPIService {
applicationIdMonoCache = Mono.just(applicationId).cache();
}
/* Get all pages in application */
Mono<ResponseDTO<ApplicationPagesDTO>> applicationPagesDTOResponseDTOMonoCache = applicationIdMonoCache
.flatMap(appId -> newPageService.findApplicationPages(appId, null, branchName, mode))
// dslMigration-over-here using the branchName and defaultId
Mono<Application> branchedApplicationMonoCached = applicationIdMonoCache
.flatMap(defaultApplicationId -> applicationService.findByDefaultIdBranchNameAndApplicationMode(
defaultApplicationId, branchName, mode))
.cache();
Mono<List<NewPage>> pagesFromCurrentApplicationMonoCached = branchedApplicationMonoCached
.flatMap(branchedApplication ->
applicationPageService.getPagesBasedOnApplicationMode(branchedApplication, mode))
.cache();
/* Get all applicationPages in application */
Mono<ResponseDTO<ApplicationPagesDTO>> applicationPagesDTOResponseDTOMonoCache = Mono.zip(
branchedApplicationMonoCached, pagesFromCurrentApplicationMonoCached)
.flatMap(tuple2 -> {
Application branchedApplication = tuple2.getT1();
List<NewPage> newPages = tuple2.getT2();
return newPageService.createApplicationPagesDTO(branchedApplication, newPages, isViewMode, true);
})
.as(this::toResponseDTO)
.doOnSuccess(consolidatedAPIResponseDTO::setPages)
.name(getQualifiedSpanName(PAGES_SPAN, mode))
.tap(Micrometer.observation(observationRegistry))
.cache();
fetches.add(applicationPagesDTOResponseDTOMonoCache);
/* Get current theme */
@ -237,8 +258,9 @@ public class ConsolidatedAPIServiceImpl implements ConsolidatedAPIService {
if (!isBlank(defaultPageId)) {
/* Get current page */
fetches.add(applicationPageService
.getPageAndMigrateDslByBranchAndDefaultPageId(defaultPageId, branchName, isViewMode, true)
fetches.add(pagesFromCurrentApplicationMonoCached
.then(applicationPageService.getPageAndMigrateDslByBranchAndDefaultPageId(
defaultPageId, branchName, isViewMode, true))
.as(this::toResponseDTO)
.doOnSuccess(consolidatedAPIResponseDTO::setPageWithMigratedDsl)
.name(getQualifiedSpanName(CURRENT_PAGE_SPAN, mode))
@ -296,12 +318,9 @@ public class ConsolidatedAPIServiceImpl implements ConsolidatedAPIService {
.tap(Micrometer.observation(observationRegistry)));
/* Get all pages in edit mode post apply migrate DSL changes */
fetches.add(applicationPagesDTOResponseDTOMonoCache
.map(ResponseDTO::getData)
.map(ApplicationPagesDTO::getPages)
fetches.add(pagesFromCurrentApplicationMonoCached
.flatMapMany(Flux::fromIterable)
.flatMap(page -> applicationPageService.getPageAndMigrateDslByBranchAndDefaultPageId(
page.getDefaultPageId(), branchName, false, true))
.flatMap(page -> applicationPageService.getPageDTOAfterMigratingDSL(page, false, true))
.collect(Collectors.toList())
.as(this::toResponseDTO)
.doOnSuccess(consolidatedAPIResponseDTO::setPagesWithMigratedDsl)

View File

@ -2,6 +2,7 @@ package com.appsmith.server.services.ce;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.ApplicationPagesDTO;
@ -9,6 +10,7 @@ import com.appsmith.server.dtos.ClonePageMetaDTO;
import com.appsmith.server.dtos.PageDTO;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Optional;
public interface ApplicationPageServiceCE {
@ -70,4 +72,9 @@ public interface ApplicationPageServiceCE {
Mono<Application> createOrUpdateSuffixedApplication(Application application, String name, int suffix);
int getEvaluationVersion();
Mono<List<NewPage>> getPagesBasedOnApplicationMode(
Application branchedApplication, ApplicationMode applicationMode);
Mono<PageDTO> getPageDTOAfterMigratingDSL(NewPage newPage, boolean viewMode, boolean migrateDsl);
}

View File

@ -34,6 +34,7 @@ import com.appsmith.server.dtos.PageNameIdDTO;
import com.appsmith.server.dtos.PluginTypeAndCountDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.CollectionUtils;
import com.appsmith.server.helpers.DSLMigrationUtils;
import com.appsmith.server.helpers.GitFileUtils;
import com.appsmith.server.helpers.GitUtils;
@ -281,23 +282,110 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
}
@Override
public Mono<PageDTO> getPageAndMigrateDslByBranchAndDefaultPageId(
String defaultPageId, String branchName, boolean viewMode, boolean migrateDsl) {
// Fetch the page with read permission in both editor and in viewer.
public Mono<List<NewPage>> getPagesBasedOnApplicationMode(
Application branchedApplication, ApplicationMode applicationMode) {
Boolean viewMode = ApplicationMode.PUBLISHED.equals(applicationMode) ? Boolean.TRUE : Boolean.FALSE;
List<ApplicationPage> applicationPages = Boolean.TRUE.equals(viewMode)
? branchedApplication.getPublishedPages()
: branchedApplication.getPages();
Set<String> pageIds =
applicationPages.stream().map(ApplicationPage::getId).collect(Collectors.toSet());
return newPageService
.findByBranchNameAndDefaultPageId(branchName, defaultPageId, pagePermission.getReadPermission())
.flatMap(newPage -> {
return sendPageViewAnalyticsEvent(newPage, viewMode)
.then(getPage(newPage, viewMode))
.zipWith(Mono.just(newPage));
.findNewPagesByApplicationId(branchedApplication.getId(), pagePermission.getReadPermission())
.filter(newPage -> pageIds.contains(newPage.getId()))
.collectList()
.flatMap(newPageList -> {
if (Boolean.TRUE.equals(viewMode)) {
return Mono.just(newPageList);
}
// autocommit if migration is required
return migrateSchemasForGitConnectedApps(branchedApplication, newPageList)
.onErrorResume(error -> {
log.debug(
"Skipping the autocommit for applicationId : {} due to error; {}",
branchedApplication.getId(),
error.getMessage());
return Mono.just(Boolean.FALSE);
})
.thenReturn(newPageList);
});
}
/**
* Publishes the autocommit if it's eligible for one
* @param application : the branched application which requires schemaMigration
* @param newPages : list of pages from db
* @return : a boolean publisher
*/
private Mono<Boolean> migrateSchemasForGitConnectedApps(Application application, List<NewPage> newPages) {
if (CollectionUtils.isNullOrEmpty(newPages)) {
return Mono.just(Boolean.FALSE);
}
if (application.getGitArtifactMetadata() == null) {
return Mono.just(Boolean.FALSE);
}
GitArtifactMetadata gitMetadata = application.getGitArtifactMetadata();
String defaultApplicationId = gitMetadata.getDefaultArtifactId();
String branchName = gitMetadata.getBranchName();
if (!StringUtils.hasText(branchName)) {
log.debug(
"Skipping the autocommit for applicationId : {}, branch name is not present", application.getId());
return Mono.just(Boolean.FALSE);
}
if (!StringUtils.hasText(defaultApplicationId)) {
log.debug(
"Skipping the autocommit for applicationId : {}, defaultApplicationId is not present",
application.getId());
return Mono.just(Boolean.FALSE);
}
// since this method is only called when the app is in edit mode
Mono<PageDTO> pageDTOMono = getPage(newPages.get(0), false);
return pageDTOMono
.zipWith(dslMigrationUtils.getLatestDslVersion())
.onErrorMap(throwable -> {
log.error("Error fetching latest DSL version", throwable);
return new AppsmithException(AppsmithError.RTS_SERVER_ERROR, "Error fetching latest DSL version");
})
.flatMap(objects -> {
PageDTO pageDTO = objects.getT1();
.flatMap(tuple2 -> {
PageDTO pageDTO = tuple2.getT1();
Integer latestDslVersion = tuple2.getT2();
// ensuring that the page has only one layout, as we don't support multiple layouts yet
// when multiple layouts are supported, this code will have to be updated
assert pageDTO.getLayouts().size() == 1;
Layout layout = pageDTO.getLayouts().get(0);
JSONObject layoutDsl = layout.getDsl();
boolean isMigrationRequired = GitUtils.isMigrationRequired(layoutDsl, latestDslVersion);
if (isMigrationRequired) {
// Triggering the autocommit
return gitAutoCommitHelper.autoCommitApplication(defaultApplicationId, branchName);
}
return Mono.just(Boolean.FALSE);
});
}
@Override
public Mono<PageDTO> getPageDTOAfterMigratingDSL(NewPage newPage, boolean viewMode, boolean migrateDsl) {
return sendPageViewAnalyticsEvent(newPage, viewMode)
.then(getPage(newPage, viewMode))
.flatMap(pageDTO -> {
if (migrateDsl) {
// Call the DSL Utils for on demand migration of the page.
// Based on view mode save the migrated DSL to the database
// Migrate the DSL to the latest version if required
NewPage newPage = objects.getT2();
if (pageDTO.getLayouts() != null) {
return migrateAndUpdatePageDsl(newPage, pageDTO, viewMode);
}
@ -307,6 +395,15 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
.map(responseUtils::updatePageDTOWithDefaultResources);
}
@Override
public Mono<PageDTO> getPageAndMigrateDslByBranchAndDefaultPageId(
String defaultPageId, String branchName, boolean viewMode, boolean migrateDsl) {
// Fetch the page with read permission in both editor and in viewer.
return newPageService
.findByBranchNameAndDefaultPageId(branchName, defaultPageId, pagePermission.getReadPermission())
.flatMap(newPage -> getPageDTOAfterMigratingDSL(newPage, viewMode, migrateDsl));
}
private Mono<PageDTO> migrateAndUpdatePageDsl(NewPage newPage, PageDTO page, boolean viewMode) {
return dslMigrationUtils
.getLatestDslVersion()
@ -323,25 +420,15 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
JSONObject layoutDsl = layout.getDsl();
boolean isMigrationRequired = GitUtils.isMigrationRequired(layoutDsl, latestDslVersion);
if (isMigrationRequired) {
// if edit mode, then trigger the auto commit event
Mono<Boolean> autoCommitEventRunner;
if (!viewMode) {
autoCommitEventRunner = gitAutoCommitHelper.autoCommitApplication(
newPage.getDefaultResources().getApplicationId(),
newPage.getDefaultResources().getBranchName());
} else {
autoCommitEventRunner = Mono.just(Boolean.FALSE);
}
// zipping them so that they can run in parallel
return Mono.zip(dslMigrationUtils.migratePageDsl(layoutDsl), autoCommitEventRunner)
return dslMigrationUtils
.migratePageDsl(layoutDsl)
.onErrorMap(throwable -> {
log.error("Error while migrating DSL ", throwable);
return new AppsmithException(
AppsmithError.RTS_SERVER_ERROR,
"Error while migrating to latest DSL version");
})
.flatMap(tuple2 -> {
JSONObject migratedDsl = tuple2.getT1();
.flatMap(migratedDsl -> {
// update the current page DTO with migrated dsl
page.getLayouts().get(0).setDsl(migratedDsl);

View File

@ -5,6 +5,7 @@ import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewPage;
@ -41,7 +42,9 @@ import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@ -431,4 +434,63 @@ public class ApplicationPageServiceTest {
&& throwable.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.ID, id)))
.verify();
}
@Test
@WithUserDetails("api_user")
public void verifyGetPagesBasedOnApplicationMode_ReturnsRigthNumberOfPages_BasedOnApplicationMode() {
final String appName = "app" + UUID.randomUUID();
Application application = new Application();
application.setName(appName);
Application createdApplication = applicationPageService
.createApplication(application, workspace.getId())
.block();
String applicationId = createdApplication.getId();
PageDTO pageDTO = new PageDTO();
pageDTO.setApplicationId(applicationId);
final String pageName = "app" + UUID.randomUUID();
pageDTO.setName(pageName);
applicationPageService.createPage(pageDTO).block();
applicationPageService.publish(applicationId, true).block();
PageDTO pageDTO1 = new PageDTO();
pageDTO1.setApplicationId(applicationId);
final String unpublishedPageName = "app" + UUID.randomUUID();
pageDTO1.setName(unpublishedPageName);
applicationPageService.createPage(pageDTO1).block();
Application updatedApplication =
applicationService.findById(createdApplication.getId()).block();
Mono<List<NewPage>> unpublishedPagesMono =
applicationPageService.getPagesBasedOnApplicationMode(updatedApplication, ApplicationMode.EDIT);
Mono<List<NewPage>> publishedPagesMono =
applicationPageService.getPagesBasedOnApplicationMode(updatedApplication, ApplicationMode.PUBLISHED);
StepVerifier.create(publishedPagesMono)
.assertNext(pages -> {
assertThat(pages.size()).isEqualTo(2);
Set<String> pageNames = pages.stream()
.map(page -> page.getUnpublishedPage().getName())
.collect(Collectors.toSet());
assertThat(pageNames).contains(pageName);
assertThat(pageNames).doesNotContain(unpublishedPageName);
})
.verifyComplete();
StepVerifier.create(unpublishedPagesMono)
.assertNext(pages -> {
assertThat(pages.size()).isEqualTo(3);
Set<String> pageNames = pages.stream()
.map(page -> page.getUnpublishedPage().getName())
.collect(Collectors.toSet());
assertThat(pageNames).contains(pageName);
assertThat(pageNames).contains(unpublishedPageName);
})
.verifyComplete();
}
}

View File

@ -6,8 +6,10 @@ import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.datasources.base.DatasourceService;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.CustomJSLib;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.domains.Tenant;
import com.appsmith.server.domains.Theme;
@ -214,8 +216,18 @@ public class ConsolidatedAPIServiceImplTest {
ApplicationPagesDTO sampleApplicationPagesDTO = new ApplicationPagesDTO();
sampleApplicationPagesDTO.setWorkspaceId("sampleWorkspaceId");
when(spyNewPageService.findApplicationPages(anyString(), any(), anyString(), any()))
.thenReturn(Mono.just(sampleApplicationPagesDTO));
doReturn(Mono.just(new Application()))
.when(spyApplicationService)
.findByDefaultIdBranchNameAndApplicationMode(anyString(), anyString(), any());
doReturn(Mono.just(List.of(new NewPage())))
.when(spyApplicationPageService)
.getPagesBasedOnApplicationMode(any(), any());
doReturn(Mono.just(sampleApplicationPagesDTO))
.when(spyNewPageService)
.createApplicationPagesDTO(any(), any(), anyBoolean(), anyBoolean());
Theme sampleTheme = new Theme();
sampleTheme.setName("sampleTheme");
@ -385,8 +397,18 @@ public class ConsolidatedAPIServiceImplTest {
ApplicationPagesDTO sampleApplicationPagesDTO = new ApplicationPagesDTO();
sampleApplicationPagesDTO.setWorkspaceId("sampleWorkspaceId");
when(spyNewPageService.findApplicationPages(anyString(), any(), anyString(), any()))
.thenReturn(Mono.just(sampleApplicationPagesDTO));
doReturn(Mono.just(new Application()))
.when(spyApplicationService)
.findByDefaultIdBranchNameAndApplicationMode(anyString(), anyString(), any());
doReturn(Mono.just(List.of(new NewPage())))
.when(spyApplicationPageService)
.getPagesBasedOnApplicationMode(any(), any());
doReturn(Mono.just(sampleApplicationPagesDTO))
.when(spyNewPageService)
.createApplicationPagesDTO(any(), any(), anyBoolean(), anyBoolean());
Theme sampleTheme = new Theme();
sampleTheme.setName("sampleTheme");
@ -406,6 +428,11 @@ public class ConsolidatedAPIServiceImplTest {
.when(spyApplicationPageService)
.getPageAndMigrateDslByBranchAndDefaultPageId(anyString(), anyString(), anyBoolean(), anyBoolean());
doReturn(Mono.just(samplePageDTO))
.doReturn(Mono.just(samplePageDTO))
.when(spyApplicationPageService)
.getPageDTOAfterMigratingDSL(any(), anyBoolean(), anyBoolean());
ActionDTO sampleActionDTO = new ActionDTO();
sampleActionDTO.setName("sampleActionDTO");
sampleActionDTO.setUpdatedAt(Instant.now());

View File

@ -10,6 +10,8 @@ import com.appsmith.server.domains.PermissionGroup;
import com.appsmith.server.domains.Workspace;
import com.appsmith.server.dtos.ApplicationPagesDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.dtos.PageNameIdDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.TextUtils;
import com.appsmith.server.newpages.base.NewPageService;
@ -18,6 +20,7 @@ import com.appsmith.server.repositories.NewPageRepository;
import com.appsmith.server.repositories.PermissionGroupRepository;
import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.PagePermission;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -31,12 +34,15 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
@Slf4j
@SpringBootTest
@ExtendWith(SpringExtension.class)
public class NewPageServiceTest {
@ -313,4 +319,91 @@ public class NewPageServiceTest {
})
.verifyComplete();
}
@Test
@WithUserDetails("api_user")
public void verifyCreateApplicationPagesDTO_ReturnsRightNumberOfPages_BasedOnViewMode() {
final String appName = "app" + UUID.randomUUID();
Application application = new Application();
application.setName(appName);
String applicationId = applicationPageService
.createApplication(application, workspaceId)
.block()
.getId();
PageDTO pageDTO = new PageDTO();
pageDTO.setApplicationId(applicationId);
final String pageName = "app" + UUID.randomUUID();
pageDTO.setName(pageName);
applicationPageService.createPage(pageDTO).block();
applicationPageService.publish(applicationId, true).block();
List<NewPage> publishedPages = newPageService
.findNewPagesByApplicationId(applicationId, pagePermission.getReadPermission())
.collectList()
.block();
PageDTO pageDTO1 = new PageDTO();
pageDTO1.setApplicationId(applicationId);
final String unpublishedPageName = "app" + UUID.randomUUID();
pageDTO1.setName(unpublishedPageName);
applicationPageService.createPage(pageDTO1).block();
Application updatedApplication =
applicationService.findById(applicationId).block();
List<NewPage> allPages = newPageService
.findNewPagesByApplicationId(applicationId, pagePermission.getReadPermission())
.collectList()
.block();
Mono<ApplicationPagesDTO> applicationPagesDTOMono =
newPageService.createApplicationPagesDTO(updatedApplication, allPages, false, true);
StepVerifier.create(applicationPagesDTOMono)
.assertNext(applicationPagesDTO -> {
assertThat(applicationPagesDTO.getWorkspaceId()).isEqualTo(workspaceId);
assertThat(applicationPagesDTO.getApplication().getName()).isEqualTo(updatedApplication.getName());
assertThat(applicationPagesDTO.getApplication().getId()).isEqualTo(updatedApplication.getId());
assertThat(applicationPagesDTO.getPages().size()).isEqualTo(3);
Map<String, PageNameIdDTO> pageNameIdMap = applicationPagesDTO.getPages().stream()
.collect(Collectors.toMap(PageNameIdDTO::getName, Function.identity()));
assertThat(pageNameIdMap.keySet()).contains(unpublishedPageName);
assertThat(pageNameIdMap.keySet()).contains(pageName);
assertThat(pageNameIdMap.get("Page1").getIsDefault()).isTrue();
})
.verifyComplete();
Mono<ApplicationPagesDTO> viewModeTrueApplicationPagesDTOMono = newPageService.createApplicationPagesDTO(
applicationService.findById(applicationId).block(), publishedPages, true, true);
StepVerifier.create(viewModeTrueApplicationPagesDTOMono)
.assertNext(applicationPagesDTO -> {
assertThat(applicationPagesDTO.getWorkspaceId()).isEqualTo(workspaceId);
assertThat(applicationPagesDTO.getApplication().getName()).isEqualTo(updatedApplication.getName());
assertThat(applicationPagesDTO.getApplication().getId()).isEqualTo(updatedApplication.getId());
assertThat(applicationPagesDTO.getPages().size()).isEqualTo(2);
Map<String, PageNameIdDTO> pageNameIdMap = applicationPagesDTO.getPages().stream()
.collect(Collectors.toMap(PageNameIdDTO::getName, Function.identity()));
assertThat(pageNameIdMap.keySet()).doesNotContain(unpublishedPageName);
assertThat(pageNameIdMap.keySet()).contains(pageName);
assertThat(pageNameIdMap.get("Page1").getIsDefault()).isTrue();
})
.verifyComplete();
Mono<ApplicationPagesDTO> viewModeTrueMono = newPageService.createApplicationPagesDTO(
applicationService.findById(applicationId).block(), allPages, true, true);
StepVerifier.create(viewModeTrueMono).verifyErrorSatisfies(error -> {
assertThat(error).isInstanceOf(AppsmithException.class);
assertThat(((AppsmithException) error).getAppErrorCode())
.isEqualTo(AppsmithError.ACL_NO_RESOURCE_FOUND.getAppErrorCode());
});
}
}