chore: Add code-split for OPL with UI module instances (#40749)

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


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/15253633122>
> Commit: dd70a1c673da72ef7e5bdea172b90d7ec338f41a
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=15253633122&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 26 May 2025 13:55:15 UTC
<!-- end of auto-generated comment: 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

- **Refactor**
- Enhanced layout update processing by modularizing synthetic widget
handling, enabling future UI-specific widget injection and removal. No
direct impact on user-facing features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
subratadeypappu 2025-05-26 23:53:13 +06:00 committed by GitHub
parent 678df9c4aa
commit 56d296afc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -24,6 +24,7 @@ import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.PagePermission;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Span;
import lombok.RequiredArgsConstructor;
@ -43,6 +44,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
@ -109,6 +111,12 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
});
}
// Extension point for UI module handling
protected Mono<Map.Entry<JSONObject, Optional<Set<String>>>> processDslAndCreateSyntheticWidgets(
String creatorId, CreatorContextType creatorType, JSONObject dsl, ArrayList<Object> mainChildren) {
return Mono.just(Map.entry(dsl, Optional.empty()));
}
protected Mono<LayoutDTO> updateLayoutDsl(
String creatorId,
String layoutId,
@ -121,6 +129,17 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
return Mono.just(generateResponseDTO(layout));
}
// Get the main DSL's children array. We *don't* create an empty list
// yet; if no UImodule instances are injected we want to leave the DSL
// unchanged so tests that expect a verbatim payload pass.
ArrayList<Object> mainChildren = (ArrayList<Object>) dsl.get(FieldName.CHILDREN);
// Process DSL with module instances if any
return processDslAndCreateSyntheticWidgets(creatorId, creatorType, dsl, mainChildren)
.flatMap(entry -> {
JSONObject processedDsl = entry.getKey();
Optional<Set<String>> syntheticWidgetNamesOpt = entry.getValue();
Set<String> widgetNames = new HashSet<>();
Map<String, Set<String>> widgetDynamicBindingsMap = new HashMap<>();
Set<String> escapedWidgetNames = new HashSet<>();
@ -131,18 +150,40 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
observationHelper.startSpan(extractAllWidgetNamesAndDynamicBindingsFromDSLSpan);
try {
dsl = extractAllWidgetNamesAndDynamicBindingsFromDSL(
dsl, widgetNames, widgetDynamicBindingsMap, creatorId, layoutId, escapedWidgetNames, creatorType);
processedDsl = extractAllWidgetNamesAndDynamicBindingsFromDSL(
processedDsl,
widgetNames,
widgetDynamicBindingsMap,
creatorId,
layoutId,
escapedWidgetNames,
creatorType);
} catch (Throwable t) {
return sendUpdateLayoutAnalyticsEvent(creatorId, layoutId, dsl, false, t, creatorType)
return sendUpdateLayoutAnalyticsEvent(creatorId, layoutId, processedDsl, false, t, creatorType)
.then(Mono.error(t));
}
observationHelper.endSpan(extractAllWidgetNamesAndDynamicBindingsFromDSLSpan);
layout.setWidgetNames(widgetNames);
// -----------------------------------------------------------------------------
// If we have synthetic widgets, filter them out and scrub the DSL
// -----------------------------------------------------------------------------
if (syntheticWidgetNamesOpt.isPresent()) {
Set<String> syntheticWidgetNames = syntheticWidgetNamesOpt.get();
Set<String> widgetNamesToPersist = Sets.difference(widgetNames, syntheticWidgetNames);
processedDsl = stripSyntheticWidgets(processedDsl, syntheticWidgetNamesOpt);
removeSpecialCharactersFromKeys(processedDsl, escapedWidgetNames);
layout.setWidgetNames(widgetNamesToPersist);
} else {
layout.setWidgetNames(widgetNames);
}
layout.setDsl(processedDsl);
// We always attach escaped names when we have them
if (!escapedWidgetNames.isEmpty()) {
layout.setMongoEscapedWidgetNames(escapedWidgetNames);
}
@ -185,9 +226,9 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
});
// First update the actions and set execute on load to true
JSONObject finalDsl = dsl;
JSONObject finalDsl = processedDsl;
Mono<LayoutDTO> layoutDTOMono = allOnLoadExecutablesMono
return allOnLoadExecutablesMono
.flatMap(allOnLoadExecutables -> {
// If there has been an error (e.g. cyclical dependency), then don't update any
// actions.
@ -201,7 +242,11 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
// setting for this
return onLoadExecutablesUtil
.updateExecutablesRunBehaviour(
flatmapOnLoadExecutables, creatorId, executableUpdatesRef, messagesRef, creatorType)
flatmapOnLoadExecutables,
creatorId,
executableUpdatesRef,
messagesRef,
creatorType)
.name(UPDATE_EXECUTABLES_RUN_BEHAVIOUR)
.tap(Micrometer.observation(observationRegistry))
.thenReturn(allOnLoadExecutables);
@ -214,7 +259,6 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
// The below field is to ensure that we record if the page load actions
// computation was
// valid when last stored in the database.
layout.setValidOnPageLoadActions(validOnLoadExecutables.get());
return onLoadExecutablesUtil
.findAndUpdateLayout(creatorId, creatorType, layoutId, layout)
@ -232,11 +276,15 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
layoutDTO.setActionUpdates(executableUpdatesRef);
layoutDTO.setMessages(messagesRef);
return sendUpdateLayoutAnalyticsEvent(creatorId, layoutId, finalDsl, true, null, creatorType)
return sendUpdateLayoutAnalyticsEvent(
creatorId, layoutId, finalDsl, true, null, creatorType)
.thenReturn(layoutDTO);
});
});
}
return layoutDTOMono;
protected JSONObject stripSyntheticWidgets(JSONObject dsl, Optional<Set<String>> syntheticNamesOpt) {
return dsl;
}
// TODO: Add contextType and change all its usage to conform to that so that we can get rid of the overloaded