feat: updated toast messages for on page load actions (#40726)

## Description
This PR:
- updates the toast message that we see on add/remove of query binding
with widget after the introduction of automatic run behaviour. We have
updated the toast messages as per
https://miro.com/app/board/uXjVIQ1GcX4=/?moveToWidget=3458764621410290209&cot=14
- Also resolves #40720 

**Steps To Reproduce**
1. Create Query1
2. Create Table1
3. Edit Query1 body to be -> SELECT * FROM public."users" LIMIT
{{Query2.data.length}};
4. Attach this query1 to table1 -> We should a toast message saying
Query1 will execute automatically on page load
Now add Query2 and see the toast message -> It says "Query1, Query2 will
execute automatically on page load instead it should just be Query2 in
the message

Ref:
https://github.com/appsmithorg/appsmith/issues/40656#issuecomment-2896912279


Fixes #40720 #40471  
_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.Table"

### 🔍 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/15179938858>
> Commit: 141e2754cf202ee54d7373a0ef3ed0ee1217c876
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=15179938858&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table`
> Spec:
> <hr>Thu, 22 May 2025 08:31:09 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**
- Improved how on-load executables are managed based on a feature flag,
dynamically adjusting their run behavior and user messages.
- **Tests**
- Added new tests to verify correct messaging and behavior updates for
executables under different feature flag conditions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: “sneha122” <“sneha@appsmith.com”>
This commit is contained in:
sneha122 2025-05-22 15:49:40 +05:30 committed by GitHub
parent c733ae0b63
commit dae6281654
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 375 additions and 106 deletions

View File

@ -312,10 +312,17 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
Flux<Executable> creatorContextExecutablesFlux = Flux<Executable> creatorContextExecutablesFlux =
this.getAllExecutablesByCreatorIdFlux(creatorId, creatorType).cache(); this.getAllExecutablesByCreatorIdFlux(creatorId, creatorType).cache();
// Check the feature flag first to determine which RunBehaviourEnum to use for comparison
return featureFlagService
.check(FeatureFlagEnum.release_reactive_actions_enabled)
.flatMap(isReactiveActionsEnabled -> {
RunBehaviourEnum runBehaviourToCheck =
isReactiveActionsEnabled ? RunBehaviourEnum.AUTOMATIC : RunBehaviourEnum.ON_PAGE_LOAD;
// Before we update the actions, fetch all the actions which are currently set to execute on load. // Before we update the actions, fetch all the actions which are currently set to execute on load.
Mono<List<Executable>> existingOnLoadExecutablesMono = creatorContextExecutablesFlux Mono<List<Executable>> existingOnLoadExecutablesMono = creatorContextExecutablesFlux
.flatMap(executable -> { .flatMap(executable -> {
if (RunBehaviourEnum.ON_PAGE_LOAD.equals(executable.getRunBehaviour())) { if (runBehaviourToCheck.equals(executable.getRunBehaviour())) {
return Mono.just(executable); return Mono.just(executable);
} }
return Mono.empty(); return Mono.empty();
@ -324,19 +331,19 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
return existingOnLoadExecutablesMono return existingOnLoadExecutablesMono
.zipWith(creatorContextExecutablesFlux.collectList()) .zipWith(creatorContextExecutablesFlux.collectList())
.flatMap(tuple -> featureFlagService .flatMap(tuple -> {
.check(FeatureFlagEnum.release_reactive_actions_enabled)
.flatMap(isReactiveActionsEnabled -> {
List<Executable> existingOnLoadExecutables = tuple.getT1(); List<Executable> existingOnLoadExecutables = tuple.getT1();
List<Executable> creatorContextExecutables = tuple.getT2(); List<Executable> creatorContextExecutables = tuple.getT2();
// There are no actions in this page. No need to proceed further since no actions would get // There are no actions in this page. No need to proceed further since no actions would
// get
// updated // updated
if (CollectionUtils.isNullOrEmpty(creatorContextExecutables)) { if (CollectionUtils.isNullOrEmpty(creatorContextExecutables)) {
return Mono.just(FALSE); return Mono.just(FALSE);
} }
// No actions require an update if no actions have been found as page load actions as well // No actions require an update if no actions have been found as page load actions as
// well
// as // as
// existing on load page actions are empty // existing on load page actions are empty
if (CollectionUtils.isNullOrEmpty(existingOnLoadExecutables) if (CollectionUtils.isNullOrEmpty(existingOnLoadExecutables)
@ -345,7 +352,8 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
return Mono.just(FALSE); return Mono.just(FALSE);
} }
// Extract names of existing page load actions and new page load actions for quick lookup. // Extract names of existing page load actions and new page load actions for quick
// lookup.
Set<String> existingOnLoadExecutableNames = existingOnLoadExecutables.stream() Set<String> existingOnLoadExecutableNames = existingOnLoadExecutables.stream()
.map(Executable::getUserExecutableName) .map(Executable::getUserExecutableName)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@ -354,12 +362,14 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
.map(Executable::getUserExecutableName) .map(Executable::getUserExecutableName)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// Calculate the actions which would need to be updated from execute on load TRUE to FALSE. // Calculate the actions which would need to be updated from execute on load TRUE to
// FALSE.
Set<String> turnedOffExecutableNames = new HashSet<>(); Set<String> turnedOffExecutableNames = new HashSet<>();
turnedOffExecutableNames.addAll(existingOnLoadExecutableNames); turnedOffExecutableNames.addAll(existingOnLoadExecutableNames);
turnedOffExecutableNames.removeAll(newOnLoadExecutableNames); turnedOffExecutableNames.removeAll(newOnLoadExecutableNames);
// Calculate the actions which would need to be updated from execute on load FALSE to TRUE // Calculate the actions which would need to be updated from execute on load FALSE to
// TRUE
Set<String> turnedOnExecutableNames = new HashSet<>(); Set<String> turnedOnExecutableNames = new HashSet<>();
turnedOnExecutableNames.addAll(newOnLoadExecutableNames); turnedOnExecutableNames.addAll(newOnLoadExecutableNames);
turnedOnExecutableNames.removeAll(existingOnLoadExecutableNames); turnedOnExecutableNames.removeAll(existingOnLoadExecutableNames);
@ -367,13 +377,16 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
for (Executable executable : creatorContextExecutables) { for (Executable executable : creatorContextExecutables) {
String executableName = executable.getUserExecutableName(); String executableName = executable.getUserExecutableName();
// If a user has ever set execute on load, this field can not be changed automatically. // If a user has ever set execute on load, this field can not be changed
// automatically.
// It has // It has
// to be explicitly changed by the user again. Add the executable to update only if this // to be explicitly changed by the user again. Add the executable to update only if
// this
// condition is false. // condition is false.
if (FALSE.equals(executable.getUserSetOnLoad())) { if (FALSE.equals(executable.getUserSetOnLoad())) {
// If this executable is no longer an onload executable, turn the execute on load to // If this executable is no longer an onload executable, turn the execute on
// load to
// false // false
if (turnedOffExecutableNames.contains(executableName)) { if (turnedOffExecutableNames.contains(executableName)) {
executable.setRunBehaviour(RunBehaviourEnum.MANUAL); executable.setRunBehaviour(RunBehaviourEnum.MANUAL);
@ -416,9 +429,23 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
} }
} }
// Now add messagesRef that would eventually be displayed to the developer user informing // Now add messagesRef that would eventually be displayed to the developer user
// informing
// them // them
// about the action setting change. // about the action setting change.
if (isReactiveActionsEnabled) {
if (!turnedOffExecutableNames.isEmpty()) {
messagesRef.add(
turnedOffExecutableNames.toString()
+ " will no longer run automatically. You can run it manually when needed.");
}
if (!turnedOnExecutableNames.isEmpty()) {
messagesRef.add(
turnedOnExecutableNames.toString()
+ " will run automatically on page load or when a variable it depends on changes");
}
} else {
if (!turnedOffExecutableNames.isEmpty()) { if (!turnedOffExecutableNames.isEmpty()) {
messagesRef.add(turnedOffExecutableNames.toString() messagesRef.add(turnedOffExecutableNames.toString()
+ " will no longer be executed on page load"); + " will no longer be executed on page load");
@ -428,13 +455,15 @@ public class OnLoadExecutablesUtilCEImpl implements OnLoadExecutablesUtilCE {
messagesRef.add(turnedOnExecutableNames.toString() messagesRef.add(turnedOnExecutableNames.toString()
+ " will be executed automatically on page load"); + " will be executed automatically on page load");
} }
}
// Finally update the actions which require an update // Finally update the actions which require an update
return Flux.fromIterable(toUpdateExecutables) return Flux.fromIterable(toUpdateExecutables)
.flatMap(executable -> this.updateUnpublishedExecutable( .flatMap(executable -> this.updateUnpublishedExecutable(
executable.getId(), executable, creatorType)) executable.getId(), executable, creatorType))
.then(Mono.just(TRUE)); .then(Mono.just(TRUE));
})); });
});
} }
@Override @Override

View File

@ -236,6 +236,246 @@ public class OnLoadExecutablesUtilCEImplTest {
verify(executableOnLoadService, times(0)).updateUnpublishedExecutable(anyString(), any(ActionDTO.class)); verify(executableOnLoadService, times(0)).updateUnpublishedExecutable(anyString(), any(ActionDTO.class));
} }
@Test
public void whenFeatureFlagOn_andExecutableTurnedOn_shouldShowReactiveMessage() {
// Setup
ActionDTO existingAction = new ActionDTO();
existingAction.setName("TestApi");
existingAction.setUserSetOnLoad(false);
existingAction.setRunBehaviour(RunBehaviourEnum.MANUAL);
existingAction.setId("1");
ActionDTO updatedAction = new ActionDTO();
updatedAction.setName("TestApi");
updatedAction.setUserSetOnLoad(false);
updatedAction.setRunBehaviour(RunBehaviourEnum.AUTOMATIC);
updatedAction.setId("1");
List<Executable> onLoadExecutables = List.of(updatedAction);
List<LayoutExecutableUpdateDTO> executableUpdates = new ArrayList<>();
List<String> messages = new ArrayList<>();
when(executableOnLoadService.getAllExecutablesByCreatorIdFlux(any())).thenReturn(Flux.just(existingAction));
when(featureFlagService.check(any())).thenReturn(Mono.just(true));
when(executableOnLoadService.updateUnpublishedExecutable(eq("1"), any()))
.thenReturn(Mono.just(updatedAction));
// Execute and verify
StepVerifier.create(onLoadExecutablesUtilCE.updateExecutablesRunBehaviour(
onLoadExecutables, "creatorId", executableUpdates, messages, CreatorContextType.PAGE))
.expectNext(true)
.verifyComplete();
// Assert
assert messages.size() == 1;
assert messages.get(0).contains("TestApi");
assert messages.get(0).contains("will run automatically on page load or when a variable it depends on changes");
}
@Test
public void whenFeatureFlagOff_andExecutableTurnedOn_shouldShowPageLoadMessage() {
// Setup
ActionDTO existingAction = new ActionDTO();
existingAction.setName("TestApi");
existingAction.setUserSetOnLoad(false);
existingAction.setRunBehaviour(RunBehaviourEnum.MANUAL);
existingAction.setId("1");
ActionDTO updatedAction = new ActionDTO();
updatedAction.setName("TestApi");
updatedAction.setUserSetOnLoad(false);
updatedAction.setRunBehaviour(RunBehaviourEnum.ON_PAGE_LOAD);
updatedAction.setId("1");
List<Executable> onLoadExecutables = List.of(updatedAction);
List<LayoutExecutableUpdateDTO> executableUpdates = new ArrayList<>();
List<String> messages = new ArrayList<>();
when(executableOnLoadService.getAllExecutablesByCreatorIdFlux(any())).thenReturn(Flux.just(existingAction));
when(featureFlagService.check(any())).thenReturn(Mono.just(false));
when(executableOnLoadService.updateUnpublishedExecutable(eq("1"), any()))
.thenReturn(Mono.just(updatedAction));
// Execute and verify
StepVerifier.create(onLoadExecutablesUtilCE.updateExecutablesRunBehaviour(
onLoadExecutables, "creatorId", executableUpdates, messages, CreatorContextType.PAGE))
.expectNext(true)
.verifyComplete();
// Assert
assert messages.size() == 1;
assert messages.get(0).contains("TestApi");
assert messages.get(0).contains("will be executed automatically on page load");
}
@Test
public void whenFeatureFlagOn_andExecutableTurnedOff_shouldShowManualMessage() {
// Setup
ActionDTO existingAction = new ActionDTO();
existingAction.setName("TestApi");
existingAction.setUserSetOnLoad(false);
existingAction.setRunBehaviour(RunBehaviourEnum.AUTOMATIC);
existingAction.setId("1");
ActionDTO updatedAction = new ActionDTO();
updatedAction.setName("TestApi");
updatedAction.setUserSetOnLoad(false);
updatedAction.setRunBehaviour(RunBehaviourEnum.MANUAL);
updatedAction.setId("1");
List<Executable> onLoadExecutables = new ArrayList<>(); // Empty list means turning off
List<LayoutExecutableUpdateDTO> executableUpdates = new ArrayList<>();
List<String> messages = new ArrayList<>();
when(executableOnLoadService.getAllExecutablesByCreatorIdFlux(any())).thenReturn(Flux.just(existingAction));
when(featureFlagService.check(any())).thenReturn(Mono.just(true));
when(executableOnLoadService.updateUnpublishedExecutable(eq("1"), any()))
.thenReturn(Mono.just(updatedAction));
// Execute and verify
StepVerifier.create(onLoadExecutablesUtilCE.updateExecutablesRunBehaviour(
onLoadExecutables, "creatorId", executableUpdates, messages, CreatorContextType.PAGE))
.expectNext(true)
.verifyComplete();
// Assert
assert messages.size() == 1;
assert messages.get(0).contains("TestApi");
assert messages.get(0).contains("will no longer run automatically");
}
@Test
public void whenOnlyOneActionChange_shouldShowOnlyThatEntityInToastMessage() {
// Setup
ActionDTO existingAction1 = new ActionDTO();
existingAction1.setName("Api1");
existingAction1.setUserSetOnLoad(false);
existingAction1.setRunBehaviour(RunBehaviourEnum.AUTOMATIC);
existingAction1.setId("1");
ActionDTO existingAction2 = new ActionDTO();
existingAction2.setName("Api2");
existingAction2.setUserSetOnLoad(false);
existingAction2.setRunBehaviour(RunBehaviourEnum.MANUAL);
existingAction2.setId("2");
ActionDTO unchangedAction1 = new ActionDTO();
unchangedAction1.setName("Api1");
unchangedAction1.setUserSetOnLoad(false);
unchangedAction1.setRunBehaviour(RunBehaviourEnum.AUTOMATIC);
unchangedAction1.setId("1");
ActionDTO updatedAction2 = new ActionDTO();
updatedAction2.setName("Api2");
updatedAction2.setUserSetOnLoad(false);
updatedAction2.setRunBehaviour(RunBehaviourEnum.AUTOMATIC);
updatedAction2.setId("2");
List<Executable> onLoadExecutables =
List.of(updatedAction2, unchangedAction1); // Only Api2 is in the onLoad list
List<LayoutExecutableUpdateDTO> executableUpdates = new ArrayList<>();
List<String> messages = new ArrayList<>();
when(executableOnLoadService.getAllExecutablesByCreatorIdFlux(any()))
.thenReturn(Flux.just(existingAction1, existingAction2));
when(featureFlagService.check(any())).thenReturn(Mono.just(true));
// Mock different behaviors for different executable IDs
when(executableOnLoadService.updateUnpublishedExecutable(eq("2"), any()))
.thenReturn(Mono.just(updatedAction2));
// Execute and verify
StepVerifier.create(onLoadExecutablesUtilCE.updateExecutablesRunBehaviour(
onLoadExecutables, "creatorId", executableUpdates, messages, CreatorContextType.PAGE))
.expectNext(true)
.verifyComplete();
// Assert
assert messages.size() == 1;
assert messages.stream()
.anyMatch(msg -> msg.contains("Api2")
&& msg.contains("will run automatically on page load or when a variable it depends on changes")
&& !msg.contains("Api1"));
}
@Test
public void whenMultipleExecutablesChange_shouldShowAllMessages() {
// Setup
ActionDTO existingAction1 = new ActionDTO();
existingAction1.setName("Api1");
existingAction1.setUserSetOnLoad(false);
existingAction1.setRunBehaviour(RunBehaviourEnum.AUTOMATIC);
existingAction1.setId("1");
ActionDTO existingAction2 = new ActionDTO();
existingAction2.setName("Api2");
existingAction2.setUserSetOnLoad(false);
existingAction2.setRunBehaviour(RunBehaviourEnum.MANUAL);
existingAction2.setId("2");
ActionDTO updatedAction1 = new ActionDTO();
updatedAction1.setName("Api1");
updatedAction1.setUserSetOnLoad(false);
updatedAction1.setRunBehaviour(RunBehaviourEnum.MANUAL);
updatedAction1.setId("1");
ActionDTO updatedAction2 = new ActionDTO();
updatedAction2.setName("Api2");
updatedAction2.setUserSetOnLoad(false);
updatedAction2.setRunBehaviour(RunBehaviourEnum.AUTOMATIC);
updatedAction2.setId("2");
List<Executable> onLoadExecutables = List.of(updatedAction2); // Only Api2 is in the onLoad list
List<LayoutExecutableUpdateDTO> executableUpdates = new ArrayList<>();
List<String> messages = new ArrayList<>();
when(executableOnLoadService.getAllExecutablesByCreatorIdFlux(any()))
.thenReturn(Flux.just(existingAction1, existingAction2));
when(featureFlagService.check(any())).thenReturn(Mono.just(true));
// Mock different behaviors for different executable IDs
when(executableOnLoadService.updateUnpublishedExecutable(eq("1"), any()))
.thenReturn(Mono.just(updatedAction1));
when(executableOnLoadService.updateUnpublishedExecutable(eq("2"), any()))
.thenReturn(Mono.just(updatedAction2));
// Execute and verify
StepVerifier.create(onLoadExecutablesUtilCE.updateExecutablesRunBehaviour(
onLoadExecutables, "creatorId", executableUpdates, messages, CreatorContextType.PAGE))
.expectNext(true)
.verifyComplete();
// Assert
assert messages.size() == 2;
assert messages.stream()
.anyMatch(msg -> msg.contains("Api1") && msg.contains("will no longer run automatically"));
assert messages.stream()
.anyMatch(msg -> msg.contains("Api2")
&& msg.contains(
"will run automatically on page load or when a variable it depends on changes"));
}
@Test
public void whenNoExecutables_shouldReturnFalse() {
// Setup
List<Executable> onLoadExecutables = new ArrayList<>();
List<LayoutExecutableUpdateDTO> executableUpdates = new ArrayList<>();
List<String> messages = new ArrayList<>();
when(executableOnLoadService.getAllExecutablesByCreatorIdFlux(any())).thenReturn(Flux.empty());
when(featureFlagService.check(any())).thenReturn(Mono.just(true));
// Execute and verify
StepVerifier.create(onLoadExecutablesUtilCE.updateExecutablesRunBehaviour(
onLoadExecutables, "creatorId", executableUpdates, messages, CreatorContextType.PAGE))
.expectNext(false)
.verifyComplete();
// Assert
assert messages.isEmpty();
assert executableUpdates.isEmpty();
}
// Helper methods to create test executables // Helper methods to create test executables
private List<Executable> createTestExecutables(String... names) { private List<Executable> createTestExecutables(String... names) {
List<Executable> executables = new ArrayList<>(); List<Executable> executables = new ArrayList<>();