diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ActionCollectionSpanCE.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ActionCollectionSpanCE.java index c95f431fac0..7dca733a6b9 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ActionCollectionSpanCE.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ActionCollectionSpanCE.java @@ -18,4 +18,5 @@ public class ActionCollectionSpanCE { // Getter spans public static final String GET_ACTION_COLLECTION_BY_ID = APPSMITH_SPAN_PREFIX + "get.actionCollectionById"; + public static final String GET_ACTION_COLLECTIONS_BY_IDS = APPSMITH_SPAN_PREFIX + "get.actionCollectionsByIds"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCE.java index f7b17293aa5..41d534dbc67 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCE.java @@ -52,6 +52,8 @@ Mono deleteUnpublishedActionCollection( Mono findById(String id, AclPermission aclPermission); + Flux findAllByIds(List ids, AclPermission aclPermission); + Mono findActionCollectionDTObyIdAndViewMode( String id, Boolean viewMode, AclPermission permission); @@ -81,6 +83,16 @@ Flux findAllActionCollectionsByContextIdAndContextTypeAndViewM Mono bulkValidateAndUpdateActionCollectionInRepository(List actionCollectionList); + /** + * General purpose bulk update method that directly saves action collections to the database without validation. + * This method is optimized for scenarios where the action collections are already validated or when + * validation is not required (e.g., refactoring operations, data migrations). + * + * @param actionCollectionList List of ActionCollection objects to update + * @return Mono indicating completion of the bulk update operation + */ + Mono bulkUpdateActionCollections(List actionCollectionList); + Mono saveLastEditInformationInParent(ActionCollectionDTO actionCollectionDTO); Flux findByArtifactIdAndArtifactType(String artifactId, ArtifactType artifactType); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCEImpl.java index 197a7b12f77..1f295e87cde 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/base/ActionCollectionServiceCEImpl.java @@ -44,6 +44,7 @@ import java.util.UUID; import static com.appsmith.external.constants.spans.ActionCollectionSpan.GET_ACTION_COLLECTION_BY_ID; +import static com.appsmith.external.constants.spans.ce.ActionCollectionSpanCE.GET_ACTION_COLLECTIONS_BY_IDS; import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNewFieldValuesIntoOldObject; import static java.lang.Boolean.TRUE; @@ -369,6 +370,14 @@ public Mono findById(String id, AclPermission aclPermission) { .tap(Micrometer.observation(observationRegistry)); } + @Override + public Flux findAllByIds(List ids, AclPermission aclPermission) { + return repository + .findAllByIds(ids, aclPermission) + .name(GET_ACTION_COLLECTIONS_BY_IDS) + .tap(Micrometer.observation(observationRegistry)); + } + @Override public Mono findActionCollectionDTObyIdAndViewMode( String id, Boolean viewMode, AclPermission permission) { @@ -622,6 +631,28 @@ public Mono bulkValidateAndUpdateActionCollectionInRepository(List indicating completion of the bulk update operation + */ + @Override + public Mono bulkUpdateActionCollections(List actionCollectionList) { + if (actionCollectionList == null || actionCollectionList.isEmpty()) { + return Mono.empty(); + } + + // Set git sync IDs for action collections that don't have them + actionCollectionList.stream() + .filter(actionCollection -> actionCollection.getGitSyncId() == null) + .forEach(this::setGitSyncIdInActionCollection); + + return repository.bulkUpdate(actionCollectionList); + } + @Override public Mono saveLastEditInformationInParent(ActionCollectionDTO actionCollectionDTO) { // Do nothing as this is already taken care for JS objects in the context of page diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/refactors/ActionCollectionRefactoringServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/refactors/ActionCollectionRefactoringServiceCEImpl.java index b6416cb1ac0..4ef4deeec41 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/refactors/ActionCollectionRefactoringServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/refactors/ActionCollectionRefactoringServiceCEImpl.java @@ -19,9 +19,11 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; @@ -51,25 +53,63 @@ public Mono refactorReferencesInExistingEntities( String oldName = refactorEntityNameDTO.getOldFullyQualifiedName(); String newName = refactorEntityNameDTO.getNewFullyQualifiedName(); - Mono> actionCollectionsMono = evalVersionMono.flatMap(evalVersion -> Flux.fromIterable( - updatableCollectionIds) - .flatMap(collectionId -> - actionCollectionService.findById(collectionId, actionPermission.getEditPermission())) + + return evalVersionMono.flatMap(evalVersion -> actionCollectionService + .findAllByIds(new ArrayList<>(updatableCollectionIds), actionPermission.getEditPermission()) + .collectList() + .flatMap(actionCollections -> { + if (actionCollections.isEmpty()) { + return Mono.empty(); + } + + log.debug( + "Processing {} action collections for refactoring with bulk processing", + actionCollections.size()); + + // Process all action collections in bulk + return processBulkActionCollections( + actionCollections, oldName, newName, evalVersion, oldNamePattern); + })); + } + + /** + * Process all action collections in a single bulk operation for refactoring + */ + private Mono processBulkActionCollections( + List actionCollections, + String oldName, + String newName, + int evalVersion, + Pattern oldNamePattern) { + + return Flux.fromIterable(actionCollections) .flatMap(actionCollection -> { final ActionCollectionDTO unpublishedCollection = actionCollection.getUnpublishedCollection(); return this.refactorNameInActionCollection( unpublishedCollection, oldName, newName, evalVersion, oldNamePattern) - .flatMap(isPresent -> { - if (Boolean.TRUE.equals(isPresent)) { - return actionCollectionService.save(actionCollection); - } - return Mono.just(actionCollection); - }); + .mapNotNull(isPresent -> Boolean.TRUE.equals(isPresent) ? actionCollection : null); }) - .collectList()); + // Keep only the ones that actually need update + .filter(Objects::nonNull) + .collectList() + .flatMap(collectionsToUpdate -> { + if (collectionsToUpdate.isEmpty()) { + log.debug("No action collections require updates for refactoring"); + return Mono.empty(); + } + + log.debug( + "Processing bulk operation for {} action collections that require refactoring", + collectionsToUpdate.size()); - return actionCollectionsMono.then(); + // Use bulk update for all action collections at once + return actionCollectionService + .bulkUpdateActionCollections(collectionsToUpdate) + .doOnSuccess(unused -> log.debug( + "Bulk refactoring completed for {} action collections", + collectionsToUpdate.size())); + }); } protected Mono refactorNameInActionCollection( diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactoringMetaDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactoringMetaDTO.java index b7fbba08f8c..953fe2e1fb1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactoringMetaDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactoringMetaDTO.java @@ -3,8 +3,8 @@ import lombok.Data; import reactor.core.publisher.Mono; -import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; @Data @@ -14,8 +14,19 @@ public class RefactoringMetaDTO { Mono pageDTOMono; Pattern oldNamePattern; - Set updatedBindingPaths = new HashSet<>(); + /** + * Thread-safe set for tracking binding paths that have been updated during refactoring. + * Uses ConcurrentHashMap.newKeySet() to ensure thread-safety as this collection can be + * modified concurrently by multiple refactoring services during reactive operations. + */ + Set updatedBindingPaths = ConcurrentHashMap.newKeySet(); + PageDTO updatedPage; - Set updatableCollectionIds = new HashSet<>(); + /** + * Thread-safe set for tracking action collection IDs that need to be updated during refactoring. + * Uses ConcurrentHashMap.newKeySet() to ensure thread-safety as this collection can be + * modified concurrently by multiple refactoring services during reactive operations. + */ + Set updatableCollectionIds = ConcurrentHashMap.newKeySet(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/RefactorEntityNameCE_DTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/RefactorEntityNameCE_DTO.java index 4fada3adb01..f9726b57401 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/RefactorEntityNameCE_DTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/RefactorEntityNameCE_DTO.java @@ -4,6 +4,7 @@ import com.appsmith.external.views.Views; import com.appsmith.server.dtos.ActionCollectionDTO; import com.appsmith.server.dtos.EntityType; +import com.appsmith.server.dtos.PageDTO; import com.fasterxml.jackson.annotation.JsonView; import lombok.AllArgsConstructor; import lombok.Getter; @@ -25,6 +26,36 @@ public class RefactorEntityNameCE_DTO { String actionId; String actionCollectionId; + + public RefactorEntityNameCE_DTO( + String pageId, + String layoutId, + String oldName, + String newName, + EntityType entityType, + String actionId, + String actionCollectionId, + String collectionName, + ActionCollectionDTO actionCollection, + String oldFullyQualifiedName, + String newFullyQualifiedName, + Boolean isInternal, + CreatorContextType contextType) { + this.pageId = pageId; + this.layoutId = layoutId; + this.oldName = oldName; + this.newName = newName; + this.entityType = entityType; + this.actionId = actionId; + this.actionCollectionId = actionCollectionId; + this.collectionName = collectionName; + this.actionCollection = actionCollection; + this.oldFullyQualifiedName = oldFullyQualifiedName; + this.newFullyQualifiedName = newFullyQualifiedName; + this.isInternal = isInternal; + this.contextType = contextType; + } + String collectionName; ActionCollectionDTO actionCollection; @@ -38,4 +69,38 @@ public class RefactorEntityNameCE_DTO { Boolean isInternal; CreatorContextType contextType; + + // Cache fields for optimization - storing objects directly + @JsonView(Views.Internal.class) + private PageDTO cachedPageDTO; + + @JsonView(Views.Internal.class) + private Integer cachedEvaluationVersion; + + @JsonView(Views.Internal.class) + private String cachedApplicationId; + + // Helper methods for cache management + public boolean hasCachedPageDTO() { + return cachedPageDTO != null; + } + + public boolean hasCachedEvaluationVersion() { + return cachedEvaluationVersion != null; + } + + public boolean hasCachedApplicationId() { + return cachedApplicationId != null; + } + + public RefactorEntityNameCE_DTO withCachedPageDTO(PageDTO pageDTO) { + this.cachedPageDTO = pageDTO; + // Don't auto-populate applicationId here, let it be set separately if needed + return this; + } + + public RefactorEntityNameCE_DTO withCachedEvaluationVersion(Integer evaluationVersion) { + this.cachedEvaluationVersion = evaluationVersion; + return this; + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java index f15f5a64eeb..52f76fe5c34 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java @@ -40,6 +40,16 @@ public interface NewActionServiceCE extends CrudService { Mono bulkValidateAndUpdateActionInRepository(List newActionList); + /** + * General purpose bulk update method that directly saves actions to the database without validation. + * This method is optimized for scenarios where the actions are already validated or when + * validation is not required (e.g., refactoring operations, data migrations). + * + * @param newActionList List of NewAction objects to update + * @return Mono indicating completion of the bulk update operation + */ + Mono bulkUpdateActions(List newActionList); + Mono extractAndSetJsonPathKeys(NewAction newAction); Mono updateUnpublishedAction(String id, ActionDTO action); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java index 644e8d4b17b..277fa54a48b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java @@ -430,6 +430,26 @@ public Mono bulkValidateAndUpdateActionInRepository(List newAct .flatMap(repository::bulkUpdate); } + /** + * General purpose bulk update method that directly saves actions to the database without validation. + * This method is optimized for scenarios where the actions are already validated or when + * validation is not required (e.g., refactoring operations, data migrations). + * + * @param newActionList List of NewAction objects to update + * @return Mono indicating completion of the bulk update operation + */ + @Override + public Mono bulkUpdateActions(List newActionList) { + if (newActionList == null || newActionList.isEmpty()) { + return Mono.empty(); + } + + // Set git sync IDs for actions that don't have them + newActionList.stream().filter(action -> action.getGitSyncId() == null).forEach(this::setGitSyncIdInNewAction); + + return repository.bulkUpdate(newActionList); + } + protected boolean isValidActionName(ActionDTO action) { return entityValidationService.validateName(action.getName()); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/refactors/NewActionRefactoringServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/refactors/NewActionRefactoringServiceCEImpl.java index eed4aa4b86d..d7ad41fea9e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/refactors/NewActionRefactoringServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/refactors/NewActionRefactoringServiceCEImpl.java @@ -27,7 +27,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -67,51 +70,102 @@ public Mono refactorReferencesInExistingEntities( CreatorContextType contextType = getDefaultContextIfNull(refactorEntityNameDTO.getContextType()); String oldName = refactorEntityNameDTO.getOldFullyQualifiedName(); String newName = refactorEntityNameDTO.getNewFullyQualifiedName(); - return getActionsByContextId(contextId, contextType) - .flatMap(newAction -> Mono.just(newAction).zipWith(evalVersionMono)) - /* - * Assuming that the datasource should not be dependent on the widget and hence not going through the same - * to look for replacement pattern. - */ - .flatMap(tuple -> { - final NewAction newAction = tuple.getT1(); - final Integer evalVersion = tuple.getT2(); - // We need actionDTO to be populated with pluginType from NewAction - // so that we can check for the JS path - ActionDTO action = newActionService.generateActionByViewMode(newAction, false); - - if (action.getActionConfiguration() == null) { - return Mono.just(newAction); - } - // If this is a JS function rename, add this collection for rename - // because the action configuration won't tell us this - if (StringUtils.hasLength(action.getCollectionId()) && newName.equals(action.getValidName())) { - updatableCollectionIds.add(action.getCollectionId()); - } - newAction.setUnpublishedAction(action); - return this.refactorNameInAction(action, oldName, newName, evalVersion, oldNamePattern) - .flatMap(updates -> { - if (updates.isEmpty()) { - return Mono.just(newAction); - } - updatedBindingPaths.addAll(updates); - if (StringUtils.hasLength(action.getCollectionId())) { - updatableCollectionIds.add(action.getCollectionId()); - } - - return newActionService - .extractAndSetJsonPathKeys(newAction) - .then(newActionService.save(newAction)); - }); - }) - .map(savedAction -> savedAction.getUnpublishedAction().getName()) - .collectList() - .doOnNext(updatedActionNames -> log.debug( - "Actions updated due to refactor name in {} {} are : {}", - contextType.toString().toLowerCase(), - contextId, - updatedActionNames)) - .then(); + + return getActionsByContextId(contextId, contextType).collectList().flatMap(actions -> { + if (actions.isEmpty()) { + return Mono.empty(); + } + + log.debug("Processing {} actions for refactoring with bulk processing", actions.size()); + + // Process all actions in a single bulk operation + return processBulk( + actions, + evalVersionMono, + oldName, + newName, + oldNamePattern, + updatableCollectionIds, + updatedBindingPaths, + contextType, + contextId); + }); + } + + /** + * Process all actions in a single bulk operation for refactoring + */ + private Mono processBulk( + List actions, + Mono evalVersionMono, + String oldName, + String newName, + Pattern oldNamePattern, + Set updatableCollectionIds, + Set updatedBindingPaths, + CreatorContextType contextType, + String contextId) { + + return evalVersionMono.flatMap(evalVersion -> { + // Collections for tracking actions that need updates + List actionsToUpdate = new ArrayList<>(); + List updatedActionNames = new ArrayList<>(); + + return Flux.fromIterable(actions) + .flatMap(newAction -> { + ActionDTO action = newActionService.generateActionByViewMode(newAction, false); + + if (action.getActionConfiguration() == null) { + return Mono.just(newAction); + } + + // If this is a JS function rename, add this collection for rename + if (StringUtils.hasLength(action.getCollectionId()) && newName.equals(action.getValidName())) { + updatableCollectionIds.add(action.getCollectionId()); + } + + newAction.setUnpublishedAction(action); + + return this.refactorNameInAction(action, oldName, newName, evalVersion, oldNamePattern) + .map(updates -> { + if (!updates.isEmpty()) { + updatedBindingPaths.addAll(updates); + // Collection will be updated separately if needed + actionsToUpdate.add(newAction); + updatedActionNames.add(action.getName()); + } + return newAction; + }); + }) + .collectList() + .flatMap(processedActions -> { + if (actionsToUpdate.isEmpty()) { + log.debug( + "No actions require updates for refactoring in {} {}", + contextType.toString().toLowerCase(), + contextId); + return Mono.empty(); + } + + log.debug( + "Processing bulk operation for {} actions that require refactoring", + actionsToUpdate.size()); + + // Extract JSON path keys for all actions that need updating + return Flux.fromIterable(actionsToUpdate) + .flatMap(newActionService::extractAndSetJsonPathKeys) + .collectList() + .flatMap(updatedActions -> { + // Use general purpose bulk update for all actions at once + return newActionService.bulkUpdateActions(updatedActions); + }) + .doOnSuccess(unused -> log.debug( + "Bulk refactoring completed for {} {} - updated actions: {}", + contextType.toString().toLowerCase(), + contextId, + updatedActionNames)); + }); + }); } protected String extractContextId(RefactorEntityNameDTO refactorEntityNameDTO) { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/refactors/PageLayoutRefactoringServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/refactors/PageLayoutRefactoringServiceImpl.java index 47e33c43ea9..23ad219324a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/refactors/PageLayoutRefactoringServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/refactors/PageLayoutRefactoringServiceImpl.java @@ -50,6 +50,28 @@ public Mono getContextDTOMono(RefactoringMetaDTO refactoringMetaDTO) { @Override public Mono getEvaluationVersionMono( String contextId, RefactorEntityNameDTO refactorEntityNameDTO, RefactoringMetaDTO refactoringMetaDTO) { + + // Check if we already have cached evaluation version + if (refactorEntityNameDTO.hasCachedEvaluationVersion()) { + return Mono.just(refactorEntityNameDTO.getCachedEvaluationVersion()); + } + + // Use cached page DTO if available + if (refactorEntityNameDTO.hasCachedPageDTO()) { + PageDTO pageDTO = refactorEntityNameDTO.getCachedPageDTO(); + String applicationId = pageDTO.getApplicationId(); + + // Set the page DTO in meta for other services to use + refactoringMetaDTO.setPageDTOMono(Mono.just(pageDTO)); + + return getEvaluationVersionMono(applicationId).map(evaluationVersion -> { + // Cache the evaluation version for future use + refactorEntityNameDTO.withCachedEvaluationVersion(evaluationVersion); + return evaluationVersion; + }); + } + + // Fallback to original implementation Mono pageDTOMono = getContextDTOMono(contextId, false); refactoringMetaDTO.setPageDTOMono(pageDTOMono); return pageDTOMono.flatMap( diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/refactors/applications/RefactoringServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/refactors/applications/RefactoringServiceCEImpl.java index c223f42af05..12c41fed755 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/refactors/applications/RefactoringServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/refactors/applications/RefactoringServiceCEImpl.java @@ -8,6 +8,7 @@ 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; @@ -20,6 +21,7 @@ import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.validations.EntityValidationService; +import com.appsmith.server.widgets.refactors.WidgetRefactoringServiceCEImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; @@ -141,6 +143,32 @@ public Mono refactorEntityName(RefactorEntityNameDTO refactorEntityNa // Sanitize refactor request wrt the type of entity being refactored service.sanitizeRefactorEntityDTO(refactorEntityNameDTO); + // Make sure to retrieve correct page id for branched page + String contextId = getBranchedContextId(refactorEntityNameDTO); + + // For contexts other than PAGE, skip context DTO fetching + if (!CreatorContextType.PAGE.equals(refactorEntityNameDTO.getContextType())) { + return continueRefactorFlow(refactorEntityNameDTO, service, contextId); + } + + // Pre-fetch context data once and populate cache to avoid redundant fetches + return contextLayoutRefactorResolver + .getContextLayoutRefactorHelper(refactorEntityNameDTO.getContextType()) + .getContextDTOMono(contextId, false) + .flatMap(contextDTO -> { + // Populate cache with actual objects based on context type + if (contextDTO instanceof PageDTO) { + refactorEntityNameDTO.withCachedPageDTO((PageDTO) contextDTO); + } + + // Continue with the rest of the refactor flow + return continueRefactorFlow(refactorEntityNameDTO, service, contextId); + }); + } + + private Mono continueRefactorFlow( + RefactorEntityNameDTO refactorEntityNameDTO, EntityRefactoringService service, String contextId) { + // Validate whether this name is allowed based on the type of entity Mono isValidNameMono; if (EntityType.WIDGET.equals(refactorEntityNameDTO.getEntityType())) { @@ -156,9 +184,6 @@ public Mono refactorEntityName(RefactorEntityNameDTO refactorEntityNa }); } - // Make sure to retrieve correct page id for branched page - String contextId = getBranchedContextId(refactorEntityNameDTO); - final Map analyticsProperties = new HashMap<>(); return isValidNameMono @@ -172,7 +197,7 @@ protected Mono> validateAndPrepareAnalyticsForRefactor( RefactorEntityNameDTO refactorEntityNameDTO, String branchedContextId, Map analyticsProperties) { - return validateEntityName(refactorEntityNameDTO, branchedContextId) + return validateEntityNameWithCache(refactorEntityNameDTO, branchedContextId) .then(prepareAnalyticsProperties(refactorEntityNameDTO, branchedContextId, analyticsProperties)); } @@ -181,11 +206,13 @@ protected Mono> prepareAnalyticsProperties( String branchedContextId, Map analyticsProperties) { refactorEntityNameDTO.setPageId(branchedContextId); - return newPageService.getByIdWithoutPermissionCheck(branchedContextId).map(page -> { - analyticsProperties.put(FieldName.APPLICATION_ID, page.getApplicationId()); - analyticsProperties.put(FieldName.PAGE_ID, refactorEntityNameDTO.getPageId()); - return analyticsProperties; - }); + + // Use cached applicationId directly - no database call needed! + if (refactorEntityNameDTO.hasCachedApplicationId()) { + analyticsProperties.put(FieldName.APPLICATION_ID, refactorEntityNameDTO.getCachedApplicationId()); + } + analyticsProperties.put(FieldName.PAGE_ID, refactorEntityNameDTO.getPageId()); + return Mono.just(analyticsProperties); } protected Mono validateEntityName(RefactorEntityNameDTO refactorEntityNameDTO, String contextId) { @@ -266,6 +293,52 @@ public Mono> getAllExistingEntitiesMono( return Flux.merge(existingEntityNamesFlux).collect(Collectors.toSet()); } + // New cache-aware method + public Mono> getAllExistingEntitiesMonoWithCache( + String contextId, + CreatorContextType contextType, + String layoutId, + boolean isFQN, + RefactorEntityNameDTO cacheDTO) { + + Iterable> existingEntityNamesFlux = + getExistingEntityNamesFluxWithCache(contextId, layoutId, isFQN, contextType, cacheDTO); + + return Flux.merge(existingEntityNamesFlux).collect(Collectors.toSet()); + } + + // Update the validation method to use cache + protected Mono validateEntityNameWithCache(RefactorEntityNameDTO refactorEntityNameDTO, String contextId) { + return isNameAllowedWithCache( + contextId, + getDefaultContextIfNull(refactorEntityNameDTO.getContextType()), + refactorEntityNameDTO.getLayoutId(), + refactorEntityNameDTO.getNewFullyQualifiedName(), + refactorEntityNameDTO) // Pass DTO with cache + .flatMap(valid -> { + if (!valid) { + return Mono.error(new AppsmithException( + AppsmithError.NAME_CLASH_NOT_ALLOWED_IN_REFACTOR, + refactorEntityNameDTO.getOldFullyQualifiedName(), + refactorEntityNameDTO.getNewFullyQualifiedName())); + } + return Mono.empty(); + }); + } + + public Mono isNameAllowedWithCache( + String contextId, + CreatorContextType contextType, + String layoutId, + String newName, + RefactorEntityNameDTO cacheDTO) { + + boolean isFQN = newName.contains("."); + + return getAllExistingEntitiesMonoWithCache(contextId, contextType, layoutId, isFQN, cacheDTO) + .map(existingNames -> !existingNames.contains(newName)); + } + protected Iterable> getExistingEntityNamesFlux( String contextId, String layoutId, boolean isFQN, CreatorContextType contextType) { Flux existingActionNamesFlux = @@ -296,4 +369,35 @@ protected Iterable> getExistingEntityNamesFlux( return list; } + + // New cache-aware method + protected Iterable> getExistingEntityNamesFluxWithCache( + String contextId, + String layoutId, + boolean isFQN, + CreatorContextType contextType, + RefactorEntityNameDTO cacheDTO) { + + Flux existingActionNamesFlux = + newActionEntityRefactoringService.getExistingEntityNames(contextId, contextType, layoutId, false); + + Flux existingWidgetNamesFlux = Flux.empty(); + Flux existingActionCollectionNamesFlux = Flux.empty(); + + if (!isFQN) { + // Use cache-aware method for widgets + existingWidgetNamesFlux = ((WidgetRefactoringServiceCEImpl) widgetEntityRefactoringService) + .getExistingEntityNamesWithCache(contextId, contextType, layoutId, false, cacheDTO); + + existingActionCollectionNamesFlux = actionCollectionEntityRefactoringService.getExistingEntityNames( + contextId, contextType, layoutId, false); + } + + ArrayList> list = new ArrayList<>(); + list.add(existingActionNamesFlux); + list.add(existingWidgetNamesFlux); + list.add(existingActionCollectionNamesFlux); + + return list; + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCE.java index 173ea95a818..84c1535ea3b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCE.java @@ -26,6 +26,8 @@ Flux findNonComposedByApplicationIdAndViewMode( Flux findByPageIds(List pageIds, AclPermission permission); + Flux findAllByIds(List ids, AclPermission permission); + Flux findAllByApplicationIds(List applicationIds, List includeFields); Flux findAllUnpublishedActionCollectionsByContextIdAndContextType( diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCEImpl.java index 10a9d9c9d64..d310589a01b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomActionCollectionRepositoryCEImpl.java @@ -90,6 +90,12 @@ public Flux findByPageIds(List pageIds, AclPermission return queryBuilder().criteria(pageIdCriteria).permission(permission).all(); } + @Override + public Flux findAllByIds(List ids, AclPermission permission) { + BridgeQuery idCriteria = Bridge.in(FieldName.ID, ids); + return queryBuilder().criteria(idCriteria).permission(permission).all(); + } + @Override public Flux findAllByApplicationIds(List applicationIds, List includeFields) { BridgeQuery applicationCriteria = Bridge.in(FieldName.APPLICATION_ID, applicationIds); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/widgets/refactors/WidgetRefactoringServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/widgets/refactors/WidgetRefactoringServiceCEImpl.java index 0aba27d7bc0..fce08deaf71 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/widgets/refactors/WidgetRefactoringServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/widgets/refactors/WidgetRefactoringServiceCEImpl.java @@ -7,6 +7,7 @@ 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; @@ -139,4 +140,38 @@ public Flux getExistingEntityNames( return Flux.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.LAYOUT_ID, layoutId)); }); } + + // New method that accepts cached data + public Flux getExistingEntityNamesWithCache( + String contextId, + CreatorContextType contextType, + String layoutId, + boolean viewMode, + RefactorEntityNameDTO cacheDTO) { + + // Use cached page DTO if available + if (cacheDTO.hasCachedPageDTO()) { + PageDTO pageDTO = cacheDTO.getCachedPageDTO(); + return extractWidgetNamesFromPageDTO(pageDTO, layoutId); + } + + // Fallback to original method + return getExistingEntityNames(contextId, contextType, layoutId, viewMode); + } + + private Flux extractWidgetNamesFromPageDTO(PageDTO pageDTO, String layoutId) { + List layouts = pageDTO.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.empty(); + } + } + return Flux.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.LAYOUT_ID, layoutId)); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/refactors/ce/RefactoringServiceCEImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/refactors/ce/RefactoringServiceCEImplTest.java index ac487b04608..41b16f13e90 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/refactors/ce/RefactoringServiceCEImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/refactors/ce/RefactoringServiceCEImplTest.java @@ -102,6 +102,10 @@ public void setUp() { Mockito.when(sessionUserService.getCurrentUser()).thenReturn(Mono.just(new User())); + // Mock the bulk find by IDs method + Mockito.when(actionCollectionRepository.findAllByIds(Mockito.anyList(), Mockito.any())) + .thenReturn(Flux.empty()); + refactoringServiceCE = new RefactoringServiceCEImpl( newPageService, updateLayoutService,