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:
parent
678df9c4aa
commit
56d296afc8
|
|
@ -24,6 +24,7 @@ import com.appsmith.server.services.SessionUserService;
|
||||||
import com.appsmith.server.solutions.PagePermission;
|
import com.appsmith.server.solutions.PagePermission;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
import io.micrometer.tracing.Span;
|
import io.micrometer.tracing.Span;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
@ -43,6 +44,7 @@ import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.regex.Pattern;
|
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(
|
protected Mono<LayoutDTO> updateLayoutDsl(
|
||||||
String creatorId,
|
String creatorId,
|
||||||
String layoutId,
|
String layoutId,
|
||||||
|
|
@ -121,122 +129,162 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
|
||||||
return Mono.just(generateResponseDTO(layout));
|
return Mono.just(generateResponseDTO(layout));
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> widgetNames = new HashSet<>();
|
// Get the main DSL's children array. We *don't* create an empty list
|
||||||
Map<String, Set<String>> widgetDynamicBindingsMap = new HashMap<>();
|
// yet; if no UI‑module instances are injected we want to leave the DSL
|
||||||
Set<String> escapedWidgetNames = new HashSet<>();
|
// unchanged so tests that expect a verbatim payload pass.
|
||||||
|
ArrayList<Object> mainChildren = (ArrayList<Object>) dsl.get(FieldName.CHILDREN);
|
||||||
|
|
||||||
Span extractAllWidgetNamesAndDynamicBindingsFromDSLSpan =
|
// Process DSL with module instances if any
|
||||||
observationHelper.createSpan(EXTRACT_ALL_WIDGET_NAMES_AND_DYNAMIC_BINDINGS_FROM_DSL);
|
return processDslAndCreateSyntheticWidgets(creatorId, creatorType, dsl, mainChildren)
|
||||||
|
.flatMap(entry -> {
|
||||||
|
JSONObject processedDsl = entry.getKey();
|
||||||
|
Optional<Set<String>> syntheticWidgetNamesOpt = entry.getValue();
|
||||||
|
|
||||||
observationHelper.startSpan(extractAllWidgetNamesAndDynamicBindingsFromDSLSpan);
|
Set<String> widgetNames = new HashSet<>();
|
||||||
|
Map<String, Set<String>> widgetDynamicBindingsMap = new HashMap<>();
|
||||||
|
Set<String> escapedWidgetNames = new HashSet<>();
|
||||||
|
|
||||||
try {
|
Span extractAllWidgetNamesAndDynamicBindingsFromDSLSpan =
|
||||||
dsl = extractAllWidgetNamesAndDynamicBindingsFromDSL(
|
observationHelper.createSpan(EXTRACT_ALL_WIDGET_NAMES_AND_DYNAMIC_BINDINGS_FROM_DSL);
|
||||||
dsl, widgetNames, widgetDynamicBindingsMap, creatorId, layoutId, escapedWidgetNames, creatorType);
|
|
||||||
|
|
||||||
} catch (Throwable t) {
|
observationHelper.startSpan(extractAllWidgetNamesAndDynamicBindingsFromDSLSpan);
|
||||||
return sendUpdateLayoutAnalyticsEvent(creatorId, layoutId, dsl, false, t, creatorType)
|
|
||||||
.then(Mono.error(t));
|
|
||||||
}
|
|
||||||
|
|
||||||
observationHelper.endSpan(extractAllWidgetNamesAndDynamicBindingsFromDSLSpan);
|
try {
|
||||||
|
processedDsl = extractAllWidgetNamesAndDynamicBindingsFromDSL(
|
||||||
layout.setWidgetNames(widgetNames);
|
processedDsl,
|
||||||
|
widgetNames,
|
||||||
if (!escapedWidgetNames.isEmpty()) {
|
widgetDynamicBindingsMap,
|
||||||
layout.setMongoEscapedWidgetNames(escapedWidgetNames);
|
creatorId,
|
||||||
}
|
layoutId,
|
||||||
|
escapedWidgetNames,
|
||||||
Set<String> executableNames = new HashSet<>();
|
creatorType);
|
||||||
Set<ExecutableDependencyEdge> edges = new HashSet<>();
|
} catch (Throwable t) {
|
||||||
Set<String> executablesUsedInDSL = new HashSet<>();
|
return sendUpdateLayoutAnalyticsEvent(creatorId, layoutId, processedDsl, false, t, creatorType)
|
||||||
List<Executable> flatmapOnLoadExecutables = new ArrayList<>();
|
.then(Mono.error(t));
|
||||||
List<LayoutExecutableUpdateDTO> executableUpdatesRef = new ArrayList<>();
|
|
||||||
List<String> messagesRef = new ArrayList<>();
|
|
||||||
|
|
||||||
AtomicReference<Boolean> validOnLoadExecutables = new AtomicReference<>(Boolean.TRUE);
|
|
||||||
|
|
||||||
// setting the layoutOnLoadActionActionErrors to empty to remove the existing
|
|
||||||
// errors before new DAG calculation.
|
|
||||||
layout.setLayoutOnLoadActionErrors(new ArrayList<>());
|
|
||||||
|
|
||||||
Mono<List<Set<DslExecutableDTO>>> allOnLoadExecutablesMono = onLoadExecutablesUtil
|
|
||||||
.findAllOnLoadExecutables(
|
|
||||||
creatorId,
|
|
||||||
evaluatedVersion,
|
|
||||||
widgetNames,
|
|
||||||
edges,
|
|
||||||
widgetDynamicBindingsMap,
|
|
||||||
flatmapOnLoadExecutables,
|
|
||||||
executablesUsedInDSL,
|
|
||||||
creatorType)
|
|
||||||
.name(FIND_ALL_ON_LOAD_EXECUTABLES)
|
|
||||||
.tap(Micrometer.observation(observationRegistry))
|
|
||||||
.onErrorResume(AppsmithException.class, error -> {
|
|
||||||
log.info(error.getMessage());
|
|
||||||
validOnLoadExecutables.set(FALSE);
|
|
||||||
layout.setLayoutOnLoadActionErrors(List.of(new ErrorDTO(
|
|
||||||
error.getAppErrorCode(),
|
|
||||||
error.getErrorType(),
|
|
||||||
layoutOnLoadActionErrorToastMessage,
|
|
||||||
error.getMessage(),
|
|
||||||
error.getTitle())));
|
|
||||||
return Mono.just(new ArrayList<>());
|
|
||||||
});
|
|
||||||
|
|
||||||
// First update the actions and set execute on load to true
|
|
||||||
JSONObject finalDsl = dsl;
|
|
||||||
|
|
||||||
Mono<LayoutDTO> layoutDTOMono = allOnLoadExecutablesMono
|
|
||||||
.flatMap(allOnLoadExecutables -> {
|
|
||||||
// If there has been an error (e.g. cyclical dependency), then don't update any
|
|
||||||
// actions.
|
|
||||||
// This is so that unnecessary updates don't happen to actions while the page is
|
|
||||||
// in invalid state.
|
|
||||||
if (!validOnLoadExecutables.get()) {
|
|
||||||
return Mono.just(allOnLoadExecutables);
|
|
||||||
}
|
}
|
||||||
// Update these executables to be executed on load, unless the user has touched
|
|
||||||
// the runBehaviour
|
observationHelper.endSpan(extractAllWidgetNamesAndDynamicBindingsFromDSLSpan);
|
||||||
// setting for this
|
|
||||||
return onLoadExecutablesUtil
|
// -----------------------------------------------------------------------------
|
||||||
.updateExecutablesRunBehaviour(
|
// If we have synthetic widgets, filter them out and scrub the DSL
|
||||||
flatmapOnLoadExecutables, creatorId, executableUpdatesRef, messagesRef, creatorType)
|
// -----------------------------------------------------------------------------
|
||||||
.name(UPDATE_EXECUTABLES_RUN_BEHAVIOUR)
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> executableNames = new HashSet<>();
|
||||||
|
Set<ExecutableDependencyEdge> edges = new HashSet<>();
|
||||||
|
Set<String> executablesUsedInDSL = new HashSet<>();
|
||||||
|
List<Executable> flatmapOnLoadExecutables = new ArrayList<>();
|
||||||
|
List<LayoutExecutableUpdateDTO> executableUpdatesRef = new ArrayList<>();
|
||||||
|
List<String> messagesRef = new ArrayList<>();
|
||||||
|
|
||||||
|
AtomicReference<Boolean> validOnLoadExecutables = new AtomicReference<>(Boolean.TRUE);
|
||||||
|
|
||||||
|
// setting the layoutOnLoadActionActionErrors to empty to remove the existing
|
||||||
|
// errors before new DAG calculation.
|
||||||
|
layout.setLayoutOnLoadActionErrors(new ArrayList<>());
|
||||||
|
|
||||||
|
Mono<List<Set<DslExecutableDTO>>> allOnLoadExecutablesMono = onLoadExecutablesUtil
|
||||||
|
.findAllOnLoadExecutables(
|
||||||
|
creatorId,
|
||||||
|
evaluatedVersion,
|
||||||
|
widgetNames,
|
||||||
|
edges,
|
||||||
|
widgetDynamicBindingsMap,
|
||||||
|
flatmapOnLoadExecutables,
|
||||||
|
executablesUsedInDSL,
|
||||||
|
creatorType)
|
||||||
|
.name(FIND_ALL_ON_LOAD_EXECUTABLES)
|
||||||
.tap(Micrometer.observation(observationRegistry))
|
.tap(Micrometer.observation(observationRegistry))
|
||||||
.thenReturn(allOnLoadExecutables);
|
.onErrorResume(AppsmithException.class, error -> {
|
||||||
})
|
log.info(error.getMessage());
|
||||||
// Now update the page layout with the page load executables and the graph.
|
validOnLoadExecutables.set(FALSE);
|
||||||
.flatMap(onLoadExecutables -> {
|
layout.setLayoutOnLoadActionErrors(List.of(new ErrorDTO(
|
||||||
layout.setLayoutOnLoadActions(onLoadExecutables);
|
error.getAppErrorCode(),
|
||||||
layout.setAllOnPageLoadActionNames(executableNames);
|
error.getErrorType(),
|
||||||
layout.setActionsUsedInDynamicBindings(executablesUsedInDSL);
|
layoutOnLoadActionErrorToastMessage,
|
||||||
// The below field is to ensure that we record if the page load actions
|
error.getMessage(),
|
||||||
// computation was
|
error.getTitle())));
|
||||||
// valid when last stored in the database.
|
return Mono.just(new ArrayList<>());
|
||||||
layout.setValidOnPageLoadActions(validOnLoadExecutables.get());
|
});
|
||||||
|
|
||||||
return onLoadExecutablesUtil
|
// First update the actions and set execute on load to true
|
||||||
.findAndUpdateLayout(creatorId, creatorType, layoutId, layout)
|
JSONObject finalDsl = processedDsl;
|
||||||
.tag("no_of_widgets", String.valueOf(widgetNames.size()))
|
|
||||||
.tag("no_of_executables", String.valueOf(executableNames.size()))
|
|
||||||
.name(FIND_AND_UPDATE_LAYOUT)
|
|
||||||
.tap(Micrometer.observation(observationRegistry));
|
|
||||||
})
|
|
||||||
.map(savedLayout -> {
|
|
||||||
savedLayout.setDsl(this.unescapeMongoSpecialCharacters(savedLayout));
|
|
||||||
return savedLayout;
|
|
||||||
})
|
|
||||||
.flatMap(savedLayout -> {
|
|
||||||
LayoutDTO layoutDTO = generateResponseDTO(savedLayout);
|
|
||||||
layoutDTO.setActionUpdates(executableUpdatesRef);
|
|
||||||
layoutDTO.setMessages(messagesRef);
|
|
||||||
|
|
||||||
return sendUpdateLayoutAnalyticsEvent(creatorId, layoutId, finalDsl, true, null, creatorType)
|
return allOnLoadExecutablesMono
|
||||||
.thenReturn(layoutDTO);
|
.flatMap(allOnLoadExecutables -> {
|
||||||
|
// If there has been an error (e.g. cyclical dependency), then don't update any
|
||||||
|
// actions.
|
||||||
|
// This is so that unnecessary updates don't happen to actions while the page is
|
||||||
|
// in invalid state.
|
||||||
|
if (!validOnLoadExecutables.get()) {
|
||||||
|
return Mono.just(allOnLoadExecutables);
|
||||||
|
}
|
||||||
|
// Update these executables to be executed on load, unless the user has touched
|
||||||
|
// the runBehaviour
|
||||||
|
// setting for this
|
||||||
|
return onLoadExecutablesUtil
|
||||||
|
.updateExecutablesRunBehaviour(
|
||||||
|
flatmapOnLoadExecutables,
|
||||||
|
creatorId,
|
||||||
|
executableUpdatesRef,
|
||||||
|
messagesRef,
|
||||||
|
creatorType)
|
||||||
|
.name(UPDATE_EXECUTABLES_RUN_BEHAVIOUR)
|
||||||
|
.tap(Micrometer.observation(observationRegistry))
|
||||||
|
.thenReturn(allOnLoadExecutables);
|
||||||
|
})
|
||||||
|
// Now update the page layout with the page load executables and the graph.
|
||||||
|
.flatMap(onLoadExecutables -> {
|
||||||
|
layout.setLayoutOnLoadActions(onLoadExecutables);
|
||||||
|
layout.setAllOnPageLoadActionNames(executableNames);
|
||||||
|
layout.setActionsUsedInDynamicBindings(executablesUsedInDSL);
|
||||||
|
// The below field is to ensure that we record if the page load actions
|
||||||
|
// computation was
|
||||||
|
// valid when last stored in the database.
|
||||||
|
|
||||||
|
return onLoadExecutablesUtil
|
||||||
|
.findAndUpdateLayout(creatorId, creatorType, layoutId, layout)
|
||||||
|
.tag("no_of_widgets", String.valueOf(widgetNames.size()))
|
||||||
|
.tag("no_of_executables", String.valueOf(executableNames.size()))
|
||||||
|
.name(FIND_AND_UPDATE_LAYOUT)
|
||||||
|
.tap(Micrometer.observation(observationRegistry));
|
||||||
|
})
|
||||||
|
.map(savedLayout -> {
|
||||||
|
savedLayout.setDsl(this.unescapeMongoSpecialCharacters(savedLayout));
|
||||||
|
return savedLayout;
|
||||||
|
})
|
||||||
|
.flatMap(savedLayout -> {
|
||||||
|
LayoutDTO layoutDTO = generateResponseDTO(savedLayout);
|
||||||
|
layoutDTO.setActionUpdates(executableUpdatesRef);
|
||||||
|
layoutDTO.setMessages(messagesRef);
|
||||||
|
|
||||||
|
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
|
// TODO: Add contextType and change all its usage to conform to that so that we can get rid of the overloaded
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user