chore: Add code-split for widget refactoring in UI module (#40226)

## Description
EE Counterpart: https://github.com/appsmithorg/appsmith-ee/pull/7143


Fixes #`Issue Number`  
_or_  
Fixes `Issue URL`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

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

### 🔍 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/14404193022>
> Commit: 5585f90660cc8d16e3a6954db12c999b3e8b6060
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14404193022&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Fri, 11 Apr 2025 14:44:36 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**
- Introduced context-aware layout management that enhances page design
updates and dynamic refactoring.
- Added improved handling for layout and widget updates, offering a
smoother editing experience.

- **Refactor**
- Streamlined layout update processes with adaptive behavior based on
context.
- Optimized service interactions for more robust and modular page and
widget processing.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
subratadeypappu 2025-04-16 15:56:50 +06:00 committed by GitHub
parent 86c5a77fa5
commit d4a0852955
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 443 additions and 155 deletions

View File

@ -0,0 +1,13 @@
package com.appsmith.server.domains.ce;
import com.appsmith.server.domains.Layout;
import java.util.List;
public interface LayoutContainer {
List<Layout> getLayouts();
void setLayouts(List<Layout> layouts);
String getId();
}

View File

@ -5,6 +5,7 @@ import com.appsmith.external.models.Policy;
import com.appsmith.external.views.Git;
import com.appsmith.external.views.Views;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.ce.LayoutContainer;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Getter;
@ -26,7 +27,7 @@ import java.util.Set;
@NoArgsConstructor
@ToString
@FieldNameConstants
public class PageDTO {
public class PageDTO implements LayoutContainer {
@Transient
@JsonView({Views.Public.class})

View File

@ -14,6 +14,9 @@ import java.util.Set;
public interface UpdateLayoutServiceCE {
Mono<LayoutDTO> updateLayout(String pageId, String applicationId, String layoutId, Layout layout);
Mono<LayoutDTO> updateLayout(
String pageId, String applicationId, String layoutId, Layout layout, CreatorContextType contextType);
Mono<Integer> updateMultipleLayouts(
String defaultApplicationId, UpdateMultiplePageLayoutDTO updateMultiplePageLayoutDTO);

View File

@ -7,7 +7,6 @@ import com.appsmith.external.exceptions.ErrorDTO;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.CreatorContextType;
import com.appsmith.external.models.Executable;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ExecutableDependencyEdge;
import com.appsmith.server.domains.Layout;
@ -21,6 +20,7 @@ import com.appsmith.server.helpers.ObservationHelperImpl;
import com.appsmith.server.helpers.WidgetSpecificUtils;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.onload.internal.OnLoadExecutablesUtil;
import com.appsmith.server.refactors.resolver.ContextLayoutRefactorResolver;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.PagePermission;
@ -59,6 +59,7 @@ import static com.appsmith.external.constants.spans.LayoutSpan.UPDATE_LAYOUT_MET
import static com.appsmith.external.constants.spans.PageSpan.GET_PAGE_BY_ID;
import static com.appsmith.external.constants.spans.ce.LayoutSpanCE.UPDATE_LAYOUT_BASED_ON_CONTEXT;
import static com.appsmith.server.constants.CommonConstants.EVALUATION_VERSION;
import static com.appsmith.server.helpers.ContextTypeUtils.isPageContext;
import static java.lang.Boolean.FALSE;
@Slf4j
@ -71,10 +72,10 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
private final NewPageService newPageService;
private final AnalyticsService analyticsService;
private final PagePermission pagePermission;
private final ApplicationService applicationService;
private final ObjectMapper objectMapper;
private final ObservationRegistry observationRegistry;
private final ObservationHelperImpl observationHelper;
private final ContextLayoutRefactorResolver contextLayoutRefactorResolver;
private final String layoutOnLoadActionErrorToastMessage =
"A cyclic dependency error has been encountered on current page, \nqueries on page load will not run. \n Please check debugger and Appsmith documentation for more information";
@ -117,7 +118,7 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
});
}
private Mono<LayoutDTO> updateLayoutDsl(
protected Mono<LayoutDTO> updateLayoutDsl(
String creatorId,
String layoutId,
Layout layout,
@ -247,22 +248,29 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
return layoutDTOMono;
}
// TODO: Add contextType and change all its usage to conform to that so that we can get rid of the overloaded
// updateLayout method
@Override
public Mono<LayoutDTO> updateLayout(String pageId, String applicationId, String layoutId, Layout layout) {
return applicationService
.findById(applicationId)
.switchIfEmpty(Mono.error(new AppsmithException(
AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, applicationId)))
.flatMap(application -> {
Integer evaluationVersion = application.getEvaluationVersion();
if (evaluationVersion == null) {
evaluationVersion = EVALUATION_VERSION;
}
return contextLayoutRefactorResolver
.getContextLayoutRefactorHelper(null)
.getEvaluationVersionMono(applicationId)
.flatMap(evaluationVersion -> {
return updateLayoutDsl(pageId, layoutId, layout, evaluationVersion, CreatorContextType.PAGE)
.name(UPDATE_LAYOUT_DSL_METHOD);
});
}
@Override
public Mono<LayoutDTO> updateLayout(
String pageId, String applicationId, String layoutId, Layout layout, CreatorContextType contextType) {
if (isPageContext(contextType)) {
return updateLayout(pageId, applicationId, layoutId, layout);
} else {
return updateLayoutDsl(pageId, layoutId, layout, EVALUATION_VERSION, contextType);
}
}
@Override
public Mono<Integer> updateMultipleLayouts(
String baseApplicationId, UpdateMultiplePageLayoutDTO updateMultiplePageLayoutDTO) {

View File

@ -1,9 +1,9 @@
package com.appsmith.server.layouts;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.helpers.ObservationHelperImpl;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.onload.internal.OnLoadExecutablesUtil;
import com.appsmith.server.refactors.resolver.ContextLayoutRefactorResolver;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.PagePermission;
@ -13,26 +13,25 @@ import org.springframework.stereotype.Service;
@Service
public class UpdateLayoutServiceImpl extends UpdateLayoutServiceCEImpl implements UpdateLayoutService {
public UpdateLayoutServiceImpl(
OnLoadExecutablesUtil onLoadExecutablesUtil,
SessionUserService sessionUserService,
NewPageService newPageService,
AnalyticsService analyticsService,
PagePermission pagePermission,
ApplicationService applicationService,
ObjectMapper objectMapper,
ObservationRegistry observationRegistry,
ObservationHelperImpl observationHelper) {
ObservationHelperImpl observationHelper,
ContextLayoutRefactorResolver contextLayoutRefactorResolver) {
super(
onLoadExecutablesUtil,
sessionUserService,
newPageService,
analyticsService,
pagePermission,
applicationService,
objectMapper,
observationRegistry,
observationHelper);
observationHelper,
contextLayoutRefactorResolver);
}
}

View File

@ -0,0 +1,109 @@
package com.appsmith.server.newpages.refactors;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.ce.LayoutContainer;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.dtos.RefactorEntityNameDTO;
import com.appsmith.server.dtos.RefactoringMetaDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.ContextLayoutRefactoringService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.List;
import static com.appsmith.server.constants.ce.CommonConstantsCE.EVALUATION_VERSION;
@Component
@RequiredArgsConstructor
@Slf4j
public class PageLayoutRefactoringServiceImpl implements ContextLayoutRefactoringService<NewPage, PageDTO> {
private final NewPageService newPageService;
private final ApplicationService applicationService;
@Override
public Mono<PageDTO> updateContext(String contextId, LayoutContainer dto) {
return newPageService.saveUnpublishedPage((PageDTO) dto);
}
@Override
public Mono<PageDTO> getContextDTOMono(String contextId, boolean viewMode) {
return newPageService.findPageById(contextId, null, viewMode);
}
@Override
public Mono<PageDTO> getContextDTOMono(RefactoringMetaDTO refactoringMetaDTO) {
return refactoringMetaDTO.getPageDTOMono();
}
@Override
public Mono<Integer> getEvaluationVersionMono(
String contextId, RefactorEntityNameDTO refactorEntityNameDTO, RefactoringMetaDTO refactoringMetaDTO) {
Mono<PageDTO> pageDTOMono = getContextDTOMono(contextId, false);
refactoringMetaDTO.setPageDTOMono(pageDTOMono);
return pageDTOMono.flatMap(
page -> applicationService.findById(page.getApplicationId()).map(application -> {
Integer evaluationVersion = application.getEvaluationVersion();
if (evaluationVersion == null) {
evaluationVersion = EVALUATION_VERSION;
}
return evaluationVersion;
}));
}
@Override
public Mono<Integer> getEvaluationVersionMono(String artifactId) {
return applicationService
.findById(artifactId)
.switchIfEmpty(Mono.error(new AppsmithException(
AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, artifactId)))
.map(application -> {
Integer evaluationVersion = application.getEvaluationVersion();
if (evaluationVersion == null) {
evaluationVersion = EVALUATION_VERSION;
}
return evaluationVersion;
});
}
@Override
public List<Layout> getLayouts(RefactoringMetaDTO refactoringMetaDTO) {
PageDTO updatedPage = refactoringMetaDTO.getUpdatedPage();
if (updatedPage == null) {
return null;
}
return updatedPage.getLayouts();
}
@Override
public Mono<PageDTO> updateLayoutByContextId(String contextId, Layout layout) {
// Implementation for updating page layout
return Mono.empty();
}
@Override
public String getId(RefactoringMetaDTO refactoringMetaDTO) {
return refactoringMetaDTO.getUpdatedPage() != null
? refactoringMetaDTO.getUpdatedPage().getId()
: null;
}
@Override
public String getArtifactId(RefactoringMetaDTO refactoringMetaDTO) {
return refactoringMetaDTO.getUpdatedPage() != null
? refactoringMetaDTO.getUpdatedPage().getApplicationId()
: null;
}
@Override
public void setUpdatedContext(RefactoringMetaDTO refactoringMetaDTO, LayoutContainer updatedContext) {
refactoringMetaDTO.setUpdatedPage((PageDTO) updatedContext);
}
}

View File

@ -167,7 +167,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
widgetDynamicBindingsMap,
executableNameToExecutableMapMono,
executableBindingsInDslRef,
evaluatedVersion)
evaluatedVersion,
creatorType)
.name(ADD_DIRECTLY_REFERENCED_EXECUTABLES_TO_GRAPH)
.tap(Micrometer.observation(observationRegistry));
@ -197,7 +198,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
executablesFoundDuringWalkRef,
bindingsFromExecutablesRef,
executableNameToExecutableMapMono,
evaluatedVersion))
evaluatedVersion,
creatorType))
.name(RECURSIVELY_ADD_EXECUTABLES_AND_THEIR_DEPENDENTS_TO_GRAPH_FROM_BINDINGS)
.tap(Micrometer.observation(observationRegistry))
// At last, add all the widget relationships to the graph as well.
@ -410,7 +412,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
// Finally update the actions which require an update
return Flux.fromIterable(toUpdateExecutables)
.flatMap(executable -> this.updateUnpublishedExecutable(executable.getId(), executable))
.flatMap(executable ->
this.updateUnpublishedExecutable(executable.getId(), executable, creatorType))
.then(Mono.just(TRUE));
});
}
@ -418,12 +421,13 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
@Override
public Mono<Layout> findAndUpdateLayout(
String creatorId, CreatorContextType creatorType, String layoutId, Layout layout) {
return pageExecutableOnLoadService.findAndUpdateLayout(creatorId, layoutId, layout);
return getExecutableOnLoadService(creatorType).findAndUpdateLayout(creatorId, layoutId, layout);
}
private Mono<Executable> updateUnpublishedExecutable(String id, Executable executable) {
private Mono<Executable> updateUnpublishedExecutable(
String id, Executable executable, CreatorContextType contextType) {
if (executable instanceof ActionDTO actionDTO) {
return pageExecutableOnLoadService.updateUnpublishedExecutable(id, actionDTO);
return getExecutableOnLoadService(contextType).updateUnpublishedExecutable(id, actionDTO);
} else return Mono.just(executable);
}
@ -437,7 +441,7 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
}
protected Flux<Executable> getAllExecutablesByCreatorIdFlux(String creatorId, CreatorContextType creatorType) {
return pageExecutableOnLoadService
return getExecutableOnLoadService(creatorType)
.getAllExecutablesByCreatorIdFlux(creatorId)
.name(GET_ALL_EXECUTABLES_BY_CREATOR_ID)
.tap(Micrometer.observation(observationRegistry));
@ -701,7 +705,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
Map<String, Set<String>> widgetDynamicBindingsMap,
Mono<Map<String, Executable>> executableNameToExecutableMapMono,
Set<EntityDependencyNode> executableBindingsInDslRef,
int evalVersion) {
int evalVersion,
CreatorContextType contextType) {
Map<String, Set<EntityDependencyNode>> bindingToWidgetNodesMap = new HashMap<>();
List<String> allBindings = new ArrayList<>();
@ -741,7 +746,7 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
// candidate for on page load
executablesUsedInDSLRef.add(possibleEntity.getValidEntityName());
return updateExecutableSelfReferencingPaths(possibleEntity)
return updateExecutableSelfReferencingPaths(possibleEntity, contextType)
.name(UPDATE_EXECUTABLE_SELF_REFERENCING_PATHS)
.tap(Micrometer.observation(observationRegistry))
.flatMap(executable -> extractAndSetExecutableBindingsInGraphEdges(
@ -763,16 +768,19 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
.thenReturn(edgesRef);
}
protected Mono<Executable> updateExecutableSelfReferencingPaths(EntityDependencyNode possibleEntity) {
return this.fillSelfReferencingPaths(possibleEntity.getExecutable()).map(executable -> {
possibleEntity.setExecutable(executable);
return executable;
});
protected Mono<Executable> updateExecutableSelfReferencingPaths(
EntityDependencyNode possibleEntity, CreatorContextType contextType) {
return this.fillSelfReferencingPaths(possibleEntity.getExecutable(), contextType)
.map(executable -> {
possibleEntity.setExecutable(executable);
return executable;
});
}
protected <T extends Executable> Mono<Executable> fillSelfReferencingPaths(T executable) {
protected <T extends Executable> Mono<Executable> fillSelfReferencingPaths(
T executable, CreatorContextType contextType) {
if (executable instanceof ActionDTO actionDTO) {
return pageExecutableOnLoadService.fillSelfReferencingPaths(actionDTO);
return getExecutableOnLoadService(contextType).fillSelfReferencingPaths(actionDTO);
} else return Mono.just(executable);
}
@ -1007,7 +1015,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
Map<String, EntityDependencyNode> executablesFoundDuringWalk,
Set<String> dynamicBindings,
Mono<Map<String, Executable>> executableNameToExecutableMapMono,
int evalVersion) {
int evalVersion,
CreatorContextType contextType) {
if (dynamicBindings == null || dynamicBindings.isEmpty()) {
return Mono.just(edges);
}
@ -1024,7 +1033,7 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
// Add dependencies of the executables found in the DSL in the graph.
.flatMap(possibleEntity -> {
if (getExecutableTypes().contains(possibleEntity.getEntityReferenceType())) {
return updateExecutableSelfReferencingPaths(possibleEntity)
return updateExecutableSelfReferencingPaths(possibleEntity, contextType)
.name(UPDATE_EXECUTABLE_SELF_REFERENCING_PATHS)
.tap(Micrometer.observation(observationRegistry))
.then(extractAndSetExecutableBindingsInGraphEdges(
@ -1052,7 +1061,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
executablesFoundDuringWalk,
newBindings,
executableNameToExecutableMapMono,
evalVersion)
evalVersion,
contextType)
.name(RECURSIVELY_ADD_EXECUTABLES_AND_THEIR_DEPENDENTS_TO_GRAPH_FROM_BINDINGS)
.tap(Micrometer.observation(observationRegistry));
});
@ -1091,7 +1101,7 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
return getUnpublishedOnLoadExecutablesExplicitSetByUserInCreatorContextFlux(creatorId, creatorType)
.name(GET_UNPUBLISHED_ON_LOAD_EXECUTABLES_EXPLICIT_SET_BY_USER_IN_CREATOR_CONTEXT)
.tap(Micrometer.observation(observationRegistry))
.flatMap(this::fillSelfReferencingPaths)
.flatMap(executable -> fillSelfReferencingPaths(executable, creatorType))
// Add the vertices and edges to the graph for these executables
.flatMap(executable -> {
EntityDependencyNode entityDependencyNode = new EntityDependencyNode(
@ -1119,7 +1129,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
protected Flux<Executable> getUnpublishedOnLoadExecutablesExplicitSetByUserInCreatorContextFlux(
String creatorId, CreatorContextType creatorType) {
return pageExecutableOnLoadService.getUnpublishedOnLoadExecutablesExplicitSetByUserInPageFlux(creatorId);
return getExecutableOnLoadService(creatorType)
.getUnpublishedOnLoadExecutablesExplicitSetByUserInPageFlux(creatorId);
}
/**
@ -1464,4 +1475,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
return onPageLoadCandidates;
}
protected ExecutableOnLoadService<?> getExecutableOnLoadService(CreatorContextType contextType) {
return pageExecutableOnLoadService;
}
}

View File

@ -0,0 +1,107 @@
package com.appsmith.server.refactors;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.ce.LayoutContainer;
import com.appsmith.server.dtos.RefactorEntityNameDTO;
import com.appsmith.server.dtos.RefactoringMetaDTO;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* Service interface for handling layout-related refactoring operations within a context
* such as a page or module. It provides methods to retrieve, update, and evaluate layouts
* while preserving consistency during entity renaming or structural changes.
*
* @param <U> The domain type associated with the context (e.g., Page, Module)
* @param <T> The layout container type that holds layouts (e.g., PageDTO, ModuleDTO)
*/
public interface ContextLayoutRefactoringService<U extends BaseDomain, T extends LayoutContainer> {
/**
* Updates the context identified by the given contextId with the provided layout container.
*
* @param contextId The unique identifier of the context to update
* @param dto The layout container containing updated layout information
* @return A Mono emitting the updated layout container
*/
Mono<T> updateContext(String contextId, LayoutContainer dto);
/**
* Retrieves the layout container (DTO) for the given contextId and view mode.
*
* @param contextId The unique identifier of the context
* @param viewMode true for published version, false for unpublished
* @return A Mono emitting the requested layout container
*/
Mono<T> getContextDTOMono(String contextId, boolean viewMode);
/**
* Retrieves the layout container using metadata derived from the refactoring context.
*
* @param refactoringMetaDTO Metadata describing the refactoring context
* @return A Mono emitting the corresponding layout container
*/
Mono<T> getContextDTOMono(RefactoringMetaDTO refactoringMetaDTO);
/**
* Retrieves the evaluation version associated with the context, used to guide DSL parsing or upgrades.
*
* @param contextId The unique identifier of the context
* @param refactorEntityNameDTO DTO containing the old and new entity names
* @param refactoringMetaDTO Metadata describing the refactoring context
* @return A Mono emitting the evaluation version for the context
*/
Mono<Integer> getEvaluationVersionMono(
String contextId, RefactorEntityNameDTO refactorEntityNameDTO, RefactoringMetaDTO refactoringMetaDTO);
/**
* Retrieves the evaluation version for the context without refactor-specific inputs.
*
* @param artifactId The unique identifier of the artifact
* @return A Mono emitting the evaluation version
*/
Mono<Integer> getEvaluationVersionMono(String artifactId);
/**
* Extracts layouts from the provided refactoring metadata.
*
* @param refactoringMetaDTO Metadata describing the refactoring context
* @return A list of layouts associated with the context
*/
List<Layout> getLayouts(RefactoringMetaDTO refactoringMetaDTO);
/**
* Updates a specific layout for the context identified by contextId.
*
* @param contextId The unique identifier of the context
* @param layout The layout to update
* @return A Mono emitting the updated layout container
*/
Mono<T> updateLayoutByContextId(String contextId, Layout layout);
/**
* Retrieves the context ID from the provided refactoring metadata.
*
* @param refactoringMetaDTO Metadata describing the refactoring context
* @return The unique context identifier
*/
String getId(RefactoringMetaDTO refactoringMetaDTO);
/**
* Retrieves the artifact ID from the provided refactoring metadata.
*
* @param refactoringMetaDTO Metadata describing the refactoring context
* @return The artifact identifier associated with the context
*/
String getArtifactId(RefactoringMetaDTO refactoringMetaDTO);
/**
* Updates the in-memory copy of the refactored context in the given metadata object.
*
* @param refactoringMetaDTO Metadata describing the refactoring context
* @param updatedContext The updated layout container to persist in metadata
*/
void setUpdatedContext(RefactoringMetaDTO refactoringMetaDTO, LayoutContainer updatedContext);
}

View File

@ -2,24 +2,23 @@ package com.appsmith.server.refactors.applications;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.models.CreatorContextType;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ActionCollection;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.dtos.EntityType;
import com.appsmith.server.dtos.LayoutDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.dtos.RefactorEntityNameDTO;
import com.appsmith.server.dtos.RefactoringMetaDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.layouts.UpdateLayoutService;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.ContextLayoutRefactoringService;
import com.appsmith.server.refactors.entities.EntityRefactoringService;
import com.appsmith.server.refactors.resolver.ContextLayoutRefactorResolver;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.PagePermission;
import com.appsmith.server.validations.EntityValidationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -36,7 +35,6 @@ import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.appsmith.server.constants.CommonConstants.EVALUATION_VERSION;
import static com.appsmith.server.helpers.ContextTypeUtils.getDefaultContextIfNull;
@Slf4j
@ -44,8 +42,6 @@ import static com.appsmith.server.helpers.ContextTypeUtils.getDefaultContextIfNu
public class RefactoringServiceCEImpl implements RefactoringServiceCE {
private final NewPageService newPageService;
private final UpdateLayoutService updateLayoutService;
private final ApplicationService applicationService;
private final PagePermission pagePermission;
private final AnalyticsService analyticsService;
private final SessionUserService sessionUserService;
private final EntityValidationService entityValidationService;
@ -54,6 +50,7 @@ public class RefactoringServiceCEImpl implements RefactoringServiceCE {
protected final EntityRefactoringService<NewAction> newActionEntityRefactoringService;
protected final EntityRefactoringService<ActionCollection> actionCollectionEntityRefactoringService;
protected final EntityRefactoringService<Layout> widgetEntityRefactoringService;
protected final ContextLayoutRefactorResolver contextLayoutRefactorResolver;
/*
* To replace fetchUsers in `{{JSON.stringify(fetchUsers)}}` with getUsers, the following regex is required :
@ -73,7 +70,7 @@ public class RefactoringServiceCEImpl implements RefactoringServiceCE {
* @return : The DSL after refactor updates
*/
Mono<Tuple2<LayoutDTO, Set<String>>> refactorName(RefactorEntityNameDTO refactorEntityNameDTO) {
String pageId = refactorEntityNameDTO.getPageId();
String contextId = getBranchedContextId(refactorEntityNameDTO);
String layoutId = refactorEntityNameDTO.getLayoutId();
String oldName = refactorEntityNameDTO.getOldFullyQualifiedName();
@ -83,21 +80,30 @@ public class RefactoringServiceCEImpl implements RefactoringServiceCE {
refactoringMetaDTO.setOldNamePattern(oldNamePattern);
refactoringMetaDTO.setEvalVersionMono(
getContextBasedEvalVersionMono(pageId, refactorEntityNameDTO, refactoringMetaDTO));
refactoringMetaDTO.setEvalVersionMono(contextLayoutRefactorResolver
.getContextLayoutRefactorHelper(refactorEntityNameDTO.getContextType())
.getEvaluationVersionMono(contextId, refactorEntityNameDTO, refactoringMetaDTO));
Mono<Void> refactoredReferencesMono = refactorAllReferences(refactorEntityNameDTO, refactoringMetaDTO);
return refactoredReferencesMono.then(Mono.defer(() -> {
PageDTO page = refactoringMetaDTO.getUpdatedPage();
Set<String> updatedBindingPaths = refactoringMetaDTO.getUpdatedBindingPaths();
if (page != null) {
List<Layout> layouts = page.getLayouts();
ContextLayoutRefactoringService<?, ?> contextLayoutRefactorHelper =
contextLayoutRefactorResolver.getContextLayoutRefactorHelper(
refactorEntityNameDTO.getContextType());
List<Layout> layouts = contextLayoutRefactorHelper.getLayouts(refactoringMetaDTO);
if (layouts != null) {
for (Layout layout : layouts) {
if (layoutId.equals(layout.getId())) {
layout.setDsl(updateLayoutService.unescapeMongoSpecialCharacters(layout));
String artifactId = contextLayoutRefactorHelper.getArtifactId(refactoringMetaDTO);
return updateLayoutService
.updateLayout(page.getId(), page.getApplicationId(), layout.getId(), layout)
.updateLayout(
contextId,
artifactId,
layout.getId(),
layout,
refactorEntityNameDTO.getContextType())
.zipWith(Mono.just(updatedBindingPaths));
}
}
@ -107,27 +113,6 @@ public class RefactoringServiceCEImpl implements RefactoringServiceCE {
}));
}
protected Mono<Integer> getContextBasedEvalVersionMono(
String contextId, RefactorEntityNameDTO refactorEntityNameDTO, RefactoringMetaDTO refactoringMetaDTO) {
Mono<PageDTO> pageMono = newPageService
// fetch the unpublished page
.findPageById(contextId, pagePermission.getEditPermission(), false)
.cache();
refactoringMetaDTO.setPageDTOMono(pageMono);
Mono<Integer> evalVersionMono = pageMono.flatMap(page -> {
return applicationService.findById(page.getApplicationId()).map(application -> {
Integer evaluationVersion = application.getEvaluationVersion();
if (evaluationVersion == null) {
evaluationVersion = EVALUATION_VERSION;
}
return evaluationVersion;
});
})
.cache();
return evalVersionMono;
}
protected static Pattern getReplacementPattern(String oldName) {
String regexPattern = preWord + oldName + postWord;
return Pattern.compile(regexPattern);

View File

@ -1,15 +1,14 @@
package com.appsmith.server.refactors.applications;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.domains.ActionCollection;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.layouts.UpdateLayoutService;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.entities.EntityRefactoringService;
import com.appsmith.server.refactors.resolver.ContextLayoutRefactorResolver;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.PagePermission;
import com.appsmith.server.validations.EntityValidationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -17,30 +16,27 @@ import org.springframework.stereotype.Service;
@Service
@Slf4j
public class RefactoringServiceImpl extends RefactoringServiceCEImpl implements RefactoringService {
public RefactoringServiceImpl(
NewPageService newPageService,
UpdateLayoutService updateLayoutService,
ApplicationService applicationService,
PagePermission pagePermission,
AnalyticsService analyticsService,
SessionUserService sessionUserService,
EntityValidationService entityValidationService,
EntityRefactoringService<Void> jsActionEntityRefactoringService,
EntityRefactoringService<NewAction> newActionEntityRefactoringService,
EntityRefactoringService<ActionCollection> actionCollectionEntityRefactoringService,
EntityRefactoringService<Layout> widgetEntityRefactoringService) {
EntityRefactoringService<Layout> widgetEntityRefactoringService,
ContextLayoutRefactorResolver contextLayoutRefactorResolver) {
super(
newPageService,
updateLayoutService,
applicationService,
pagePermission,
analyticsService,
sessionUserService,
entityValidationService,
jsActionEntityRefactoringService,
newActionEntityRefactoringService,
actionCollectionEntityRefactoringService,
widgetEntityRefactoringService);
widgetEntityRefactoringService,
contextLayoutRefactorResolver);
}
}

View File

@ -0,0 +1,13 @@
package com.appsmith.server.refactors.resolver;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.refactors.ContextLayoutRefactoringService;
import org.springframework.stereotype.Service;
@Service
public class ContextLayoutRefactorResolver extends ContextLayoutRefactorResolverCE {
public ContextLayoutRefactorResolver(ContextLayoutRefactoringService<NewPage, PageDTO> pageLayoutRefactorService) {
super(pageLayoutRefactorService);
}
}

View File

@ -0,0 +1,21 @@
package com.appsmith.server.refactors.resolver;
import com.appsmith.external.models.CreatorContextType;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.refactors.ContextLayoutRefactoringService;
import org.springframework.stereotype.Service;
@Service
public class ContextLayoutRefactorResolverCE {
private final ContextLayoutRefactoringService<NewPage, PageDTO> pageLayoutRefactorService;
public ContextLayoutRefactorResolverCE(
ContextLayoutRefactoringService<NewPage, PageDTO> pageLayoutRefactorService) {
this.pageLayoutRefactorService = pageLayoutRefactorService;
}
public ContextLayoutRefactoringService<?, ?> getContextLayoutRefactorHelper(CreatorContextType contextType) {
return pageLayoutRefactorService;
}
}

View File

@ -1,18 +1,19 @@
package com.appsmith.server.widgets.refactors;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.models.CreatorContextType;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.ce.LayoutContainer;
import com.appsmith.server.dtos.EntityType;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.dtos.RefactorEntityNameDTO;
import com.appsmith.server.dtos.RefactoringMetaDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.ContextLayoutRefactoringService;
import com.appsmith.server.refactors.entities.EntityRefactoringServiceCE;
import com.appsmith.server.services.AstService;
import com.appsmith.server.refactors.resolver.ContextLayoutRefactorResolver;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
@ -26,16 +27,15 @@ import java.util.Set;
import java.util.regex.Pattern;
import static com.appsmith.external.constants.AnalyticsEvents.REFACTOR_WIDGET;
import static com.appsmith.server.helpers.ContextTypeUtils.isModuleContext;
import static com.appsmith.server.helpers.ContextTypeUtils.isPageContext;
@Slf4j
@RequiredArgsConstructor
public class WidgetRefactoringServiceCEImpl implements EntityRefactoringServiceCE<Layout> {
private final NewPageService newPageService;
private final AstService astService;
private final ObjectMapper objectMapper;
private final WidgetRefactorUtil widgetRefactorUtil;
protected final ObjectMapper objectMapper;
protected final WidgetRefactorUtil widgetRefactorUtil;
private final ContextLayoutRefactorResolver contextLayoutRefactorResolver;
@Override
public AnalyticsEvents getRefactorAnalyticsEvent(EntityType entityType) {
@ -45,53 +45,67 @@ public class WidgetRefactoringServiceCEImpl implements EntityRefactoringServiceC
@Override
public Mono<Void> refactorReferencesInExistingEntities(
RefactorEntityNameDTO refactorEntityNameDTO, RefactoringMetaDTO refactoringMetaDTO) {
if (!isPageContext(refactorEntityNameDTO.getContextType())) {
if (!isPageContext(refactorEntityNameDTO.getContextType())
&& !isModuleContext(refactorEntityNameDTO.getContextType())) {
return Mono.empty().then();
}
Mono<PageDTO> pageMono = refactoringMetaDTO.getPageDTOMono();
Mono<Integer> evalVersionMono = refactoringMetaDTO.getEvalVersionMono();
Set<String> updatedBindingPaths = refactoringMetaDTO.getUpdatedBindingPaths();
Pattern oldNamePattern = refactoringMetaDTO.getOldNamePattern();
CreatorContextType contextType = refactorEntityNameDTO.getContextType();
String layoutId = refactorEntityNameDTO.getLayoutId();
String oldName = refactorEntityNameDTO.getOldFullyQualifiedName();
String newName = refactorEntityNameDTO.getNewFullyQualifiedName();
Mono<PageDTO> pageDTOMono = Mono.zip(pageMono, evalVersionMono).flatMap(tuple -> {
PageDTO page = tuple.getT1();
int evalVersion = tuple.getT2();
Set<String> updatedBindingPaths = refactoringMetaDTO.getUpdatedBindingPaths();
Pattern oldNamePattern = refactoringMetaDTO.getOldNamePattern();
List<Layout> layouts = page.getLayouts();
for (Layout layout : layouts) {
if (layoutId.equals(layout.getId()) && layout.getDsl() != null) {
// DSL has removed all the old names and replaced it with new name. If the change of name
// was one of the mongoEscaped widgets, then update the names in the set as well
Set<String> mongoEscapedWidgetNames = layout.getMongoEscapedWidgetNames();
if (mongoEscapedWidgetNames != null && mongoEscapedWidgetNames.contains(oldName)) {
mongoEscapedWidgetNames.remove(oldName);
mongoEscapedWidgetNames.add(newName);
}
// Get the appropriate context refactor service based on context type
ContextLayoutRefactoringService<? extends BaseDomain, ? extends LayoutContainer> contextLayoutRefactorHelper =
contextLayoutRefactorResolver.getContextLayoutRefactorHelper(contextType);
final JsonNode dslNode = objectMapper.convertValue(layout.getDsl(), JsonNode.class);
Mono<PageDTO> refactorNameInDslMono = widgetRefactorUtil
.refactorNameInDsl(dslNode, oldName, newName, evalVersion, oldNamePattern)
.flatMap(dslBindingPaths -> {
updatedBindingPaths.addAll(dslBindingPaths);
layout.setDsl(objectMapper.convertValue(dslNode, JSONObject.class));
page.setLayouts(layouts);
refactoringMetaDTO.setUpdatedPage(page);
return Mono.just(page);
});
// Get context DTO mono (either PageDTO or ModuleDTO)
Mono<? extends LayoutContainer> contextDTOMono =
contextLayoutRefactorHelper.getContextDTOMono(refactoringMetaDTO);
// Since the page has most probably changed, save the page and return.
return refactorNameInDslMono.flatMap(newPageService::saveUnpublishedPage);
}
}
// If we have reached here, the layout was not found and the page should be returned as is.
return Mono.just(page);
});
return contextDTOMono
.flatMap(contextDTO -> {
String contextId = contextDTO.getId();
Mono<Integer> evalVersionMono = contextLayoutRefactorHelper.getEvaluationVersionMono(
contextId, refactorEntityNameDTO, refactoringMetaDTO);
return pageDTOMono.then();
return evalVersionMono.flatMap(evalVersion -> {
List<Layout> layouts = contextDTO.getLayouts();
if (layouts == null) {
return Mono.just(contextDTO);
}
for (Layout layout : layouts) {
if (layoutId.equals(layout.getId()) && layout.getDsl() != null) {
// DSL has removed all the old names and replaced it with new name. If the change of
// name
// was one of the mongoEscaped widgets, then update the names in the set as well
Set<String> mongoEscapedWidgetNames = layout.getMongoEscapedWidgetNames();
if (mongoEscapedWidgetNames != null && mongoEscapedWidgetNames.contains(oldName)) {
mongoEscapedWidgetNames.remove(oldName);
mongoEscapedWidgetNames.add(newName);
}
final JsonNode dslNode = objectMapper.convertValue(layout.getDsl(), JsonNode.class);
return widgetRefactorUtil
.refactorNameInDsl(dslNode, oldName, newName, evalVersion, oldNamePattern)
.flatMap(dslBindingPaths -> {
updatedBindingPaths.addAll(dslBindingPaths);
layout.setDsl(objectMapper.convertValue(dslNode, JSONObject.class));
contextDTO.setLayouts(layouts);
contextLayoutRefactorHelper.setUpdatedContext(
refactoringMetaDTO, contextDTO);
return contextLayoutRefactorHelper.updateContext(contextId, contextDTO);
});
}
}
return Mono.just(contextDTO);
});
})
.then();
}
@Override
@ -103,23 +117,26 @@ public class WidgetRefactoringServiceCEImpl implements EntityRefactoringServiceC
@Override
public Flux<String> getExistingEntityNames(
String contextId, CreatorContextType contextType, String layoutId, boolean viewMode) {
return newPageService
// fetch the unpublished page
.findPageById(contextId, null, viewMode)
.flatMapMany(page -> {
List<Layout> layouts = page.getLayouts();
for (Layout layout : layouts) {
if (layoutId.equals(layout.getId())) {
if (layout.getWidgetNames() != null
&& layout.getWidgetNames().size() > 0) {
return Flux.fromIterable(layout.getWidgetNames());
}
// In case of no widget names (which implies that there is no DSL), return an empty set.
return Flux.empty();
}
Mono<?> contextDTOMono = contextLayoutRefactorResolver
.getContextLayoutRefactorHelper(contextType)
.getContextDTOMono(contextId, viewMode);
return contextDTOMono.flatMapMany(contextDTO -> {
LayoutContainer layoutContainer = (LayoutContainer) contextDTO;
List<Layout> layouts = layoutContainer.getLayouts();
if (layouts == null) {
return Flux.empty();
}
for (Layout layout : layouts) {
if (layoutId.equals(layout.getId())) {
if (layout.getWidgetNames() != null
&& !layout.getWidgetNames().isEmpty()) {
return Flux.fromIterable(layout.getWidgetNames());
}
return Flux.error(
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.LAYOUT_ID, layoutId));
});
// In case of no widget names (which implies that there is no DSL), return an empty set.
return Flux.empty();
}
}
return Flux.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.LAYOUT_ID, layoutId));
});
}
}

View File

@ -1,9 +1,8 @@
package com.appsmith.server.widgets.refactors;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.entities.EntityRefactoringService;
import com.appsmith.server.services.AstService;
import com.appsmith.server.refactors.resolver.ContextLayoutRefactorResolver;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
@ -11,10 +10,9 @@ import org.springframework.stereotype.Service;
public class WidgetRefactoringServiceImpl extends WidgetRefactoringServiceCEImpl
implements EntityRefactoringService<Layout> {
public WidgetRefactoringServiceImpl(
NewPageService newPageService,
AstService astService,
ObjectMapper objectMapper,
WidgetRefactorUtil widgetRefactorUtil) {
super(newPageService, astService, objectMapper, widgetRefactorUtil);
WidgetRefactorUtil widgetRefactorUtil,
ContextLayoutRefactorResolver contextLayoutRefactorResolver) {
super(objectMapper, widgetRefactorUtil, contextLayoutRefactorResolver);
}
}

View File

@ -20,6 +20,7 @@ import com.appsmith.server.newactions.base.NewActionService;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.applications.RefactoringServiceCEImpl;
import com.appsmith.server.refactors.entities.EntityRefactoringService;
import com.appsmith.server.refactors.resolver.ContextLayoutRefactorResolver;
import com.appsmith.server.repositories.ActionCollectionRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
@ -90,6 +91,9 @@ class RefactoringServiceCEImplTest {
@SpyBean
private EntityRefactoringService<Layout> widgetEntityRefactoringService;
@SpyBean
private ContextLayoutRefactorResolver contextLayoutRefactorResolver;
@Autowired
private EntityValidationService entityValidationService;
@ -101,15 +105,14 @@ class RefactoringServiceCEImplTest {
refactoringServiceCE = new RefactoringServiceCEImpl(
newPageService,
updateLayoutService,
applicationService,
pagePermission,
analyticsService,
sessionUserService,
entityValidationService,
jsActionEntityRefactoringService,
newActionEntityRefactoringService,
actionCollectionEntityRefactoringService,
widgetEntityRefactoringService);
widgetEntityRefactoringService,
contextLayoutRefactorResolver);
}
@Test
@ -167,7 +170,7 @@ class RefactoringServiceCEImplTest {
.thenReturn(Flux.empty());
Mockito.when(updateLayoutService.updateLayout(
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any()))
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any()))
.thenReturn(Mono.just(layout));
Mockito.doReturn(Flux.just(oldUnpublishedCollection.getName()))
@ -305,7 +308,7 @@ class RefactoringServiceCEImplTest {
layout.setLayoutOnLoadActions(new ArrayList<>());
Mockito.when(updateLayoutService.updateLayout(
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any()))
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any()))
.thenReturn(Mono.just(layout));
Mockito.doReturn(Flux.just(oldUnpublishedCollection.getName()))