From 427b0e7350ba92c40f32f273ae059551b2d103c7 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Mon, 6 Jun 2022 09:26:14 +0530 Subject: [PATCH 1/2] fix: onPageLoad false failure issue (#14141) 1. Fixed on page load actions getting called multiple times. 2. Now Evaluation only happens once we complete the fetching of the page's all entities including js objects and actions, this avoids cases where actions are not defined when ran. Co-authored-by: Rishabh-Rathod (cherry picked from commit 3bcb1daf734f10fbfdce3a0044ed4c1a0b84b4d8) --- .../cypress/fixtures/executeAction.json | 651 ++++++++++++++++++ .../PreviewMode/ExecuteAction_spec.js | 54 ++ app/client/src/actions/evaluationActions.ts | 5 +- app/client/src/actions/jsActionActions.ts | 6 + app/client/src/actions/pageActions.tsx | 25 +- app/client/src/actions/pluginActionActions.ts | 10 +- .../src/ce/constants/ReduxActionConstants.tsx | 1 + .../AppViewer/AppViewerPageContainer.tsx | 11 +- app/client/src/sagas/ActionSagas.ts | 5 +- app/client/src/sagas/InitSagas.ts | 66 +- app/client/src/sagas/PageSagas.tsx | 88 ++- 11 files changed, 818 insertions(+), 104 deletions(-) create mode 100644 app/client/cypress/fixtures/executeAction.json create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/ExecuteAction_spec.js diff --git a/app/client/cypress/fixtures/executeAction.json b/app/client/cypress/fixtures/executeAction.json new file mode 100644 index 0000000000..6be478e7e8 --- /dev/null +++ b/app/client/cypress/fixtures/executeAction.json @@ -0,0 +1,651 @@ +{ + "clientSchemaVersion": 1, + "serverSchemaVersion": 4, + "exportedApplication": { + "name": "Execute Action Test", + "isPublic": false, + "appIsExample": false, + "unreadCommentThreads": 0, + "color": "#D9E7FF", + "icon": "camera", + "slug": "execute-action-test", + "evaluationVersion": 2, + "applicationVersion": 2, + "isManualUpdate": false, + "new": true + }, + "datasourceList": [], + "pageList": [ + { + "userPermissions": [ + "read:pages", + "manage:pages" + ], + "gitSyncId": "62987c9ec43c3d0bd6572220_62987c9ec43c3d0bd6572222", + "unpublishedPage": { + "name": "Page1", + "slug": "page1", + "layouts": [ + { + "id": "Page1", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896.0, + "snapColumns": 64.0, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0.0, + "bottomRow": 1290.0, + "containerStyle": "none", + "snapRows": 125.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 59.0, + "minHeight": 1292.0, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1.0, + "dynamicBindingPathList": [], + "leftColumn": 0.0, + "children": [ + { + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 26.0, + "bottomRow": 36.0, + "parentRowSpace": 10.0, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 20.0625, + "dynamicTriggerPathList": [], + "leftColumn": 23.0, + "dynamicBindingPathList": [ + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "User count :{{Api1.data.users.length}}", + "key": "i4b3tj04fv", + "isDeprecated": false, + "rightColumn": 38.0, + "textAlign": "LEFT", + "widgetId": "feinx8nce5", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1.0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "fontSize": "1rem" + } + ] + }, + "layoutOnLoadActions": [ + [ + { + "id": "Page1_Api1", + "name": "Api1", + "confirmBeforeExecute": false, + "pluginType": "API", + "jsonPathKeys": [], + "timeoutInMillisecond": 10000 + } + ] + ], + "new": false + } + ], + "userPermissions": [] + }, + "publishedPage": { + "name": "Page1", + "slug": "page1", + "layouts": [ + { + "id": "Page1", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896.0, + "snapColumns": 64.0, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0.0, + "bottomRow": 1290.0, + "containerStyle": "none", + "snapRows": 125.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 59.0, + "minHeight": 1292.0, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1.0, + "dynamicBindingPathList": [], + "leftColumn": 0.0, + "children": [ + { + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 26.0, + "bottomRow": 36.0, + "parentRowSpace": 10.0, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 20.0625, + "dynamicTriggerPathList": [], + "leftColumn": 23.0, + "dynamicBindingPathList": [ + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "User count :{{Api1.data.users.length}}", + "key": "i4b3tj04fv", + "isDeprecated": false, + "rightColumn": 38.0, + "textAlign": "LEFT", + "widgetId": "feinx8nce5", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1.0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "fontSize": "1rem" + } + ] + }, + "layoutOnLoadActions": [ + [ + { + "id": "Page1_Api1", + "name": "Api1", + "confirmBeforeExecute": false, + "pluginType": "API", + "jsonPathKeys": [], + "timeoutInMillisecond": 10000 + } + ] + ], + "new": false + } + ], + "userPermissions": [] + }, + "new": true + }, + { + "userPermissions": [ + "read:pages", + "manage:pages" + ], + "gitSyncId": "62987c9ec43c3d0bd6572220_62987cdac43c3d0bd6572227", + "unpublishedPage": { + "name": "Page2", + "slug": "page2", + "layouts": [ + { + "id": "Page2", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224.0, + "snapColumns": 64.0, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0.0, + "bottomRow": 730.0, + "containerStyle": "none", + "snapRows": 70.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 59.0, + "minHeight": 710.0, + "parentColumnSpace": 1.0, + "dynamicBindingPathList": [], + "leftColumn": 0.0, + "children": [ + { + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 31.0, + "bottomRow": 41.0, + "parentRowSpace": 10.0, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 20.0625, + "dynamicTriggerPathList": [], + "leftColumn": 23.0, + "dynamicBindingPathList": [ + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "User count :{{Api1.data.users.length}}", + "key": "i4b3tj04fv", + "isDeprecated": false, + "rightColumn": 38.0, + "textAlign": "LEFT", + "widgetId": "31eat3ae9u", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1.0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "fontSize": "1rem" + } + ] + }, + "layoutOnLoadActions": [ + [ + { + "id": "Page2_Api1", + "name": "Api1", + "confirmBeforeExecute": false, + "pluginType": "API", + "jsonPathKeys": [], + "timeoutInMillisecond": 10000 + } + ] + ], + "new": false + } + ], + "userPermissions": [] + }, + "publishedPage": { + "name": "Page2", + "slug": "page2", + "layouts": [ + { + "id": "Page2", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224.0, + "snapColumns": 64.0, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0.0, + "bottomRow": 730.0, + "containerStyle": "none", + "snapRows": 70.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 59.0, + "minHeight": 710.0, + "parentColumnSpace": 1.0, + "dynamicBindingPathList": [], + "leftColumn": 0.0, + "children": [ + { + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 31.0, + "bottomRow": 41.0, + "parentRowSpace": 10.0, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 20.0625, + "dynamicTriggerPathList": [], + "leftColumn": 23.0, + "dynamicBindingPathList": [ + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "User count :{{Api1.data.users.length}}", + "key": "i4b3tj04fv", + "isDeprecated": false, + "rightColumn": 38.0, + "textAlign": "LEFT", + "widgetId": "31eat3ae9u", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1.0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "fontSize": "1rem" + } + ] + }, + "layoutOnLoadActions": [ + [ + { + "id": "Page2_Api1", + "name": "Api1", + "confirmBeforeExecute": false, + "pluginType": "API", + "jsonPathKeys": [], + "timeoutInMillisecond": 10000 + } + ] + ], + "new": false + } + ], + "userPermissions": [] + }, + "new": true + } + ], + "pageOrder": [ + "Page1", + "Page2" + ], + "publishedPageOrder": [ + "Page1", + "Page2" + ], + "publishedDefaultPageName": "Page1", + "unpublishedDefaultPageName": "Page1", + "actionList": [ + { + "id": "Page1_Api1", + "userPermissions": [ + "read:actions", + "execute:actions", + "manage:actions" + ], + "gitSyncId": "62987c9ec43c3d0bd6572220_62987cafc43c3d0bd6572224", + "pluginType": "API", + "pluginId": "restapi-plugin", + "unpublishedAction": { + "name": "Api1", + "datasource": { + "userPermissions": [], + "name": "DEFAULT_REST_DATASOURCE", + "pluginId": "restapi-plugin", + "datasourceConfiguration": { + "url": "https://mock-api.appsmith.com" + }, + "invalids": [], + "messages": [], + "isValid": true, + "new": true + }, + "pageId": "Page1", + "actionConfiguration": { + "timeoutInMillisecond": 10000, + "paginationType": "NONE", + "path": "/users", + "headers": [], + "encodeParamsToggle": true, + "queryParameters": [ + { + "key": "pageSize", + "value": "5" + } + ], + "bodyFormData": [], + "httpMethod": "GET", + "pluginSpecifiedTemplates": [ + { + "value": true + } + ], + "formData": { + "apiContentType": "none" + } + }, + "executeOnLoad": true, + "dynamicBindingPathList": [], + "isValid": true, + "invalids": [], + "messages": [], + "jsonPathKeys": [], + "confirmBeforeExecute": false, + "userPermissions": [], + "validName": "Api1" + }, + "publishedAction": { + "name": "Api1", + "datasource": { + "userPermissions": [], + "name": "DEFAULT_REST_DATASOURCE", + "pluginId": "restapi-plugin", + "datasourceConfiguration": { + "url": "https://mock-api.appsmith.com" + }, + "invalids": [], + "messages": [], + "isValid": true, + "new": true + }, + "pageId": "Page1", + "actionConfiguration": { + "timeoutInMillisecond": 10000, + "paginationType": "NONE", + "path": "/users", + "headers": [], + "encodeParamsToggle": true, + "queryParameters": [ + { + "key": "pageSize", + "value": "5" + } + ], + "bodyFormData": [], + "httpMethod": "GET", + "pluginSpecifiedTemplates": [ + { + "value": true + } + ], + "formData": { + "apiContentType": "none" + } + }, + "executeOnLoad": true, + "dynamicBindingPathList": [], + "isValid": true, + "invalids": [], + "messages": [], + "jsonPathKeys": [], + "confirmBeforeExecute": false, + "userPermissions": [], + "validName": "Api1" + }, + "new": false + }, + { + "id": "Page2_Api1", + "userPermissions": [ + "read:actions", + "execute:actions", + "manage:actions" + ], + "gitSyncId": "62987c9ec43c3d0bd6572220_62987ce3c43c3d0bd657222a", + "pluginType": "API", + "pluginId": "restapi-plugin", + "unpublishedAction": { + "name": "Api1", + "datasource": { + "userPermissions": [], + "name": "DEFAULT_REST_DATASOURCE", + "pluginId": "restapi-plugin", + "datasourceConfiguration": { + "url": "https://mock-api.appsmith.com" + }, + "invalids": [], + "messages": [], + "isValid": true, + "new": true + }, + "pageId": "Page2", + "actionConfiguration": { + "timeoutInMillisecond": 10000, + "paginationType": "NONE", + "path": "/users", + "headers": [], + "encodeParamsToggle": true, + "queryParameters": [ + { + "key": "pageSize", + "value": "10" + } + ], + "bodyFormData": [], + "httpMethod": "GET", + "pluginSpecifiedTemplates": [ + { + "value": true + } + ], + "formData": { + "apiContentType": "none" + } + }, + "executeOnLoad": true, + "dynamicBindingPathList": [], + "isValid": true, + "invalids": [], + "messages": [], + "jsonPathKeys": [], + "confirmBeforeExecute": false, + "userPermissions": [], + "validName": "Api1" + }, + "publishedAction": { + "name": "Api1", + "datasource": { + "userPermissions": [], + "name": "DEFAULT_REST_DATASOURCE", + "pluginId": "restapi-plugin", + "datasourceConfiguration": { + "url": "https://mock-api.appsmith.com" + }, + "invalids": [], + "messages": [], + "isValid": true, + "new": true + }, + "pageId": "Page2", + "actionConfiguration": { + "timeoutInMillisecond": 10000, + "paginationType": "NONE", + "path": "/users", + "headers": [], + "encodeParamsToggle": true, + "queryParameters": [ + { + "key": "pageSize", + "value": "10" + } + ], + "bodyFormData": [], + "httpMethod": "GET", + "pluginSpecifiedTemplates": [ + { + "value": true + } + ], + "formData": { + "apiContentType": "none" + } + }, + "executeOnLoad": true, + "dynamicBindingPathList": [], + "isValid": true, + "invalids": [], + "messages": [], + "jsonPathKeys": [], + "confirmBeforeExecute": false, + "userPermissions": [], + "validName": "Api1" + }, + "new": false + } + ], + "actionCollectionList": [], + "invisibleActionFields": { + "Page1_Api1": { + "unpublishedUserSetOnLoad": true, + "publishedUserSetOnLoad": true + }, + "Page2_Api1": { + "unpublishedUserSetOnLoad": true, + "publishedUserSetOnLoad": true + } + }, + "editModeTheme": { + "name": "Classic", + "displayName": "Classic", + "new": true, + "isSystemTheme": true + }, + "publishedTheme": { + "name": "Classic", + "displayName": "Classic", + "new": true, + "isSystemTheme": true + }, + "publishedLayoutmongoEscapedWidgets": {}, + "unpublishedLayoutmongoEscapedWidgets": {} +} \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/ExecuteAction_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/ExecuteAction_spec.js new file mode 100644 index 0000000000..c68fd35850 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/ExecuteAction_spec.js @@ -0,0 +1,54 @@ +import homePage from "../../../../locators/HomePage"; + +describe("Execute Action Functionality", function() { + // before(() => { + // cy.get(homePage.homeIcon).click(); + // cy.get(homePage.optionsIcon) + // .first() + // .click(); + // // Importing the App from the sample application + // cy.get(homePage.orgImportAppOption).click({ force: true }); + // cy.get(homePage.orgImportAppModal).should("be.visible"); + // cy.xpath(homePage.uploadLogo).attachFile("executeAction.json"); + // cy.get(homePage.importAppProgressWrapper).should("be.visible"); + // }); + + it.skip("checks whether execute action is getting called on page load only once", function() { + // Open deployed version + cy.get(homePage.deployPopupOptionTrigger).click({ force: true }); + cy.get(homePage.currentDeployedPreviewBtn) + .invoke("removeAttr", "target") + .click(); + + let completedIds = []; + + cy.get("@postExecute.all") + .then((respBody) => { + const totalRequests = [ + ...new Set(respBody.map((req) => req.browserRequestId)), + ]; + completedIds = totalRequests; + return totalRequests; + }) + .should("have.length", 1); + + cy.wait(500); + + cy.get(".t--page-switch-tab") + .contains("Page2") + .click({ force: true }); + + cy.wait(1000); + + cy.get("@postExecute.all") + .then((respBody) => { + const totalRequests = [ + ...new Set(respBody.map((req) => req.browserRequestId)), + ]; + return totalRequests.filter((reqId) => !completedIds.includes(reqId)); + }) + .should("have.length", 1); + + cy.wait(2000); + }); +}); diff --git a/app/client/src/actions/evaluationActions.ts b/app/client/src/actions/evaluationActions.ts index 9bc36cf226..8493ca3fd3 100644 --- a/app/client/src/actions/evaluationActions.ts +++ b/app/client/src/actions/evaluationActions.ts @@ -11,8 +11,9 @@ import { QueryActionConfig } from "entities/Action"; export const FIRST_EVAL_REDUX_ACTIONS = [ // Pages - ReduxActionTypes.FETCH_PAGE_SUCCESS, - ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS, + // ReduxActionTypes.FETCH_PAGE_SUCCESS, + // ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS, + ReduxActionTypes.FETCH_ALL_PAGE_ENTITY_COMPLETION, ]; export const EVALUATE_REDUX_ACTIONS = [ ...FIRST_EVAL_REDUX_ACTIONS, diff --git a/app/client/src/actions/jsActionActions.ts b/app/client/src/actions/jsActionActions.ts index d6c045fd8e..c83a7de4ad 100644 --- a/app/client/src/actions/jsActionActions.ts +++ b/app/client/src/actions/jsActionActions.ts @@ -134,6 +134,12 @@ export const fetchJSCollectionsForPageSuccess = (actions: JSCollection[]) => { }; }; +export const fetchJSCollectionsForPageError = () => { + return { + type: ReduxActionErrorTypes.FETCH_JS_ACTIONS_FOR_PAGE_ERROR, + }; +}; + export const fetchJSCollectionsForView = ({ applicationId, }: { diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index 8416b466aa..0eb0de5d97 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -5,9 +5,9 @@ import { ReduxActionTypes, UpdateCanvasPayload, ReduxActionErrorTypes, - AnyReduxAction, WidgetReduxActionTypes, ReplayReduxActionTypes, + AnyReduxAction, } from "@appsmith/constants/ReduxActionConstants"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { WidgetOperation } from "widgets/BaseWidget"; @@ -62,20 +62,29 @@ export const fetchPublishedPage = ( }, }); -export const fetchPageSuccess = ( - postEvalActions: Array, -): EvaluationReduxAction => { +export const fetchPageSuccess = (): EvaluationReduxAction => { return { type: ReduxActionTypes.FETCH_PAGE_SUCCESS, - postEvalActions, payload: undefined, }; }; -export const fetchPublishedPageSuccess = ( - postEvalActions: Array, -): EvaluationReduxAction => ({ +export const fetchPublishedPageSuccess = (): EvaluationReduxAction => ({ type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS, + payload: undefined, +}); + +/** + * After all page entities are fetched like DSL, actions and JsObjects, + * we trigger evaluation using this redux action, here we supply postEvalActions + * to trigger action after evaluation has been completed like executeOnPageLoadAction + * + * @param {Array} postEvalActions + */ +export const fetchAllPageEntityCompletion = ( + postEvalActions: Array, +) => ({ + type: ReduxActionTypes.FETCH_ALL_PAGE_ENTITY_COMPLETION, postEvalActions, payload: undefined, }); diff --git a/app/client/src/actions/pluginActionActions.ts b/app/client/src/actions/pluginActionActions.ts index 80811ce30c..30c8a547d3 100644 --- a/app/client/src/actions/pluginActionActions.ts +++ b/app/client/src/actions/pluginActionActions.ts @@ -54,23 +54,25 @@ export const fetchActionsForView = ({ export const fetchActionsForPage = ( pageId: string, - postEvalActions: Array = [], ): EvaluationReduxAction => { return { type: ReduxActionTypes.FETCH_ACTIONS_FOR_PAGE_INIT, payload: { pageId }, - postEvalActions, }; }; export const fetchActionsForPageSuccess = ( actions: Action[], - postEvalActions?: Array, ): EvaluationReduxAction => { return { type: ReduxActionTypes.FETCH_ACTIONS_FOR_PAGE_SUCCESS, payload: actions, - postEvalActions, + }; +}; + +export const fetchActionsForPageError = () => { + return { + type: ReduxActionErrorTypes.FETCH_ACTIONS_FOR_PAGE_ERROR, }; }; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index aa704a5522..ebab26e24c 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -207,6 +207,7 @@ export const ReduxActionTypes = { CLEAR_CANVAS: "CLEAR_CANVAS", FETCH_PAGE_INIT: "FETCH_PAGE_INIT", FETCH_PAGE_SUCCESS: "FETCH_PAGE_SUCCESS", + FETCH_ALL_PAGE_ENTITY_COMPLETION: "FETCH_ALL_PAGE_ENTITY_COMPLETION", DROP_WIDGET_CANVAS: "DROP_WIDGET_CANVAS", REMOVE_WIDGET_CANVAS: "REMOVE_WIDGET_CANVAS", LOAD_WIDGET_PANE: "LOAD_WIDGET_PANE", diff --git a/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx b/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx index 04c8a1c6d0..63181830ed 100644 --- a/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx +++ b/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useMemo } from "react"; +import React, { useMemo } from "react"; import { Link, RouteComponentProps, withRouter } from "react-router-dom"; -import { useDispatch, useSelector } from "react-redux"; +import { useSelector } from "react-redux"; import { getIsFetchingPage } from "selectors/appViewSelectors"; import styled from "styled-components"; import { AppViewerRouteParams } from "constants/routes"; @@ -19,7 +19,6 @@ import { isPermitted, PERMISSION_TYPE, } from "../Applications/permissionHelpers"; -import { fetchPublishedPage } from "actions/pageActions"; import { builderURL } from "RouteBuilder"; const Section = styled.section<{ @@ -36,19 +35,13 @@ const Section = styled.section<{ type AppViewerPageContainerProps = RouteComponentProps; function AppViewerPageContainer(props: AppViewerPageContainerProps) { - const dispatch = useDispatch(); const currentPageName = useSelector(getCurrentPageName); const widgets = useSelector(getCanvasWidgetDsl); const isFetchingPage = useSelector(getIsFetchingPage); const currentApplication = useSelector(getCurrentApplication); const { match } = props; - const { pageId } = match.params; const { applicationSlug, pageSlug } = useSelector(selectURLSlugs); - useEffect(() => { - pageId && dispatch(fetchPublishedPage(pageId, true)); - }, [pageId, location.pathname]); - // get appsmith editr link const appsmithEditorLink = useMemo(() => { if ( diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index ce2b3f30a4..30e74fe541 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -285,9 +285,8 @@ export function* fetchActionsForPageSaga( ); const isValidResponse = yield validateResponse(response); if (isValidResponse) { - yield put( - fetchActionsForPageSuccess(response.data, action.postEvalActions), - ); + yield put(fetchActionsForPageSuccess(response.data)); + // wait for success of PerformanceTracker.stopAsyncTracking( PerformanceTransactionName.FETCH_PAGE_ACTIONS_API, ); diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index 5316ba4a70..8e49e446c8 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -20,6 +20,7 @@ import { ERROR_CODES } from "@appsmith/constants/ApiConstants"; import { fetchPage, + fetchPageSuccess, fetchPublishedPage, fetchPublishedPageSuccess, resetApplicationWidgets, @@ -87,6 +88,7 @@ import { isURLDeprecated, getUpdatedRoute } from "utils/helpers"; import { fillPathname, viewerURL, builderURL } from "RouteBuilder"; import { enableGuidedTour } from "actions/onboardingActions"; import { setPreviewModeAction } from "actions/editorActions"; +import { fetchAllPageEntityCompletion } from "actions/pageActions"; import { fetchSelectedAppThemeAction, fetchAppThemesAction, @@ -97,17 +99,9 @@ export function* failFastApiCalls( successActions: string[], failureActions: string[], ) { - const triggerEffects = []; - for (const triggerAction of triggerActions) { - triggerEffects.push(put(triggerAction)); - } - const successEffects = []; - for (const successAction of successActions) { - successEffects.push(take(successAction)); - } - yield all(triggerEffects); + yield all(triggerActions.map((triggerAction) => put(triggerAction))); const effectRaceResult = yield race({ - success: all(successEffects), + success: all(successActions.map((successAction) => take(successAction))), failure: take(failureActions), }); if (effectRaceResult.failure) { @@ -230,19 +224,12 @@ function* initiateEditorApplicationAndPages(payload: InitializeEditorPayload) { yield call(initiateURLUpdate, toLoadPageId, APP_MODE.EDIT, payload.pageId); - const fetchPageCallResult: boolean = yield failFastApiCalls( - [fetchPage(toLoadPageId, true)], - [ReduxActionTypes.FETCH_PAGE_SUCCESS], - [ReduxActionErrorTypes.FETCH_PAGE_ERROR], - ); - - if (!fetchPageCallResult) return; - return toLoadPageId; } -function* initiateEditorActions(applicationId: string) { +function* initiateEditorActions(toLoadPageId: string, applicationId: string) { const initActionsCalls = [ + fetchPage(toLoadPageId, true), fetchActions({ applicationId }, []), fetchJSCollections({ applicationId }), fetchSelectedAppThemeAction(applicationId), @@ -254,12 +241,14 @@ function* initiateEditorActions(applicationId: string) { ReduxActionTypes.FETCH_ACTIONS_SUCCESS, ReduxActionTypes.FETCH_APP_THEMES_SUCCESS, ReduxActionTypes.FETCH_SELECTED_APP_THEME_SUCCESS, + fetchPageSuccess().type, ]; const failureActionEffects = [ ReduxActionErrorTypes.FETCH_JS_ACTIONS_ERROR, ReduxActionErrorTypes.FETCH_ACTIONS_ERROR, ReduxActionErrorTypes.FETCH_APP_THEMES_ERROR, ReduxActionErrorTypes.FETCH_SELECTED_APP_THEME_ERROR, + ReduxActionErrorTypes.FETCH_PAGE_ERROR, ]; const allActionCalls: boolean = yield failFastApiCalls( initActionsCalls, @@ -273,7 +262,7 @@ function* initiateEditorActions(applicationId: string) { yield put({ type: ReduxActionTypes.FETCH_PLUGIN_AND_JS_ACTIONS_SUCCESS, }); - yield put(executePageLoadActions()); + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); } } @@ -337,7 +326,8 @@ function* initializeEditorSaga( PerformanceTransactionName.INIT_EDIT_APP, ); - yield call(initiateEditorApplicationAndPages, payload); + const toLoadPageId = yield call(initiateEditorApplicationAndPages, payload); + if (!toLoadPageId) return; const { id: applicationId, name }: ApplicationPayload = yield select( getCurrentApplication, @@ -348,7 +338,7 @@ function* initializeEditorSaga( ); yield all([ - call(initiateEditorActions, applicationId), + call(initiateEditorActions, toLoadPageId, applicationId), call(initiatePluginsAndDatasources), call(populatePageDSLsSaga), ]); @@ -430,15 +420,16 @@ export function* initializeAppViewerSaga( [ fetchActionsForView({ applicationId }), fetchJSCollectionsForView({ applicationId }), - fetchPublishedPage(toLoadPageId, true, true), fetchSelectedAppThemeAction(applicationId), fetchAppThemesAction(applicationId), + fetchPublishedPage(toLoadPageId, true, true), ], [ ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS, ReduxActionTypes.FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS, ReduxActionTypes.FETCH_APP_THEMES_SUCCESS, ReduxActionTypes.FETCH_SELECTED_APP_THEME_SUCCESS, + fetchPublishedPageSuccess().type, ], [ ReduxActionErrorTypes.FETCH_ACTIONS_VIEW_MODE_ERROR, @@ -451,34 +442,7 @@ export function* initializeAppViewerSaga( if (!resultOfPrimaryCalls) return; - //Delay page load actions till all actions are retrieved. - yield put(fetchPublishedPageSuccess([executePageLoadActions()])); - - if (toLoadPageId) { - yield put(fetchPublishedPage(toLoadPageId, true)); - - const resultOfFetchPage: { - success: boolean; - failure: boolean; - } = yield race({ - success: take(ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS), - failure: take(ReduxActionErrorTypes.FETCH_PUBLISHED_PAGE_ERROR), - }); - - if (resultOfFetchPage.failure) { - yield put({ - type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST, - payload: { - code: get( - resultOfFetchPage, - "failure.payload.error.code", - ERROR_CODES.SERVER_ERROR, - ), - }, - }); - return; - } - } + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); yield put(fetchCommentThreadsInit()); diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index d337f1faf3..d093efd39b 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -24,6 +24,9 @@ import { setLastUpdatedTime, ClonePageActionPayload, CreatePageActionPayload, + generateTemplateError, + generateTemplateSuccess, + fetchAllPageEntityCompletion, } from "actions/pageActions"; import PageApi, { ClonePageRequest, @@ -76,6 +79,8 @@ import { fetchActionsForPage, setActionsToExecuteOnPageLoad, setJSActionsToExecuteOnPageLoad, + fetchActionsForPageSuccess, + fetchActionsForPageError, } from "actions/pluginActionActions"; import { UrlDataState } from "reducers/entityReducers/appReducer"; import { APP_MODE } from "entities/App"; @@ -93,10 +98,7 @@ import { ERROR_CODES } from "@appsmith/constants/ApiConstants"; import AnalyticsUtil from "utils/AnalyticsUtil"; import DEFAULT_TEMPLATE from "templates/default"; import { GenerateTemplatePageRequest } from "api/PageApi"; -import { - generateTemplateError, - generateTemplateSuccess, -} from "actions/pageActions"; + import { getAppMode } from "selectors/applicationSelectors"; import { setCrudInfoModalData } from "actions/crudInfoModalActions"; import { selectMultipleWidgetsAction } from "actions/widgetSelectionActions"; @@ -105,11 +107,16 @@ import { getFirstTimeUserOnboardingApplicationId, inGuidedTour, } from "selectors/onboardingSelectors"; -import { fetchJSCollectionsForPage } from "actions/jsActionActions"; +import { + fetchJSCollectionsForPage, + fetchJSCollectionsForPageSuccess, + fetchJSCollectionsForPageError, +} from "actions/jsActionActions"; import WidgetFactory from "utils/WidgetFactory"; import { toggleShowDeviationDialog } from "actions/onboardingActions"; import { builderURL, generateTemplateURL } from "RouteBuilder"; +import { failFastApiCalls } from "./InitSagas"; const WidgetTypes = WidgetFactory.widgetTypes; @@ -223,12 +230,15 @@ export function* handleFetchedPage({ // set current page yield put(updateCurrentPage(pageId, pageSlug)); // dispatch fetch page success - yield put( - fetchPageSuccess( - // Execute page load actions post page load - isFirstLoad ? [] : [executePageLoadActions()], - ), - ); + yield put(fetchPageSuccess()); + + /* Currently, All Actions are fetched in initSagas and on pageSwitch we only fetch page + */ + // Hence, if is not isFirstLoad then trigger evaluation with execute pageLoad action + if (!isFirstLoad) { + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); + } + // Sets last updated time yield put(setLastUpdatedTime(lastUpdatedTime)); const extractedDSL = extractCurrentDSL(fetchPageResponse); @@ -321,15 +331,16 @@ export function* fetchPublishedPageSaga( // set current page yield put(updateCurrentPage(pageId, response.data.slug)); + // dispatch fetch page success + yield put(fetchPublishedPageSuccess()); + + /* Currently, All Actions are fetched in initSagas and on pageSwitch we only fetch page + */ + // Hence, if is not isFirstLoad then trigger evaluation with execute pageLoad action if (!firstLoad) { - // dispatch fetch page success - yield put( - fetchPublishedPageSuccess( - // Execute page load actions post published page eval - [executePageLoadActions()], - ), - ); + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); } + PerformanceTracker.stopAsyncTracking( PerformanceTransactionName.FETCH_PAGE_API, ); @@ -977,6 +988,15 @@ export function* generateTemplatePageSaga( const isValidResponse: boolean = yield validateResponse(response); if (isValidResponse) { const pageId = response.data.page.id; + + yield put( + generateTemplateSuccess({ + page: response.data.page, + isNewPage: !request.pageId, + // if pageId if not defined, that means a new page is generated. + }), + ); + yield handleFetchedPage({ fetchPageResponse: { data: response.data.page, @@ -986,16 +1006,30 @@ export function* generateTemplatePageSaga( isFirstLoad: true, }); - // TODO : Add this to onSuccess (Redux Action) - yield put( - generateTemplateSuccess({ - page: response.data.page, - isNewPage: !request.pageId, // if pageId if not defined, that means a new page is generated. - }), + // trigger evaluation after completion of page success & fetch actions for page + fetch jsobject for page + + const triggersAfterPageFetch = [ + fetchActionsForPage(pageId), + fetchJSCollectionsForPage(pageId), + ]; + + const afterActionsFetch = yield failFastApiCalls( + triggersAfterPageFetch, + [ + fetchActionsForPageSuccess([]).type, + fetchJSCollectionsForPageSuccess([]).type, + ], + [ + fetchActionsForPageError().type, + fetchJSCollectionsForPageError().type, + ], ); - // TODO : Add this to onSuccess (Redux Action) - yield put(fetchActionsForPage(pageId, [executePageLoadActions()])); - // TODO : Add it to onSuccessCallback + + if (!afterActionsFetch) { + throw new Error("Failed generating template"); + } + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); + history.replace( builderURL({ pageSlug: response.data.page.slug, From 068db3be5245e50c8f8bb739a8f0beffd445d469 Mon Sep 17 00:00:00 2001 From: Rishabh Rathod Date: Tue, 7 Jun 2022 12:06:27 +0530 Subject: [PATCH 2/2] fix: crash due to evalMetaUpdate (#14320) * fix: crash due to evalMetaUpdate * Add default value for workerResponse * patch fix * fix root cause of DataCloneError (cherry picked from commit dbcd69e63e57aa214bd280ac081093b523fba2a1) --- app/client/src/sagas/EvaluationsSaga.ts | 2 +- app/client/src/workers/evaluation.worker.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index a762978a03..0ce22349ef 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -127,7 +127,7 @@ function* evaluateTreeSaga( dataTree, dependencies, errors, - evalMetaUpdates, + evalMetaUpdates = [], evaluationOrder, jsUpdates, logs, diff --git a/app/client/src/workers/evaluation.worker.ts b/app/client/src/workers/evaluation.worker.ts index 49559af3ab..bbf9685935 100644 --- a/app/client/src/workers/evaluation.worker.ts +++ b/app/client/src/workers/evaluation.worker.ts @@ -149,7 +149,6 @@ ctx.addEventListener( evaluationOrder = dataTreeEvaluator.sortedDependencies; dataTree = dataTreeResponse.evalTree; jsUpdates = dataTreeResponse.jsUpdates; - evalMetaUpdates = dataTreeResponse.evalMetaUpdates; dataTree = dataTree && JSON.parse(JSON.stringify(dataTree)); } else { if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { @@ -166,7 +165,11 @@ ctx.addEventListener( unEvalUpdates = updateResponse.unEvalUpdates; dataTree = JSON.parse(JSON.stringify(dataTreeEvaluator.evalTree)); jsUpdates = updateResponse.jsUpdates; - evalMetaUpdates = updateResponse.evalMetaUpdates; + // evalMetaUpdates can have moment object as value which will cause DataCloneError + // hence, stringify and parse to avoid such errors + evalMetaUpdates = JSON.parse( + JSON.stringify(updateResponse.evalMetaUpdates), + ); } dependencies = dataTreeEvaluator.inverseDependencyMap; errors = dataTreeEvaluator.errors;