Skip to content

Commit 475939f

Browse files
authored
feat: add onPageUnload option in UI behind feature flag. (#41008)
## Description <ins>Problem</ins> The action configuration UI lacked support for running actions specifically on page unload, limiting automation and flexibility for users. <ins>Root cause</ins> There was no ON_PAGE_UNLOAD option in the ActionRunBehaviour enum or related UI, and feature flag handling for this behavior was missing. <ins>Solution</ins> This PR handles the addition of the ON_PAGE_UNLOAD run behavior to the ActionRunBehaviour enum, updates UI and settings to support it, leverages feature flags for dynamic option availability, and adds tests to ensure robust behavior. Fixes #40995 _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.JS" ### 🔍 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/15774903938> > Commit: fe0b500 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=15774903938&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.JS` > Spec: > <hr>Fri, 20 Jun 2025 09:27:32 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 a new "On page unload" run behavior option for actions and JS functions, allowing actions to be triggered when a page is unloaded. - Added a feature flag to enable or disable the "On page unload" run behavior. - **Enhancements** - Run behavior options in the editor now dynamically update based on feature flag settings, ensuring only valid options are shown. - **Tests** - Added comprehensive unit tests for utility functions handling run behavior options and defaults. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 814fb3d commit 475939f

6 files changed

Lines changed: 253 additions & 27 deletions

File tree

app/client/src/PluginActionEditor/types/PluginActionTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export enum ActionRunBehaviour {
66
ON_PAGE_LOAD = "ON_PAGE_LOAD",
77
MANUAL = "MANUAL",
88
AUTOMATIC = "AUTOMATIC",
9+
ON_PAGE_UNLOAD = "ON_PAGE_UNLOAD",
910
}
1011

1112
export type ActionRunBehaviourType = `${ActionRunBehaviour}`;

app/client/src/ce/entities/FeatureFlag.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export const FEATURE_FLAG = {
6060
release_ai_chat_integrations_enabled: "release_ai_chat_integrations_enabled",
6161
release_reactive_actions_enabled: "release_reactive_actions_enabled",
6262
license_ai_agent_instance_enabled: "license_ai_agent_instance_enabled",
63+
release_jsobjects_onpageunloadactions_enabled:
64+
"release_jsobjects_onpageunloadactions_enabled",
6365
} as const;
6466

6567
export type FeatureFlag = keyof typeof FEATURE_FLAG;
@@ -110,6 +112,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
110112
release_ai_chat_integrations_enabled: false,
111113
release_reactive_actions_enabled: false,
112114
license_ai_agent_instance_enabled: false,
115+
release_jsobjects_onpageunloadactions_enabled: false,
113116
};
114117

115118
export const AB_TESTING_EVENT_KEYS = {

app/client/src/constants/AppsmithActionConstants/formConfig/PluginSettings.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,13 @@ export const RUN_BEHAVIOR_VALUES = [
2222
children: "Manual",
2323
},
2424
];
25+
26+
export const JS_OBJECT_RUN_BEHAVIOR_VALUES = [
27+
...RUN_BEHAVIOR_VALUES,
28+
{
29+
label: "On page unload",
30+
subText: "Query runs when the page unloads or when manually triggered",
31+
value: ActionRunBehaviour.ON_PAGE_UNLOAD,
32+
children: "On page unload",
33+
},
34+
];

app/client/src/pages/Editor/JSEditor/JSEditorToolbar/components/JSFunctionSettings.tsx

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
import React, { useCallback, useState } from "react";
2-
import {
3-
Flex,
4-
Option,
5-
Select,
6-
Text,
7-
type SelectOptionProps,
8-
} from "@appsmith/ads";
9-
import type { JSAction } from "entities/JSCollection";
1+
import { Flex, Option, Select, Text } from "@appsmith/ads";
102
import {
113
createMessage,
124
JS_EDITOR_SETTINGS,
135
NO_JS_FUNCTIONS,
146
} from "ee/constants/messages";
7+
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
158
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
16-
import type { OnUpdateSettingsProps } from "../types";
17-
import {
18-
ActionRunBehaviour,
19-
type ActionRunBehaviourType,
20-
} from "PluginActionEditor/types/PluginActionTypes";
9+
import type { JSAction } from "entities/JSCollection";
10+
import { type ActionRunBehaviourType } from "PluginActionEditor/types/PluginActionTypes";
11+
import React, { useCallback, useMemo, useState } from "react";
2112
import styled from "styled-components";
22-
import { RUN_BEHAVIOR_VALUES } from "constants/AppsmithActionConstants/formConfig/PluginSettings";
2313
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
24-
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
14+
import type { OnUpdateSettingsProps } from "../types";
15+
import {
16+
getDefaultRunBehaviorOptionWhenFeatureFlagIsDisabled,
17+
getRunBehaviorOptionsBasedOnFeatureFlags,
18+
} from "./utils";
2519

2620
const OptionLabel = styled(Text)`
2721
color: var(--ads-v2-color-fg);
@@ -61,20 +55,31 @@ const FunctionSettingRow = (props: FunctionSettingsRowProps) => {
6155
const isReactiveActionsEnabled = useFeatureFlag(
6256
FEATURE_FLAG.release_reactive_actions_enabled,
6357
);
64-
const options = RUN_BEHAVIOR_VALUES.filter(
65-
(option) =>
66-
isReactiveActionsEnabled || option.value !== ActionRunBehaviour.AUTOMATIC,
67-
) as SelectOptionProps[];
58+
const isOnPageUnloadEnabled = useFeatureFlag(
59+
FEATURE_FLAG.release_jsobjects_onpageunloadactions_enabled,
60+
);
61+
62+
const options = useMemo(
63+
() =>
64+
getRunBehaviorOptionsBasedOnFeatureFlags(
65+
isReactiveActionsEnabled,
66+
isOnPageUnloadEnabled,
67+
),
68+
[isReactiveActionsEnabled, isOnPageUnloadEnabled],
69+
);
70+
6871
let selectedValue = options.find((opt) => opt.value === runBehaviour);
6972

70-
/* temporary check added to switch from automatic to page load as the run behaviour when feature flag is turned off */
71-
if (
72-
runBehaviour === ActionRunBehaviour.AUTOMATIC &&
73-
!isReactiveActionsEnabled
74-
) {
75-
selectedValue = options.find(
76-
(opt) => opt.value === ActionRunBehaviour.ON_PAGE_LOAD,
73+
const defaultRunBehaviourOption =
74+
getDefaultRunBehaviorOptionWhenFeatureFlagIsDisabled(
75+
runBehaviour,
76+
isReactiveActionsEnabled,
77+
isOnPageUnloadEnabled,
78+
options,
7779
);
80+
81+
if (defaultRunBehaviourOption) {
82+
selectedValue = defaultRunBehaviourOption;
7883
}
7984

8085
const onSelectOptions = useCallback(
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import {
2+
ActionRunBehaviour,
3+
type ActionRunBehaviourType,
4+
} from "PluginActionEditor/types/PluginActionTypes";
5+
import {
6+
getDefaultRunBehaviorOptionWhenFeatureFlagIsDisabled,
7+
getRunBehaviorOptionsBasedOnFeatureFlags,
8+
} from "./utils";
9+
import { JS_OBJECT_RUN_BEHAVIOR_VALUES } from "constants/AppsmithActionConstants/formConfig/PluginSettings";
10+
import type { SelectOptionProps } from "@appsmith/ads";
11+
12+
describe("getRunBehaviorOptions", () => {
13+
it("should return the correct options", () => {
14+
const flagsOutputMatrix: {
15+
isReactiveActionsEnabled: boolean;
16+
isOnPageUnloadEnabled: boolean;
17+
expectedOptions: SelectOptionProps[];
18+
}[] = [
19+
{
20+
isReactiveActionsEnabled: true,
21+
isOnPageUnloadEnabled: true,
22+
expectedOptions: JS_OBJECT_RUN_BEHAVIOR_VALUES,
23+
},
24+
{
25+
isReactiveActionsEnabled: false,
26+
isOnPageUnloadEnabled: true,
27+
expectedOptions: JS_OBJECT_RUN_BEHAVIOR_VALUES.filter(
28+
(option) => option.value !== ActionRunBehaviour.AUTOMATIC,
29+
),
30+
},
31+
{
32+
isReactiveActionsEnabled: true,
33+
isOnPageUnloadEnabled: false,
34+
expectedOptions: JS_OBJECT_RUN_BEHAVIOR_VALUES.filter(
35+
(option) => option.value !== ActionRunBehaviour.ON_PAGE_UNLOAD,
36+
),
37+
},
38+
{
39+
isReactiveActionsEnabled: false,
40+
isOnPageUnloadEnabled: false,
41+
expectedOptions: JS_OBJECT_RUN_BEHAVIOR_VALUES.filter(
42+
(option) =>
43+
option.value !== ActionRunBehaviour.AUTOMATIC &&
44+
option.value !== ActionRunBehaviour.ON_PAGE_UNLOAD,
45+
),
46+
},
47+
];
48+
49+
flagsOutputMatrix.forEach(
50+
({
51+
expectedOptions,
52+
isOnPageUnloadEnabled,
53+
isReactiveActionsEnabled,
54+
}) => {
55+
const options = getRunBehaviorOptionsBasedOnFeatureFlags(
56+
isReactiveActionsEnabled,
57+
isOnPageUnloadEnabled,
58+
);
59+
60+
expect(options).toEqual(expectedOptions);
61+
},
62+
);
63+
});
64+
});
65+
66+
describe("getDefaultRunBehaviorOptionWhenFeatureFlagIsDisabled", () => {
67+
const onPageLoadOption =
68+
JS_OBJECT_RUN_BEHAVIOR_VALUES.find(
69+
(option) => option.value === ActionRunBehaviour.ON_PAGE_LOAD,
70+
) ?? null;
71+
const manualOption =
72+
JS_OBJECT_RUN_BEHAVIOR_VALUES.find(
73+
(option) => option.value === ActionRunBehaviour.MANUAL,
74+
) ?? null;
75+
76+
const flagsOutputMatrix: {
77+
runBehaviour: ActionRunBehaviourType;
78+
isReactiveActionsEnabled: boolean;
79+
isOnPageUnloadEnabled: boolean;
80+
expectedOption: SelectOptionProps | null;
81+
}[] = [
82+
{
83+
runBehaviour: ActionRunBehaviour.AUTOMATIC,
84+
isReactiveActionsEnabled: true,
85+
isOnPageUnloadEnabled: true,
86+
expectedOption: null,
87+
},
88+
{
89+
runBehaviour: ActionRunBehaviour.AUTOMATIC,
90+
isReactiveActionsEnabled: false,
91+
isOnPageUnloadEnabled: true,
92+
expectedOption: onPageLoadOption,
93+
},
94+
{
95+
runBehaviour: ActionRunBehaviour.AUTOMATIC,
96+
isReactiveActionsEnabled: true,
97+
isOnPageUnloadEnabled: false,
98+
expectedOption: null,
99+
},
100+
{
101+
runBehaviour: ActionRunBehaviour.AUTOMATIC,
102+
isReactiveActionsEnabled: false,
103+
isOnPageUnloadEnabled: false,
104+
expectedOption: onPageLoadOption,
105+
},
106+
{
107+
runBehaviour: ActionRunBehaviour.ON_PAGE_UNLOAD,
108+
isReactiveActionsEnabled: true,
109+
isOnPageUnloadEnabled: true,
110+
expectedOption: null,
111+
},
112+
{
113+
runBehaviour: ActionRunBehaviour.ON_PAGE_UNLOAD,
114+
isReactiveActionsEnabled: false,
115+
isOnPageUnloadEnabled: true,
116+
expectedOption: null,
117+
},
118+
{
119+
runBehaviour: ActionRunBehaviour.ON_PAGE_UNLOAD,
120+
isReactiveActionsEnabled: true,
121+
isOnPageUnloadEnabled: false,
122+
expectedOption: manualOption,
123+
},
124+
{
125+
runBehaviour: ActionRunBehaviour.ON_PAGE_UNLOAD,
126+
isReactiveActionsEnabled: false,
127+
isOnPageUnloadEnabled: false,
128+
expectedOption: manualOption,
129+
},
130+
];
131+
132+
it("should return the correct options", () => {
133+
flagsOutputMatrix.forEach(
134+
({
135+
expectedOption,
136+
isOnPageUnloadEnabled,
137+
isReactiveActionsEnabled,
138+
runBehaviour,
139+
}) => {
140+
const option = getDefaultRunBehaviorOptionWhenFeatureFlagIsDisabled(
141+
runBehaviour,
142+
isReactiveActionsEnabled,
143+
isOnPageUnloadEnabled,
144+
JS_OBJECT_RUN_BEHAVIOR_VALUES,
145+
);
146+
147+
expect(option).toEqual(expectedOption);
148+
},
149+
);
150+
});
151+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
ActionRunBehaviour,
3+
type ActionRunBehaviourType,
4+
} from "PluginActionEditor/types/PluginActionTypes";
5+
import { JS_OBJECT_RUN_BEHAVIOR_VALUES } from "constants/AppsmithActionConstants/formConfig/PluginSettings";
6+
7+
import { type SelectOptionProps } from "@appsmith/ads";
8+
/**
9+
* Returns the list of run behavior options based on the current feature flags.
10+
* - If reactive actions are disabled, removes AUTOMATIC.
11+
* - If onPageUnload is disabled, removes ON_PAGE_UNLOAD.
12+
*/
13+
export const getRunBehaviorOptionsBasedOnFeatureFlags = (
14+
isReactiveActionsEnabled: boolean,
15+
isOnPageUnloadEnabled: boolean,
16+
) =>
17+
JS_OBJECT_RUN_BEHAVIOR_VALUES.filter(
18+
(option) =>
19+
(isReactiveActionsEnabled ||
20+
option.value !== ActionRunBehaviour.AUTOMATIC) &&
21+
(isOnPageUnloadEnabled ||
22+
option.value !== ActionRunBehaviour.ON_PAGE_UNLOAD),
23+
) as SelectOptionProps[];
24+
25+
/**
26+
* Returns the default run behavior option if the current one is no longer available due to feature flag changes.
27+
* - AUTOMATIC falls back to ON_PAGE_LOAD if reactive actions are disabled.
28+
* - ON_PAGE_UNLOAD falls back to MANUAL if onPageUnload is disabled.
29+
*/
30+
export const getDefaultRunBehaviorOptionWhenFeatureFlagIsDisabled = (
31+
runBehaviour: ActionRunBehaviourType,
32+
isReactiveActionsEnabled: boolean,
33+
isOnPageUnloadEnabled: boolean,
34+
options: SelectOptionProps[],
35+
): SelectOptionProps | null => {
36+
if (
37+
runBehaviour === ActionRunBehaviour.AUTOMATIC &&
38+
!isReactiveActionsEnabled
39+
) {
40+
return (
41+
options.find((opt) => opt.value === ActionRunBehaviour.ON_PAGE_LOAD) ??
42+
null
43+
);
44+
}
45+
46+
if (
47+
runBehaviour === ActionRunBehaviour.ON_PAGE_UNLOAD &&
48+
!isOnPageUnloadEnabled
49+
) {
50+
return (
51+
options.find((opt) => opt.value === ActionRunBehaviour.MANUAL) ?? null
52+
);
53+
}
54+
55+
return null;
56+
};

0 commit comments

Comments
 (0)