diff --git a/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/ExecuteAction_Spec.ts b/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/ExecuteAction_Spec.ts index fbf4f753151..c4653d3f430 100644 --- a/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/ExecuteAction_Spec.ts +++ b/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/ExecuteAction_Spec.ts @@ -1,5 +1,6 @@ import { agHelper, + assertHelper, deployMode, homePage, locators, @@ -17,6 +18,8 @@ describe( agHelper.AssertElementVisibility(locators._widgetInCanvas("textwidget")); deployMode.DeployApp(); + assertHelper.AssertNetworkStatus("@postExecute", 200, true); + agHelper.GetNAssertContains( locators._widgetInDeployed("textwidget"), "User count :5", @@ -37,6 +40,9 @@ describe( agHelper.GetNClickByContains(locators._deployedPage, "Page2"); + assertHelper.AssertNetworkStatus("@getConsolidatedData", 200, true); + assertHelper.AssertNetworkStatus("@postExecute", 200, true); + agHelper.GetNAssertContains( locators._widgetInDeployed("textwidget"), "User count :10", @@ -55,6 +61,9 @@ describe( agHelper.GetNClickByContains(locators._deployedPage, "Page1"); + assertHelper.AssertNetworkStatus("@getConsolidatedData", 200, true); + assertHelper.AssertNetworkStatus("@postExecute", 200, true); + agHelper.GetNAssertContains( locators._widgetInDeployed("textwidget"), "User count :5", diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index 003d0aa20a6..f4e1a78b71a 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -72,6 +72,10 @@ export interface FetchPublishedPageActionPayload { pageWithMigratedDsl?: FetchPageResponse; } +export interface FetchPublishedPageResourcesPayload { + pageId: string; +} + export const fetchPublishedPageAction = ( pageId: string, bustCache = false, @@ -290,6 +294,17 @@ export const clonePageSuccess = ({ }; }; +// Fetches resources required for published page, currently only used for fetching actions +// In future we can reuse this for fetching other page level resources in published mode +export const fetchPublishedPageResourcesAction = ( + pageId: string, +): ReduxAction => ({ + type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_RESOURCES_INIT, + payload: { + pageId, + }, +}); + // update a page export interface UpdatePageActionPayload { diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index e3c5510381f..0745fbf2523 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -980,6 +980,7 @@ const AppViewActionTypes = { FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS: "FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS", SET_APP_VIEWER_HEADER_HEIGHT: "SET_APP_VIEWER_HEADER_HEIGHT", SET_APP_SIDEBAR_PINNED: "SET_APP_SIDEBAR_PINNED", + FETCH_PUBLISHED_PAGE_RESOURCES_INIT: "FETCH_PUBLISHED_PAGE_RESOURCES_INIT", }; const AppViewActionErrorTypes = { @@ -988,6 +989,7 @@ const AppViewActionErrorTypes = { PUBLISH_APPLICATION_ERROR: "PUBLISH_APPLICATION_ERROR", FETCH_ACTIONS_VIEW_MODE_ERROR: "FETCH_ACTION_VIEW_MODE_ERROR", FETCH_JS_ACTIONS_VIEW_MODE_ERROR: "FETCH_JS_ACTIONS_VIEW_MODE_ERROR", + FETCH_PUBLISHED_PAGE_RESOURCES_ERROR: "FETCH_PUBLISHED_PAGE_RESOURCES_ERROR", }; const WorkspaceActionTypes = { diff --git a/app/client/src/ce/sagas/PageSagas.tsx b/app/client/src/ce/sagas/PageSagas.tsx index c58237cc015..decc911734c 100644 --- a/app/client/src/ce/sagas/PageSagas.tsx +++ b/app/client/src/ce/sagas/PageSagas.tsx @@ -10,6 +10,7 @@ import type { DeletePageActionPayload, FetchPageActionPayload, FetchPublishedPageActionPayload, + FetchPublishedPageResourcesPayload, GenerateTemplatePageActionPayload, SetPageOrderActionPayload, SetupPageActionPayload, @@ -87,6 +88,7 @@ import { fetchActionsForPage, fetchActionsForPageError, fetchActionsForPageSuccess, + fetchActionsForView, setActionsToExecuteOnPageLoad, setJSActionsToExecuteOnPageLoad, } from "actions/pluginActionActions"; @@ -113,6 +115,7 @@ import { import WidgetFactory from "WidgetProvider/factory"; import { builderURL } from "ee/RouteBuilder"; import { failFastApiCalls, waitForWidgetConfigBuild } from "sagas/InitSagas"; +import { type InitConsolidatedApi } from "sagas/InitSagas"; import { resizePublishedMainCanvasToLowestWidget } from "sagas/WidgetOperationUtils"; import { checkAndLogErrorsIfCyclicDependency, @@ -146,6 +149,7 @@ import type { LayoutSystemTypes } from "layoutSystems/types"; import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors"; import { convertToBasePageIdSelector } from "selectors/pageListSelectors"; import type { Page } from "entities/Page"; +import ConsolidatedPageLoadApi from "api/ConsolidatedPageLoadApi"; export const checkIfMigrationIsNeeded = ( fetchPageResponse?: FetchPageResponse, @@ -378,6 +382,43 @@ export function* fetchPublishedPageSaga( } } +export function* fetchPublishedPageResourcesSaga( + action: ReduxAction, +) { + try { + const { pageId } = action.payload; + + const params = { defaultPageId: pageId }; + const initConsolidatedApiResponse: ApiResponse = + yield ConsolidatedPageLoadApi.getConsolidatedPageLoadDataView(params); + + const isValidResponse: boolean = yield validateResponse( + initConsolidatedApiResponse, + ); + const response: InitConsolidatedApi | undefined = + initConsolidatedApiResponse.data; + + if (isValidResponse) { + // We need to recall consolidated view API in order to fetch actions when page is switched + // As in the first call only actions of the current page are fetched + // In future, we can reuse this saga to fetch other resources of the page like actionCollections etc + const { publishedActions } = response; + + // Sending applicationId as empty as we have publishedActions present, + // it won't call the actions view api with applicationId + yield put(fetchActionsForView({ applicationId: "", publishedActions })); + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.FETCH_PUBLISHED_PAGE_RESOURCES_ERROR, + payload: { + error, + }, + }); + } +} + export function* fetchAllPublishedPagesSaga() { try { const pageIdentities: { pageId: string; basePageId: string }[] = diff --git a/app/client/src/ee/sagas/PageSagas.tsx b/app/client/src/ee/sagas/PageSagas.tsx index 2f52077ca52..1661b83810a 100644 --- a/app/client/src/ee/sagas/PageSagas.tsx +++ b/app/client/src/ee/sagas/PageSagas.tsx @@ -21,6 +21,7 @@ import { refreshTheApp, setupPageSaga, setupPublishedPageSaga, + fetchPublishedPageResourcesSaga, } from "ce/sagas/PageSagas"; import { all, @@ -72,5 +73,9 @@ export default function* pageSagas() { ReduxActionTypes.SETUP_PUBLISHED_PAGE_INIT, setupPublishedPageSaga, ), + takeLatest( + ReduxActionTypes.FETCH_PUBLISHED_PAGE_RESOURCES_INIT, + fetchPublishedPageResourcesSaga, + ), ]); } diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index 5fab3e349ef..8ac8d38e651 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -28,7 +28,10 @@ import { useSelector } from "react-redux"; import BrandingBadge from "./BrandingBadge"; import { setAppViewHeaderHeight } from "actions/appViewActions"; import { CANVAS_SELECTOR } from "constants/WidgetConstants"; -import { setupPublishedPage } from "actions/pageActions"; +import { + setupPublishedPage, + fetchPublishedPageResourcesAction, +} from "actions/pageActions"; import usePrevious from "utils/hooks/usePrevious"; import { getIsBranchUpdated } from "../utils"; import { APP_MODE } from "entities/App"; @@ -168,6 +171,9 @@ function AppViewer(props: Props) { )?.pageId; if (pageId) { dispatch(setupPublishedPage(pageId, true)); + + // Used for fetching page resources + dispatch(fetchPublishedPageResourcesAction(basePageId)); } } } diff --git a/app/client/src/reducers/uiReducers/appViewReducer.tsx b/app/client/src/reducers/uiReducers/appViewReducer.tsx index 893d18219d8..1d3671392f2 100644 --- a/app/client/src/reducers/uiReducers/appViewReducer.tsx +++ b/app/client/src/reducers/uiReducers/appViewReducer.tsx @@ -31,6 +31,11 @@ const appViewReducer = createReducer(initialState, { ) => { return { ...state, isFetchingPage: false }; }, + [ReduxActionErrorTypes.FETCH_PUBLISHED_PAGE_RESOURCES_ERROR]: ( + state: AppViewReduxState, + ) => { + return { ...state, isFetchingPage: false }; + }, [ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS]: ( state: AppViewReduxState, ) => { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java index b52362f4268..a2cf04bbd12 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCE.java @@ -78,11 +78,10 @@ Flux findAllByApplicationIdAndViewMode( Flux findAllByApplicationIdAndViewMode( String applicationId, Boolean viewMode, Optional permission, Optional sort); - Flux findAllByApplicationIdAndPluginType( - String applicationId, Boolean viewMode, AclPermission permission, Sort sort, List pluginTypes); - Flux getActionsForViewMode(String applicationId); + Flux getActionsForViewModeByPageId(String pageId); + ActionViewDTO generateActionViewDTO(NewAction action, ActionDTO actionDTO, boolean viewMode); Mono deleteUnpublishedAction(String id); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java index 0ea6e88c480..41586161539 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java @@ -729,19 +729,6 @@ public Flux findByPageIdAndViewMode(String pageId, Boolean viewMode, return repository.findByPageIdAndViewMode(pageId, viewMode, permission).flatMap(this::sanitizeAction); } - @Override - public Flux findAllByApplicationIdAndPluginType( - String applicationId, - Boolean viewMode, - AclPermission permission, - Sort sort, - List excludedPluginTypes) { - return repository - .findPublishedActionsByApplicationIdAndPluginType(applicationId, excludedPluginTypes, permission, sort) - .name(VIEW_MODE_FETCH_ACTIONS_FROM_DB) - .tap(Micrometer.observation(observationRegistry)); - } - @Override public Flux findAllByApplicationIdAndViewMode( String applicationId, Boolean viewMode, AclPermission permission, Sort sort) { @@ -776,19 +763,40 @@ public Flux findAllByApplicationIdAndViewMode( .flatMapMany(this::addMissingPluginDetailsIntoAllActions); } - @Override public Flux getActionsForViewMode(String applicationId) { if (applicationId == null || applicationId.isEmpty()) { - return Flux.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.APPLICATION_ID)); + return Flux.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.PAGE_ID)); + } + + List excludedPluginTypes = List.of(PluginType.JS.toString()); + + // fetch the published actions by appId + // No need to sort the results + return repository + .findPublishedActionsByAppIdAndExcludedPluginType( + applicationId, excludedPluginTypes, actionPermission.getExecutePermission(), null) + .name(VIEW_MODE_FETCH_ACTIONS_FROM_DB) + .tap(Micrometer.observation(observationRegistry)) + .map(action -> generateActionViewDTO(action, action.getPublishedAction(), true)); + } + + @Override + public Flux getActionsForViewModeByPageId(String pageId) { + + if (pageId == null || pageId.isEmpty()) { + return Flux.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.PAGE_ID)); } List excludedPluginTypes = List.of(PluginType.JS.toString()); - // fetch the published actions by applicationId + // fetch the published actions by pageId // No need to sort the results - return findAllByApplicationIdAndPluginType( - applicationId, true, actionPermission.getExecutePermission(), null, excludedPluginTypes) + return repository + .findPublishedActionsByPageIdAndExcludedPluginType( + pageId, excludedPluginTypes, actionPermission.getExecutePermission(), null) + .name(VIEW_MODE_FETCH_ACTIONS_FROM_DB) + .tap(Micrometer.observation(observationRegistry)) .map(action -> generateActionViewDTO(action, action.getPublishedAction(), true)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCE.java index 3bd9822f22a..80351b502b0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCE.java @@ -37,8 +37,11 @@ Flux findAllActionsByNameAndPageIdsAndViewMode( Flux findByApplicationId(String applicationId, AclPermission aclPermission, Sort sort); - Flux findPublishedActionsByApplicationIdAndPluginType( - String applicationId, List pluginTypes, AclPermission aclPermission, Sort sort); + Flux findPublishedActionsByPageIdAndExcludedPluginType( + String pageId, List pluginTypes, AclPermission aclPermission, Sort sort); + + Flux findPublishedActionsByAppIdAndExcludedPluginType( + String appId, List pluginTypes, AclPermission aclPermission, Sort sort); Flux findByApplicationId( String applicationId, Optional aclPermission, Optional sort); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCEImpl.java index b86ea47ffff..0d56c4dcabb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewActionRepositoryCEImpl.java @@ -202,18 +202,17 @@ protected BridgeQuery getCriterionForFindByApplicationId(String appli return Bridge.equal(NewAction.Fields.applicationId, applicationId); } - @Override - public Flux findPublishedActionsByApplicationIdAndPluginType( + public Flux findPublishedActionsByAppIdAndExcludedPluginType( String applicationId, List excludedPluginTypes, AclPermission aclPermission, Sort sort) { return queryBuilder() - .criteria(getCriterionForFindPublishedActionsByApplicationIdAndPluginType( + .criteria(getCriterionForFindPublishedActionsByAppIdAndExcludedPluginType( applicationId, excludedPluginTypes)) .permission(aclPermission) .sort(sort) .all(); } - protected BridgeQuery getCriterionForFindPublishedActionsByApplicationIdAndPluginType( + protected BridgeQuery getCriterionForFindPublishedActionsByAppIdAndExcludedPluginType( String applicationId, List excludedPluginTypes) { final BridgeQuery q = getCriterionForFindByApplicationId(applicationId); q.and(Bridge.or( @@ -221,6 +220,25 @@ protected BridgeQuery getCriterionForFindPublishedActionsByApplicatio Bridge.isNull(NewAction.Fields.pluginType))); q.isNotNull(NewAction.Fields.publishedAction_pageId); + return q; + } + + @Override + public Flux findPublishedActionsByPageIdAndExcludedPluginType( + String pageId, List excludedPluginTypes, AclPermission aclPermission, Sort sort) { + return queryBuilder() + .criteria(getCriterionForFindPublishedActionsByPageIdAndExcludedPluginType(pageId, excludedPluginTypes)) + .permission(aclPermission) + .sort(sort) + .all(); + } + + protected BridgeQuery getCriterionForFindPublishedActionsByPageIdAndExcludedPluginType( + String pageId, List excludedPluginTypes) { + final BridgeQuery q = Bridge.equal(NewAction.Fields.publishedAction_pageId, pageId); + q.and(Bridge.or( + Bridge.notIn(NewAction.Fields.pluginType, excludedPluginTypes), + Bridge.isNull(NewAction.Fields.pluginType))); return q; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java index 5716920b8d7..7ec347e840b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java @@ -37,6 +37,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; import reactor.core.observability.micrometer.Micrometer; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -197,9 +198,15 @@ public Mono getConsolidatedInfoForPageLoad( /* Fetch default application id if not provided */ Mono branchedApplicationMonoCached; - if (isBlank(baseApplicationId)) { - branchedApplicationMonoCached = newPageService + Mono branchedPageMonoCached = Mono.empty(); + if (!isBlank(basePageId)) { + branchedPageMonoCached = newPageService .findByBranchNameAndBasePageIdAndApplicationMode(branchName, basePageId, mode) + .cache(); + } + + if (isBlank(baseApplicationId)) { + branchedApplicationMonoCached = branchedPageMonoCached .map(NewPage::getApplicationId) .flatMap(applicationId -> applicationService.findByBranchedApplicationIdAndApplicationMode(applicationId, mode)) @@ -279,18 +286,23 @@ public Mono getConsolidatedInfoForPageLoad( /* Fetch view specific data */ if (isViewMode) { - /* Get list of all actions in view mode */ - fetches.add(branchedApplicationMonoCached - .name(getQualifiedSpanName(APPLICATION_ID_SPAN, mode)) - .tap(Micrometer.observation(observationRegistry)) - .flatMap(branchedApplication -> newActionService - .getActionsForViewMode(branchedApplication.getId()) - .collectList()) - .as(this::toResponseDTO) - .doOnError(e -> log.error("Error fetching actions for view mode", e)) - .doOnSuccess(consolidatedAPIResponseDTO::setPublishedActions) - .name(getQualifiedSpanName(ACTIONS_SPAN, mode)) - .tap(Micrometer.observation(observationRegistry))); + /* Get list of all actions of the page in view mode */ + if (!isBlank(basePageId)) { + // When branchName is null, we don't need to fetch page from DB to derive pageId + // We can simply reuse the pageId that is passed by client to query actions + Mono branchedPageIdMono = !StringUtils.hasText(branchName) + ? Mono.just(basePageId) + : branchedPageMonoCached.map(NewPage::getId); + fetches.add(branchedPageIdMono + .flatMap(branchedPageId -> newActionService + .getActionsForViewModeByPageId(branchedPageId) + .collectList()) + .as(this::toResponseDTO) + .doOnError(e -> log.error("Error fetching actions for view mode", e)) + .doOnSuccess(consolidatedAPIResponseDTO::setPublishedActions) + .name(getQualifiedSpanName(ACTIONS_SPAN, mode)) + .tap(Micrometer.observation(observationRegistry))); + } /* Get list of all action collections in view mode */ fetches.add(branchedApplicationMonoCached diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java index 00d38a0eebc..c030539e9ce 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java @@ -222,6 +222,7 @@ public void testPageLoadResponseForViewMode() { NewPage mockNewPage = new NewPage(); mockNewPage.setApplicationId("mockApplicationId"); + mockNewPage.setId("mockPageId"); doReturn(Mono.just(mockNewPage)) .when(spyNewPageService) .findByBranchNameAndBasePageId(anyString(), anyString(), any()); @@ -257,7 +258,7 @@ public void testPageLoadResponseForViewMode() { ActionViewDTO sampleActionViewDTO = new ActionViewDTO(); sampleActionViewDTO.setName("sampleActionViewDTO"); - doReturn(Flux.just(sampleActionViewDTO)).when(spyNewActionService).getActionsForViewMode(anyString()); + doReturn(Flux.just(sampleActionViewDTO)).when(spyNewActionService).getActionsForViewModeByPageId(anyString()); ActionCollectionViewDTO sampleActionCollectionViewDTO = new ActionCollectionViewDTO(); sampleActionCollectionViewDTO.setName("sampleActionCollectionViewDTO");