* added multi select back * (WIP): Complete the dynamc height update logic * (WIP): Dynamic height logic * (WIP): Container computation logic, Next steps: Prevent reflow when resize is disabled. Fix logic of widgets randomly changing positions (Debug) * Fix logic in container computations * Integrate for PoC * fixed the no initial load dynamic height updates * Stop vertical resize and reflow when dynamic height is enabled for a widget * added another container in text widget * enabled dynamic height for container widgets * removed dynamic height feature from list widget * Fixed Button and Input components height increase * added an experiment to overflow the content if maxHEight is less * removed the ref of Textwidget by mistake, added it back * fixed text widget height overflow problem with a little hack * added long labels with text * fixed the table scroll issue * overflow fixed for json form widget * added extra 8px height for Switch, Rating and Checkbox Height * (WIP): Resolve issues * (WIP): Fix widget padding issue * added overflow container for Radio and Switch group widgets * (WIP): Have modals work with dynamic height * added the overlay and the handles * added dragging behavior to the dots * fixed the overlapping with the selection tool * (WIP): Fix issues reported * now we can update the property pane values back from overlay handles * now we can update the property pane values back from overlay handles * (WIP): Fix table widget * Fix package.json * Remove unit tests temporarily * Fix unit test * (WIP): Fix modal resize. Fix cursors. Fix border issue on non-resizable widgets * fetch component heights using the requestAnimationFrame callback * behavioural changes * (WIP): Fix issues on the platform * Update main container size appropriately * more behavioural changes * overlay now only be visible when hovering over the dots * grid showing and widget reselecting * added onfocus and onblur events to property pane listeners * added onfocus and onblur events to property pane listeners * added a range slider for min and max * added demarcations for slider values * (WIP): Fix platform workflows for dynamic height * Fix issues with widgets * Fix removed import * - Add missing cypress files * set the limits * limit increase on change * Fix z-index of min max limit indicators. Fix unused-vars warnings * Fix Table Widget and Text Widget issues * Fix: all the bugs in the bug master list for DH (#16268) * changed the zindex for the signifiers * showing signifiers only when the widget is selected * made changes suggested by Momcilo * activate the dots when the fields are active * created a new centered dot handle * removed overlays on focus and made the border more like deisgn * handles on top of other widgets * hide the overlay when multiple widgets are selected * added a white border * added a white border * bug #15509 resolved * changed the minDynamicHeightLimit to 2 instead of 4 to fix the Bug #15527 * removed the height auto fix from BaseInputComponent to fix the Bug #15388 * removed the condition to not ccalculate dynamic height when the row difference is less than 2 to fix the bug 15353 * made fixes for the bug #16307 * made fixes for the bug #16308 * made fixes for bug 16310 * made fixes for the bug #16402 * removed some log statements * made fixes for the bug #16407 * fixed label problem found in the issue #16543 * made fixes for the issue #16547 * made fixes for the bug #16492 * redeploy * (WIP): Fix to make this branch functional * imported LabelWithTooltip back from design system * signifier is now centered * filled the signifier with primary color * overlay hidden while dragging * made the signifier dashed border also draggable * Fix issue #16590 (#16798) * set the limits to 4 rows * replaced the static 40 value * added signifiers for modal widget * added signifiers for modal widget * tried solving the scroll issue for widgets when there are limits * solved the height problem using ResizeObserver * (WIP): Fix maxDynamicHeight issue with container widgets: * made the changes as per the review * fixed the issue for input widget when label gets out of border * hide text widget overflow options if auto height is enabled * (WIP): In view mode, invisible widgets now donot take space (#16920) * (WIP): In view mode, invisible widgets now donot take space * (WIP): Enable the feature where invisible widgets in view mode don't take space to all widgets irrespective of the dynamic height feature * Remove Replay conditional * removed the scroll container for container type widgets * removed the scroll container for container type widgets * updated the hook to set overflow none for text widget * fixed the should dynamic height logic to respect the min height limit * Modal widget adheres to dynamic height (#16995) * Modal widget adheres to dynamic height * WIP: POC: fix dynamic height issues (#16996) Fix height less than 4 issue. Fix JSONForm adherence to min and max height * POC: Dynamic height undo redo issue (#17085) * Revert debouce timeout * (WIP): Fix issue with undo-redo in dynamic height * fix: Dynamic height issue fixes (#17153) * Dynamic height issue fixes == - Fix issue where nested widgets did not ensure parent dynamic height updates - Fix issue where Modal widget updates came in subsequent renders - Fix issue where JSONForm collapses - Fix performance issue for independent updates * Use functions to get min and max dynamic height * Fix issue where variable might have been undefined * added the dynamic container into the deploy mode as well * added overflow-x hidden when overflow-y is active in the dynamic height container * fix: Dynamic height Issue fixes (#17204) Fix preview mode invisible widgets. Fix Tabs widget dynamic height. * removed a console.log statement * removed the slider control file * imported the LabelWithTooltip from the repo rather than ds * word-break CSS rules added for Switch and Checkbox widget when Dynamic Height is enabled * abstracted the check for dynamic height with limits enabled as isDynamicHeightWithLimitsEnabledForWidget * abstracted the static value of 10 in dynamic height overlay to GridDefaults * abstracted min and max dynamic height limits to getters * fix: replaced all the refs for simpler widgets (#17353) * replaced all the refs for simpler widgets * removed the updateDynamicHeight from componentDidUpdate in BaseWidget * added back lifecycle methods back to BaseWidget * removed the contentRef from SwitchGroup and Table * updating the height from the auto height with limits as well * some hacks to make the limits work * working solution * used setTimeout to send an update to updateDynamicHeight from overlay update * removed a log * added requestanimationframe in settimeout Co-authored-by: Ankur Singhal <ankursinghal@Ankurs-MacBook-Pro-2.local> Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * Fix issues caused during merge * Remove unneeded derived property * removed more unnecessary code which should have been removed after removing the ref dependency * fixed the maxDynamicHeight issue * Fix issue where property configs were not being sent * fix: Auto Height Feature - add selectors for tests (#17687) Add selectors for auto height cypress tests * fix: removed height auto default theme (#17415) removed height auto css rule from the default theme Co-authored-by: Ankur Singhal <ankursinghal@Ankurs-MacBook-Pro-2.local> * fix: Auto Height Feature - Resolve issues and restructure code (#17686) * Fix issues in dynamic height. Restructure code and reduce abstraction leaks * Fix typescript issues * Update based on review comments. Comment migrations, as a cyclic import is causing the jest tests to fail. * Remove unused imports * Decrease code nesting * added the base styles for the overlay like position and z-index in its styled component css * used the isDynamicHeightEnabled prop to set the height of SwitchGroup and RadioGroup widgets from 32px to 100% in case of inline mode * fix: Auto Height - Resolve issues (#17737) * Fix Tabs Widget showTabs toggle based auto height. Revert removal of BaseWidget code. Remove box-intersect and use a bruteforce algorithm. Add base logic for having containers collapse due to hidden child widgets * Hide scroll contents and overflow property pane controls when dynamic height is enabled * Removed the class property expectedHeight from BaseWidget as it is not useful in the overlay logic after some changes * fixed the left alignment issue of label in the rich text editor by adding some styles applied only when the dynamic height is enabled * fixed the input field stretching issue in case of Dynamic height by adding some CSS styles when isDynamicHeight is true * Fix failing modal widget cypress tests * Fix issue with scrollContents and Tabs Widget defaulTab * added a little bit padding of 4px to the right of scroll container of dynamic height with limit * Add test locators for resize handles * removed the dynamic height logic from the table widget * fix: Auto-Height invisible widgets (#17849) * Fix issue where invisible widgets were still taking space * Make sure to collapse only if dynamic height is enabled * Fix issues with reflow (not the invisible widgets) * Fix container min height issues * Fix reflow with original bottom and top values. Testing needed * Fix invisible widgets * fix: enabled dynamic height for stat box widget (#17971) enabled dynamic height for stat box widget Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: added a min height to rich text editor so that it does not collapse (#17970) added a min height to rich text editor so that it does not collapse Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * Fix issue with resizing auto height widget * Add helper text to educate users regarding the scroll disconnect in WYSIWYG * fix: Auto Height Fixes (#18111) AUTO HEIGHT FIXES - Fix JSONForm height discrepancy - Fix issue where widgets moved below the other - Fix droptarget height after parent container resize * fix: sliced up the DynamicHeightOverlay component a little bit (#18100) * sliced up the DynamicHeightOverlay component a little bit * more refactoring * more refactoring * used release event emitter and refactored more Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: rich text editor center alignment issue (#18142) * removed the center alignment from rich text editor * dummy commit Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: old DSL container collapse (#18160) * Fix issue where old containers from old DSLs used to collapse when auto height was enabled * Fix issue where old containers don't allow new widgets to be added when auto height is enabled, this is because the shouldScrollContents is undefined * fix: input widgets issue (#18172) fixed the auto height not working issue Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: preview deploy mode (#18174) fixed the preview and deploy mode Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height limits label intersection with handle dot (#18186) fixed the position of the limits label to the right so that it will not intersect with the handle dot Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height limits rich text editor min height (#18187) decrease the min height of the RTE so that it does not have the boundary issue with the max limit when auto height with limits is enabled Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: grammatical error in the help text (#18188) changed react to reacts in the helpText of the dynamic height property in the proeprty pane Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height tabs double scroll (#18210) solved the issue by disabling the scroll for the child canvas widget in the tabs widget Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height limits resizing (#18213) * fixed the auto height limits resizing issue * made the auto height overlay independent of isResizing and used its own property to show the grid * some more refactoring Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * dummy commit * fix: old apps container issue (#18255) filtered out the widgets which are detached from layout Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: fixing auto height in childless containers. (#18263) fixing auto height in childless containers. * task: Dynamic height reflow fixes in Branch (#18244) dynamic height reflow fixes * fix: compact label issue and min and max limits numeric input (#18282) fixed compact label issue and turned min and max limits to numeric input Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: LabelWithTooltip help icon fix * fix: NaN and min limit for min and max (#18284) * fixed compact label issue and turned min and max limits to numeric input * fixed NaN and set min to be 4 Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: validation issues for min max (#18286) * fixed compact label issue and turned min and max limits to numeric input * fixed NaN and set min to be 4 * validations start working min max Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * added a full stop to container scroll helper text * validations start working min max * dummy commit * feat: stop resizing auto height widgets vertically because of Drag n Drop Reflow (#18267) * reflow fixes * stop resizing auto height widgets vertically because of Drag n Drop Reflow * feat: Analytics for Dynamic height (#18279) * Fix canvas min height issue and invisible widgets issue and remove logs and fix issue where widgets overlapped when coming back from preview mode to edit mode * Fix issue with containers not respecting auto height and decreasing height * Fix issue with modal widget not hugging contents, and container widgets never become visible after going invisible * Fix issue where existing containers don't have correct min height for child canvas * fix: canvasLevelsReducers test (#18301) fixed the canvasLevelsReducers test Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: removed auto height min max config from widget features (#18316) removed auto height min max config from widget features Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: Fixing Modal Height updates (#18317) Fixing Modal Height updates * fix: text widget background auto height (#18319) added background color of Text widget back to the auto height container Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * test: cypress tests for auto height (#17676) * Added tests for dynamic height * updated tests for another usecase * moved locators into commonfile * updated common method * added tests for some more widgets * Added tests for jsonForm / Form widget * Updated the test * updated test for multiple text widgets * updated test with few more usecases * updated the dsl * updated tests for text change * updated tests based on new changes * updated cypress test fixes * fix: auto height container merge poc wrt release (#18334) updated the poc wrt PR already merged in the release regarding the auto height container Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: renamed auto height overlay components and added some tests (#18333) * renamed auto height overlay components and added some tests * replaced the 10 value with GridDefaults * avoiding event to reach drop target Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * updated tests * Merge all code into one branch * Fix failing AutoHeightcontainer test * fix: Fix reflow computations which were causing widget overlap (#18300) * Fix reflow computations which were causing widget overlap * Fix issues with parent container height and overlapping widgets * Remove console logs * Revert comment * Fix issues related to reflow of containers * feat: Making getEffectedBoxes a Recursive function in autoHeight Reflow (#18336) Making getEffectedBoxes a Recursive function in autoHeight Reflow * Return null for invisible widgets from withWidgetProps * Remove duplicate import Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> * Remove missed console log * fix: Label position gets deselected on selecting already selected option (#18298) * fix: Label position gets deselected on selecting the already selected value * Added migration for Currency & Phone input widgets * simplify migration function using a utility * combine conditions * Increments LATEST_PAGE_VERSION * Update DynamicHeight_Visibility_spec.js updated a check wrt auto height * Handling Modals for canvas size calculations * fix: migrate label position test failing issue (#18365) fixed migrate label postition test failing issue Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * removed the two unwanted imports from DSLMigrations to fix client build * fix: Auto height zero and limits issue (#18366) fixed the auto height zero and limits issue Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: Auto height regression issues (#18367) * Fix auto height regression issues #18367 * feat: auto height migrations (#18368) Add auto height migrations * Increase file caching size * Use manual array for list of auto height enabled widgets * Fix cypress test dsl versions * Revert changes to shouldUpdateHeightDynamically * Update test results based on code changes * Marginally increase the workbox file size cache * review comment incorporated for test spec * Update container auto height property on drop * added small wait for validation Co-authored-by: Ankur Singhal <ankur@appsmith.com> Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: Abhinav Jha <zatanna@Abhinavs-iMac.lan> Co-authored-by: Ankur Singhal <ankursinghal@Ankurs-MacBook-Pro-2.local> Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Albin <albin@appsmith.com> Co-authored-by: Aswath K <aswath.sana@gmail.com> Co-authored-by: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Co-authored-by: Apple <nandan@thinkify.io>
826 lines
22 KiB
TypeScript
826 lines
22 KiB
TypeScript
import {
|
|
actionChannel,
|
|
ActionPattern,
|
|
all,
|
|
call,
|
|
delay,
|
|
fork,
|
|
put,
|
|
select,
|
|
spawn,
|
|
take,
|
|
} from "redux-saga/effects";
|
|
|
|
import {
|
|
EvaluationReduxAction,
|
|
AnyReduxAction,
|
|
ReduxAction,
|
|
ReduxActionType,
|
|
ReduxActionTypes,
|
|
} from "@appsmith/constants/ReduxActionConstants";
|
|
import {
|
|
getDataTree,
|
|
getUnevaluatedDataTree,
|
|
} from "selectors/dataTreeSelectors";
|
|
import { getWidgets } from "sagas/selectors";
|
|
import WidgetFactory, { WidgetTypeConfigMap } from "utils/WidgetFactory";
|
|
import { GracefulWorkerService } from "utils/WorkerUtil";
|
|
import {
|
|
EvalError,
|
|
EVAL_WORKER_ACTIONS,
|
|
PropertyEvaluationErrorType,
|
|
} from "utils/DynamicBindingUtils";
|
|
import log from "loglevel";
|
|
import { WidgetProps } from "widgets/BaseWidget";
|
|
import PerformanceTracker, {
|
|
PerformanceTransactionName,
|
|
} from "utils/PerformanceTracker";
|
|
import * as Sentry from "@sentry/react";
|
|
import { Action } from "redux";
|
|
import {
|
|
EVALUATE_REDUX_ACTIONS,
|
|
FIRST_EVAL_REDUX_ACTIONS,
|
|
setDependencyMap,
|
|
setEvaluatedTree,
|
|
shouldLint,
|
|
shouldProcessBatchedAction,
|
|
} from "actions/evaluationActions";
|
|
import {
|
|
evalErrorHandler,
|
|
handleJSFunctionExecutionErrorLog,
|
|
logSuccessfulBindings,
|
|
postEvalActionDispatcher,
|
|
updateTernDefinitions,
|
|
} from "./PostEvaluationSagas";
|
|
import { JSAction } from "entities/JSCollection";
|
|
import { getAppMode } from "selectors/applicationSelectors";
|
|
import { APP_MODE } from "entities/App";
|
|
import { get, isUndefined } from "lodash";
|
|
import {
|
|
setEvaluatedArgument,
|
|
setEvaluatedSnippet,
|
|
setGlobalSearchFilterContext,
|
|
} from "actions/globalSearchActions";
|
|
import {
|
|
executeActionTriggers,
|
|
TriggerMeta,
|
|
} from "./ActionExecution/ActionExecutionSagas";
|
|
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
|
import { Toaster, Variant } from "design-system";
|
|
import {
|
|
createMessage,
|
|
SNIPPET_EXECUTION_FAILED,
|
|
SNIPPET_EXECUTION_SUCCESS,
|
|
} from "@appsmith/constants/messages";
|
|
import { validate } from "workers/Evaluation/validations";
|
|
import { diff } from "deep-diff";
|
|
import { REPLAY_DELAY } from "entities/Replay/replayUtils";
|
|
import { EvaluationVersion } from "api/ApplicationApi";
|
|
import { makeUpdateJSCollection } from "sagas/JSPaneSagas";
|
|
import {
|
|
ENTITY_TYPE,
|
|
LogObject,
|
|
UserLogObject,
|
|
} from "entities/AppsmithConsole";
|
|
import { Replayable } from "entities/Replay/ReplayEntity/ReplayEditor";
|
|
import {
|
|
logActionExecutionError,
|
|
UncaughtPromiseError,
|
|
} from "sagas/ActionExecution/errorUtils";
|
|
import { Channel } from "redux-saga";
|
|
import { ActionDescription } from "entities/DataTree/actionTriggers";
|
|
import { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer";
|
|
import { FormEvalActionPayload } from "./FormEvaluationSaga";
|
|
import { getSelectedAppTheme } from "selectors/appThemingSelectors";
|
|
import { updateMetaState } from "actions/metaActions";
|
|
import { getAllActionValidationConfig } from "selectors/entitiesSelector";
|
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
|
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
|
import { AppTheme } from "entities/AppTheming";
|
|
import { ActionValidationConfigMap } from "constants/PropertyControlConstants";
|
|
import { storeLogs, updateTriggerMeta } from "./DebuggerSagas";
|
|
import { lintTreeSaga, lintWorker } from "./LintingSagas";
|
|
import {
|
|
EvalTreeRequestData,
|
|
EvalTreeResponseData,
|
|
} from "workers/Evaluation/types";
|
|
|
|
const evalWorker = new GracefulWorkerService(
|
|
new Worker(
|
|
new URL("../workers/Evaluation/evaluation.worker.ts", import.meta.url),
|
|
{
|
|
type: "module",
|
|
name: "evalWorker",
|
|
},
|
|
),
|
|
);
|
|
|
|
let widgetTypeConfigMap: WidgetTypeConfigMap;
|
|
|
|
function* evaluateTreeSaga(
|
|
postEvalActions?: Array<AnyReduxAction>,
|
|
shouldReplay = true,
|
|
requiresLinting = false,
|
|
) {
|
|
const allActionValidationConfig: {
|
|
[actionId: string]: ActionValidationConfigMap;
|
|
} = yield select(getAllActionValidationConfig);
|
|
const unevalTree: DataTree = yield select(getUnevaluatedDataTree);
|
|
const widgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
const theme: AppTheme = yield select(getSelectedAppTheme);
|
|
const appMode: APP_MODE | undefined = yield select(getAppMode);
|
|
const isEditMode = appMode === APP_MODE.EDIT;
|
|
log.debug({ unevalTree });
|
|
PerformanceTracker.startAsyncTracking(
|
|
PerformanceTransactionName.DATA_TREE_EVALUATION,
|
|
);
|
|
const evalTreeRequestData: EvalTreeRequestData = {
|
|
unevalTree,
|
|
widgetTypeConfigMap,
|
|
widgets,
|
|
theme,
|
|
shouldReplay,
|
|
allActionValidationConfig,
|
|
requiresLinting: isEditMode && requiresLinting,
|
|
};
|
|
|
|
const workerResponse: EvalTreeResponseData = yield call(
|
|
evalWorker.request,
|
|
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
|
evalTreeRequestData,
|
|
);
|
|
|
|
const {
|
|
dataTree,
|
|
dependencies,
|
|
errors,
|
|
evalMetaUpdates = [],
|
|
evaluationOrder,
|
|
jsUpdates,
|
|
logs,
|
|
userLogs,
|
|
unEvalUpdates,
|
|
isCreateFirstTree = false,
|
|
} = workerResponse;
|
|
PerformanceTracker.stopAsyncTracking(
|
|
PerformanceTransactionName.DATA_TREE_EVALUATION,
|
|
);
|
|
PerformanceTracker.startAsyncTracking(
|
|
PerformanceTransactionName.SET_EVALUATED_TREE,
|
|
);
|
|
const oldDataTree: DataTree = yield select(getDataTree);
|
|
|
|
const updates = diff(oldDataTree, dataTree) || [];
|
|
|
|
yield put(setEvaluatedTree(updates));
|
|
PerformanceTracker.stopAsyncTracking(
|
|
PerformanceTransactionName.SET_EVALUATED_TREE,
|
|
);
|
|
// if evalMetaUpdates are present only then dispatch updateMetaState
|
|
if (evalMetaUpdates.length) {
|
|
yield put(updateMetaState(evalMetaUpdates));
|
|
}
|
|
log.debug({ evalMetaUpdatesLength: evalMetaUpdates.length });
|
|
|
|
const updatedDataTree: DataTree = yield select(getDataTree);
|
|
if (
|
|
!(!isCreateFirstTree && Object.keys(jsUpdates).length > 0) &&
|
|
!!userLogs &&
|
|
userLogs.length > 0
|
|
) {
|
|
yield all(
|
|
userLogs.map((log: UserLogObject) => {
|
|
return call(
|
|
storeLogs,
|
|
log.logObject,
|
|
log.source.name,
|
|
log.source.type,
|
|
log.source.id,
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
log.debug({ jsUpdates: jsUpdates });
|
|
log.debug({ dataTree: updatedDataTree });
|
|
logs?.forEach((evalLog: any) => log.debug(evalLog));
|
|
// Added type as any due to https://github.com/redux-saga/redux-saga/issues/1482
|
|
yield call(evalErrorHandler as any, errors, updatedDataTree, evaluationOrder);
|
|
|
|
if (appMode !== APP_MODE.PUBLISHED) {
|
|
yield call(makeUpdateJSCollection, jsUpdates);
|
|
yield fork(
|
|
logSuccessfulBindings,
|
|
unevalTree,
|
|
updatedDataTree,
|
|
evaluationOrder,
|
|
isCreateFirstTree,
|
|
);
|
|
|
|
yield fork(updateTernDefinitions, updatedDataTree, unEvalUpdates);
|
|
}
|
|
yield put(setDependencyMap(dependencies));
|
|
if (postEvalActions && postEvalActions.length) {
|
|
yield call(postEvalActionDispatcher, postEvalActions);
|
|
}
|
|
}
|
|
|
|
export function* evaluateActionBindings(
|
|
bindings: string[],
|
|
executionParams: Record<string, any> | string = {},
|
|
) {
|
|
const workerResponse: { errors: EvalError[]; values: unknown } = yield call(
|
|
evalWorker.request,
|
|
EVAL_WORKER_ACTIONS.EVAL_ACTION_BINDINGS,
|
|
{
|
|
bindings,
|
|
executionParams,
|
|
},
|
|
);
|
|
|
|
const { errors, values } = workerResponse;
|
|
|
|
yield call(evalErrorHandler, errors);
|
|
return values;
|
|
}
|
|
|
|
/*
|
|
* Used to evaluate and execute dynamic trigger end to end
|
|
* Widget action fields and JS Object run triggers this flow
|
|
*
|
|
* We start a duplex request with the worker and wait till the time we get a 'finished' event from the
|
|
* worker. Worker will evaluate a block of code and ask the main thread to execute it. The result of this
|
|
* execution is returned to the worker where it can resolve/reject the current promise.
|
|
*/
|
|
|
|
export function* evaluateAndExecuteDynamicTrigger(
|
|
dynamicTrigger: string,
|
|
eventType: EventType,
|
|
triggerMeta: TriggerMeta,
|
|
callbackData?: Array<any>,
|
|
globalContext?: Record<string, unknown>,
|
|
) {
|
|
const unEvalTree: DataTree = yield select(getUnevaluatedDataTree);
|
|
log.debug({ execute: dynamicTrigger });
|
|
const { isFinishedChannel } = yield call(
|
|
evalWorker.duplexRequest,
|
|
EVAL_WORKER_ACTIONS.EVAL_TRIGGER,
|
|
{
|
|
dataTree: unEvalTree,
|
|
dynamicTrigger,
|
|
callbackData,
|
|
globalContext,
|
|
eventType,
|
|
triggerMeta,
|
|
},
|
|
);
|
|
|
|
let keepAlive = true;
|
|
|
|
while (keepAlive) {
|
|
const { requestData } = yield take(isFinishedChannel);
|
|
log.debug({ requestData, eventType, triggerMeta, dynamicTrigger });
|
|
|
|
if (requestData.finished) {
|
|
keepAlive = false;
|
|
|
|
const { result } = requestData;
|
|
yield call(updateTriggerMeta, triggerMeta, dynamicTrigger);
|
|
|
|
// Check for any logs in the response and store them in the redux store
|
|
if (
|
|
!!result &&
|
|
result.hasOwnProperty("logs") &&
|
|
!!result.logs &&
|
|
result.logs.length
|
|
) {
|
|
yield call(
|
|
storeLogs,
|
|
result.logs,
|
|
triggerMeta.source?.name || triggerMeta.triggerPropertyName || "",
|
|
eventType === EventType.ON_JS_FUNCTION_EXECUTE
|
|
? ENTITY_TYPE.JSACTION
|
|
: ENTITY_TYPE.WIDGET,
|
|
triggerMeta.source?.id || "",
|
|
);
|
|
}
|
|
|
|
/* Handle errors during evaluation
|
|
* A finish event with errors means that the error was not caught by the user code.
|
|
* We raise an error telling the user that an uncaught error has occurred
|
|
* */
|
|
if (
|
|
!!result &&
|
|
result.hasOwnProperty("errors") &&
|
|
!!result.errors &&
|
|
result.errors.length
|
|
) {
|
|
if (
|
|
result.errors[0].errorMessage !==
|
|
"UncaughtPromiseRejection: User cancelled action execution"
|
|
) {
|
|
throw new UncaughtPromiseError(result.errors[0].errorMessage);
|
|
}
|
|
}
|
|
|
|
// It is possible to get a few triggers here if the user
|
|
// still uses the old way of action runs and not promises. For that we
|
|
// need to manually execute these triggers outside the promise flow
|
|
const { triggers } = result;
|
|
if (triggers && triggers.length) {
|
|
log.debug({ triggers });
|
|
yield all(
|
|
triggers.map((trigger: ActionDescription) =>
|
|
call(executeActionTriggers, trigger, eventType, triggerMeta),
|
|
),
|
|
);
|
|
}
|
|
// Return value of a promise is returned
|
|
isFinishedChannel.close();
|
|
return result;
|
|
}
|
|
yield call(evalErrorHandler, requestData.errors);
|
|
isFinishedChannel.close();
|
|
}
|
|
}
|
|
|
|
export function* executeDynamicTriggerRequest(
|
|
mainThreadRequestChannel: Channel<any>,
|
|
) {
|
|
while (true) {
|
|
const { mainThreadResponseChannel, requestData, requestId } = yield take(
|
|
mainThreadRequestChannel,
|
|
);
|
|
log.debug({ requestData });
|
|
if (requestData?.logs) {
|
|
const { eventType, triggerMeta } = requestData;
|
|
yield call(
|
|
storeLogs,
|
|
requestData.logs,
|
|
triggerMeta?.source?.name || triggerMeta?.triggerPropertyName || "",
|
|
eventType === EventType.ON_JS_FUNCTION_EXECUTE
|
|
? ENTITY_TYPE.JSACTION
|
|
: ENTITY_TYPE.WIDGET,
|
|
triggerMeta?.source?.id || "",
|
|
);
|
|
}
|
|
if (requestData?.trigger) {
|
|
// if we have found a trigger, we need to execute it and respond back
|
|
log.debug({ trigger: requestData.trigger });
|
|
yield spawn(
|
|
executeTriggerRequestSaga,
|
|
requestId,
|
|
requestData,
|
|
requestData.eventType,
|
|
mainThreadResponseChannel,
|
|
requestData.triggerMeta,
|
|
);
|
|
}
|
|
if (requestData.type === EVAL_WORKER_ACTIONS.LINT_TREE) {
|
|
yield spawn(lintTreeSaga, {
|
|
pathsToLint: requestData.lintOrder,
|
|
jsUpdates: requestData.jsUpdates,
|
|
unevalTree: requestData.unevalTree,
|
|
});
|
|
}
|
|
if (requestData?.errors) {
|
|
yield call(evalErrorHandler, requestData.errors);
|
|
}
|
|
}
|
|
}
|
|
|
|
interface ResponsePayload {
|
|
data: {
|
|
subRequestId: string;
|
|
reason?: string;
|
|
resolve?: unknown;
|
|
};
|
|
success: boolean;
|
|
eventType?: EventType;
|
|
}
|
|
|
|
/*
|
|
* It is necessary to respond back as the worker is waiting with a pending promise and wanting to know if it should
|
|
* resolve or reject it with the data the execution has provided
|
|
*/
|
|
function* executeTriggerRequestSaga(
|
|
requestId: string,
|
|
requestData: { trigger: ActionDescription; subRequestId: string },
|
|
eventType: EventType,
|
|
responseFromExecutionChannel: Channel<unknown>,
|
|
triggerMeta: TriggerMeta,
|
|
) {
|
|
const responsePayload: ResponsePayload = {
|
|
data: {
|
|
resolve: undefined,
|
|
reason: undefined,
|
|
subRequestId: requestData.subRequestId,
|
|
},
|
|
success: false,
|
|
eventType,
|
|
};
|
|
try {
|
|
responsePayload.data.resolve = yield call(
|
|
executeActionTriggers,
|
|
requestData.trigger,
|
|
eventType,
|
|
triggerMeta,
|
|
);
|
|
responsePayload.success = true;
|
|
} catch (error) {
|
|
// When error occurs in execution of triggers,
|
|
// a success: false is sent to reject the promise
|
|
|
|
// @ts-expect-error: reason is of type string
|
|
responsePayload.data.reason = { message: error.message };
|
|
responsePayload.success = false;
|
|
}
|
|
responseFromExecutionChannel.put({
|
|
method: EVAL_WORKER_ACTIONS.PROCESS_TRIGGER,
|
|
requestId: requestId,
|
|
...responsePayload,
|
|
});
|
|
}
|
|
|
|
export function* clearEvalCache() {
|
|
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.CLEAR_CACHE);
|
|
|
|
return true;
|
|
}
|
|
|
|
export function* executeFunction(
|
|
collectionName: string,
|
|
action: JSAction,
|
|
collectionId: string,
|
|
) {
|
|
const functionCall = `${collectionName}.${action.name}()`;
|
|
const { isAsync } = action.actionConfiguration;
|
|
let response: {
|
|
errors: any[];
|
|
result: any;
|
|
logs?: LogObject[];
|
|
};
|
|
|
|
if (isAsync) {
|
|
try {
|
|
response = yield call(
|
|
evaluateAndExecuteDynamicTrigger,
|
|
functionCall,
|
|
EventType.ON_JS_FUNCTION_EXECUTE,
|
|
{
|
|
source: {
|
|
id: collectionId,
|
|
name: `${collectionName}.${action.name}`,
|
|
},
|
|
triggerPropertyName: `${collectionName}.${action.name}`,
|
|
},
|
|
);
|
|
} catch (e) {
|
|
if (e instanceof UncaughtPromiseError) {
|
|
logActionExecutionError(e.message);
|
|
}
|
|
response = { errors: [e], result: undefined };
|
|
}
|
|
} else {
|
|
response = yield call(
|
|
evalWorker.request,
|
|
EVAL_WORKER_ACTIONS.EXECUTE_SYNC_JS,
|
|
{
|
|
functionCall,
|
|
},
|
|
);
|
|
|
|
const { logs } = response;
|
|
// Check for any logs in the response and store them in the redux store
|
|
if (!!logs && logs.length > 0) {
|
|
yield call(
|
|
storeLogs,
|
|
logs,
|
|
collectionName + "." + action.name,
|
|
ENTITY_TYPE.JSACTION,
|
|
collectionId,
|
|
);
|
|
}
|
|
}
|
|
|
|
const { errors, result } = response;
|
|
|
|
const isDirty = !!errors.length;
|
|
|
|
yield call(
|
|
handleJSFunctionExecutionErrorLog,
|
|
collectionId,
|
|
collectionName,
|
|
action,
|
|
errors,
|
|
);
|
|
return { result, isDirty };
|
|
}
|
|
|
|
export function* validateProperty(
|
|
property: string,
|
|
value: any,
|
|
props: WidgetProps,
|
|
) {
|
|
const unevalTree: DataTree = yield select(getUnevaluatedDataTree);
|
|
// @ts-expect-error: We have a typeMismatch for validationPaths
|
|
const validation = unevalTree[props.widgetName].validationPaths[property];
|
|
const response: unknown = yield call(
|
|
evalWorker.request,
|
|
EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY,
|
|
{
|
|
property,
|
|
value,
|
|
props,
|
|
validation,
|
|
},
|
|
);
|
|
|
|
return response;
|
|
}
|
|
|
|
function evalQueueBuffer() {
|
|
let canTake = false;
|
|
let collectedPostEvalActions: any = [];
|
|
const take = () => {
|
|
if (canTake) {
|
|
const resp = collectedPostEvalActions;
|
|
collectedPostEvalActions = [];
|
|
canTake = false;
|
|
return { postEvalActions: resp, type: "BUFFERED_ACTION" };
|
|
}
|
|
};
|
|
const flush = () => {
|
|
if (canTake) {
|
|
return [take() as Action];
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
const put = (action: EvaluationReduxAction<unknown | unknown[]>) => {
|
|
if (!shouldProcessBatchedAction(action)) {
|
|
return;
|
|
}
|
|
canTake = true;
|
|
|
|
const postEvalActions = getPostEvalActions(action);
|
|
collectedPostEvalActions.push(...postEvalActions);
|
|
};
|
|
|
|
return {
|
|
take,
|
|
put,
|
|
isEmpty: () => {
|
|
return !canTake;
|
|
},
|
|
flush,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extract the post eval actions from an evaluation action
|
|
* Batched actions have post eval actions inside them, extract that
|
|
*
|
|
* **/
|
|
function getPostEvalActions(
|
|
action: EvaluationReduxAction<unknown | unknown[]>,
|
|
): AnyReduxAction[] {
|
|
const postEvalActions: AnyReduxAction[] = [];
|
|
if (action.postEvalActions) {
|
|
postEvalActions.push(...action.postEvalActions);
|
|
}
|
|
if (
|
|
action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS &&
|
|
Array.isArray(action.payload)
|
|
) {
|
|
action.payload.forEach((batchedAction) => {
|
|
if (batchedAction.postEvalActions) {
|
|
postEvalActions.push(
|
|
...(batchedAction.postEvalActions as AnyReduxAction[]),
|
|
);
|
|
}
|
|
});
|
|
}
|
|
return postEvalActions;
|
|
}
|
|
|
|
function* evaluationChangeListenerSaga() {
|
|
// Explicitly shutdown old worker if present
|
|
yield all([call(evalWorker.shutdown), call(lintWorker.shutdown)]);
|
|
const [{ mainThreadRequestChannel }] = yield all([
|
|
call(evalWorker.start),
|
|
call(lintWorker.start),
|
|
]);
|
|
|
|
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.SETUP);
|
|
yield spawn(executeDynamicTriggerRequest, mainThreadRequestChannel);
|
|
|
|
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
|
|
const initAction: {
|
|
type: ReduxActionType;
|
|
postEvalActions: Array<ReduxAction<unknown>>;
|
|
} = yield take(FIRST_EVAL_REDUX_ACTIONS);
|
|
yield fork(evaluateTreeSaga, initAction.postEvalActions, false, true);
|
|
const evtActionChannel: ActionPattern<Action<any>> = yield actionChannel(
|
|
EVALUATE_REDUX_ACTIONS,
|
|
evalQueueBuffer(),
|
|
);
|
|
while (true) {
|
|
const action: EvaluationReduxAction<unknown | unknown[]> = yield take(
|
|
evtActionChannel,
|
|
);
|
|
|
|
if (shouldProcessBatchedAction(action)) {
|
|
const postEvalActions = getPostEvalActions(action);
|
|
yield call(
|
|
evaluateTreeSaga,
|
|
postEvalActions,
|
|
get(action, "payload.shouldReplay"),
|
|
shouldLint(action),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function* evaluateSnippetSaga(action: any) {
|
|
try {
|
|
let { expression } = action.payload;
|
|
const { dataType, isTrigger } = action.payload;
|
|
if (isTrigger) {
|
|
expression = `function() { ${expression} }`;
|
|
}
|
|
const workerResponse: {
|
|
errors: any;
|
|
result: any;
|
|
triggers: any;
|
|
} = yield call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_EXPRESSION, {
|
|
expression,
|
|
dataType,
|
|
isTrigger,
|
|
});
|
|
const { errors, result, triggers } = workerResponse;
|
|
if (triggers && triggers.length > 0) {
|
|
yield all(
|
|
triggers.map((trigger: any) =>
|
|
call(
|
|
executeActionTriggers,
|
|
trigger,
|
|
EventType.ON_SNIPPET_EXECUTE,
|
|
{},
|
|
),
|
|
),
|
|
);
|
|
//Result is when trigger is present. Following code will hide the evaluated snippet section
|
|
yield put(setEvaluatedSnippet(result));
|
|
} else {
|
|
/*
|
|
JSON.stringify(undefined) is undefined.
|
|
We need to set it manually to "undefined" for codeEditor to display it.
|
|
*/
|
|
yield put(
|
|
setEvaluatedSnippet(
|
|
errors?.length
|
|
? JSON.stringify(errors, null, 2)
|
|
: isUndefined(result)
|
|
? "undefined"
|
|
: JSON.stringify(result),
|
|
),
|
|
);
|
|
}
|
|
Toaster.show({
|
|
text: createMessage(
|
|
errors?.length ? SNIPPET_EXECUTION_FAILED : SNIPPET_EXECUTION_SUCCESS,
|
|
),
|
|
variant: errors?.length ? Variant.danger : Variant.success,
|
|
});
|
|
yield put(
|
|
setGlobalSearchFilterContext({
|
|
executionInProgress: false,
|
|
}),
|
|
);
|
|
} catch (e) {
|
|
yield put(
|
|
setGlobalSearchFilterContext({
|
|
executionInProgress: false,
|
|
}),
|
|
);
|
|
Toaster.show({
|
|
text: createMessage(SNIPPET_EXECUTION_FAILED),
|
|
variant: Variant.danger,
|
|
});
|
|
log.error(e);
|
|
Sentry.captureException(e);
|
|
}
|
|
}
|
|
|
|
export function* evaluateArgumentSaga(action: any) {
|
|
const { name, type, value } = action.payload;
|
|
try {
|
|
const workerResponse: {
|
|
errors: Array<unknown>;
|
|
result: unknown;
|
|
} = yield call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_EXPRESSION, {
|
|
expression: value,
|
|
});
|
|
const lintErrors = (workerResponse.errors || []).filter(
|
|
(error: any) => error.errorType !== PropertyEvaluationErrorType.LINT,
|
|
);
|
|
if (workerResponse.result) {
|
|
const validation = validate({ type }, workerResponse.result, {}, "");
|
|
if (!validation.isValid)
|
|
validation.messages?.map((message) => {
|
|
lintErrors.unshift({
|
|
...validation,
|
|
...{
|
|
errorType: PropertyEvaluationErrorType.VALIDATION,
|
|
errorMessage: message,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
yield put(
|
|
setEvaluatedArgument({
|
|
[name]: {
|
|
type,
|
|
value: workerResponse.result,
|
|
name,
|
|
errors: lintErrors,
|
|
isInvalid: lintErrors.length > 0,
|
|
},
|
|
}),
|
|
);
|
|
} catch (e) {
|
|
log.error(e);
|
|
Sentry.captureException(e);
|
|
}
|
|
}
|
|
|
|
export function* updateReplayEntitySaga(
|
|
actionPayload: ReduxAction<{
|
|
entityId: string;
|
|
entity: Replayable;
|
|
entityType: ENTITY_TYPE;
|
|
}>,
|
|
) {
|
|
//Delay updates to replay object to not persist every keystroke
|
|
yield delay(REPLAY_DELAY);
|
|
const { entity, entityId, entityType } = actionPayload.payload;
|
|
const workerResponse: unknown = yield call(
|
|
evalWorker.request,
|
|
EVAL_WORKER_ACTIONS.UPDATE_REPLAY_OBJECT,
|
|
{
|
|
entityId,
|
|
entity,
|
|
entityType,
|
|
},
|
|
);
|
|
|
|
return workerResponse;
|
|
}
|
|
|
|
export function* workerComputeUndoRedo(operation: string, entityId: string) {
|
|
const workerResponse: unknown = yield call(evalWorker.request, operation, {
|
|
entityId,
|
|
});
|
|
return workerResponse;
|
|
}
|
|
|
|
// Type to represent the state of the evaluation reducer
|
|
export interface FormEvaluationConfig
|
|
extends ReduxAction<FormEvalActionPayload> {
|
|
currentEvalState: FormEvaluationState;
|
|
}
|
|
|
|
// Function to trigger the form eval job in the worker
|
|
export function* evalFormConfig(formEvaluationConfigObj: FormEvaluationConfig) {
|
|
const workerResponse: unknown = yield call(
|
|
evalWorker.request,
|
|
EVAL_WORKER_ACTIONS.INIT_FORM_EVAL,
|
|
formEvaluationConfigObj,
|
|
);
|
|
|
|
return workerResponse;
|
|
}
|
|
|
|
export function* setAppVersionOnWorkerSaga(action: {
|
|
type: ReduxActionType;
|
|
payload: EvaluationVersion;
|
|
}) {
|
|
const version: EvaluationVersion = action.payload;
|
|
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.SET_EVALUATION_VERSION, {
|
|
version,
|
|
});
|
|
}
|
|
|
|
export default function* evaluationSagaListeners() {
|
|
yield take(ReduxActionTypes.START_EVALUATION);
|
|
while (true) {
|
|
try {
|
|
yield call(evaluationChangeListenerSaga);
|
|
} catch (e) {
|
|
log.error(e);
|
|
Sentry.captureException(e);
|
|
}
|
|
}
|
|
}
|