chore: Add separate flow for Anvil app publish (#39891)

## Description

Anvil apps can have additional tasks before publishing so separate it
out

Fixes
https://www.notion.so/appsmith/Prompt-users-to-save-tools-on-deploy-1bcfe271b0e2804abe30fe462061f454?pvs=4

## Automation

/ok-to-test tags="@tag.Sanity"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/14052281640>
> Commit: df611bee4f3c54107f2478f97b9263491b2b2e2e
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14052281640&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Sanity`
> Spec:
> <hr>Tue, 25 Mar 2025 06:04:32 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced an enhanced publishing flow for Anvil applications with
dedicated actions for initiation, success, and error scenarios.
- Updated the deployment behavior to automatically use the Anvil
publishing process when enabled.

- **Refactor**
- Streamlined the code by removing legacy functionality related to
schema generation.
- Consolidated utility methods for determining default pages for
improved consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Hetu Nandu 2025-03-25 15:25:48 +05:30 committed by GitHub
parent b61b12ec00
commit 3d1192aba1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 92 additions and 67 deletions

View File

@ -20,16 +20,6 @@ export const isActionDirty = (id: string) =>
const getActionRunningState = (state: AppState) =>
state.ui.pluginActionEditor.isRunning;
const getActionSchemaGeneratingState = (state: AppState) =>
state.ui.pluginActionEditor.isSchemaGenerating;
export const isActionSchemaGenerating = (id: string) =>
createSelector(
[getActionSchemaGeneratingState],
(isSchemaGeneratingMap) =>
id in isSchemaGeneratingMap && isSchemaGeneratingMap[id],
);
export const isActionRunning = (id: string) =>
createSelector(
[getActionRunningState],

View File

@ -5,7 +5,10 @@ import type {
ImportApplicationRequest,
UpdateApplicationPayload,
} from "ee/api/ApplicationApi";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import {
ReduxActionErrorTypes,
ReduxActionTypes,
} from "ee/constants/ReduxActionConstants";
import type { NavigationSetting, ThemeSetting } from "constants/AppConstants";
import type { IconNames } from "@appsmith/ads";
import type { Datasource } from "entities/Datasource";
@ -154,6 +157,27 @@ export const publishApplication = (applicationId: string) => {
};
};
export const publishAnvilApplication = (applicationId: string) => {
return {
type: ReduxActionTypes.PUBLISH_ANVIL_APPLICATION_INIT,
payload: {
applicationId,
},
};
};
export const publishAnvilApplicationSuccess = () => {
return {
type: ReduxActionTypes.PUBLISH_ANVIL_APPLICATION_SUCCESS,
};
};
export const publishAnvilApplicationError = () => {
return {
type: ReduxActionErrorTypes.PUBLISH_ANVIL_APPLICATION_ERROR,
};
};
export const importApplication = (appDetails: ImportApplicationRequest) => {
return {
type: ReduxActionTypes.IMPORT_APPLICATION_INIT,

View File

@ -984,6 +984,8 @@ const AppViewActionTypes = {
FETCH_PUBLISHED_PAGE_RESOURCES_INIT: "FETCH_PUBLISHED_PAGE_RESOURCES_INIT",
FETCH_PUBLISHED_PAGE_RESOURCES_SUCCESS:
"FETCH_PUBLISHED_PAGE_RESOURCES_SUCCESS",
PUBLISH_ANVIL_APPLICATION_INIT: "PUBLISH_ANVIL_APPLICATION_INIT",
PUBLISH_ANVIL_APPLICATION_SUCCESS: "PUBLISH_ANVIL_APPLICATION_SUCCESS",
};
const AppViewActionErrorTypes = {
@ -993,6 +995,7 @@ const AppViewActionErrorTypes = {
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",
PUBLISH_ANVIL_APPLICATION_ERROR: "PUBLISH_ANVIL_APPLICATION_ERROR",
};
const WorkspaceActionTypes = {

View File

@ -4,7 +4,6 @@ import {
ReduxActionTypes,
} from "ee/constants/ReduxActionConstants";
import type {
ApplicationPagePayload,
ApplicationResponsePayload,
ChangeAppViewAccessRequest,
CreateApplicationRequest,
@ -29,7 +28,10 @@ import ApplicationApi from "ee/api/ApplicationApi";
import { all, call, put, select, take } from "redux-saga/effects";
import { validateResponse } from "sagas/ErrorSagas";
import { getCurrentApplicationIdForCreateNewApp } from "ee/selectors/applicationSelectors";
import {
getCurrentApplication,
getCurrentApplicationIdForCreateNewApp,
} from "ee/selectors/applicationSelectors";
import type { ApiResponse } from "api/ApiResponses";
import history from "utils/history";
import type { AppState } from "ee/reducers";
@ -103,6 +105,7 @@ import type { IconNames } from "@appsmith/ads";
import {
defaultNavigationSetting,
keysOfNavigationSetting,
type NavigationSetting,
} from "constants/AppConstants";
import { setAllEntityCollapsibleStates } from "actions/editorContextActions";
import { getCurrentEnvironmentId } from "ee/selectors/environmentSelectors";
@ -115,18 +118,52 @@ import equal from "fast-deep-equal";
import { getFromServerWhenNoPrefetchedResult } from "sagas/helper";
import type { Page } from "entities/Page";
import type { ApplicationPayload } from "entities/Application";
export const findDefaultPage = (pages: ApplicationPagePayload[] = []) => {
const defaultPage = pages.find((page) => page.isDefault) ?? pages[0];
return defaultPage;
};
import { objectKeys } from "@appsmith/utils";
import { findDefaultPage } from "pages/utils";
export let windowReference: Window | null = null;
export function* publishApplicationSaga(
requestAction: ReduxAction<PublishApplicationRequest>,
) {
const currentApplication: ApplicationPayload | undefined = yield select(
getCurrentApplication,
);
if (currentApplication) {
const appName = currentApplication.name;
const appId = currentApplication?.id;
const pageCount = currentApplication?.pages?.length;
const navigationSettingsWithPrefix: Record<
string,
NavigationSetting[keyof NavigationSetting]
> = {};
if (currentApplication.applicationDetail?.navigationSetting) {
const settingKeys = objectKeys(
currentApplication.applicationDetail.navigationSetting,
) as Array<keyof NavigationSetting>;
settingKeys.map((key: keyof NavigationSetting) => {
if (currentApplication.applicationDetail?.navigationSetting?.[key]) {
const value: NavigationSetting[keyof NavigationSetting] =
currentApplication.applicationDetail.navigationSetting[key];
navigationSettingsWithPrefix[`navigationSetting_${key}`] = value;
}
});
}
AnalyticsUtil.logEvent("PUBLISH_APP", {
appId,
appName,
pageCount,
...navigationSettingsWithPrefix,
isPublic: !!currentApplication?.isPublic,
templateTitle: currentApplication?.forkedFromTemplateTitle,
});
}
try {
const request = requestAction.payload;
const response: PublishApplicationResponse = yield call(

View File

@ -1,9 +1,9 @@
import { Button, Tooltip } from "@appsmith/ads";
import { objectKeys } from "@appsmith/utils";
import { showConnectGitModal } from "actions/gitSyncActions";
import { publishApplication } from "ee/actions/applicationActions";
import { getCurrentApplication } from "ee/selectors/applicationSelectors";
import type { NavigationSetting } from "constants/AppConstants";
import {
publishAnvilApplication,
publishApplication,
} from "ee/actions/applicationActions";
import {
createMessage,
DEPLOY_BUTTON_TOOLTIP,
@ -25,6 +25,7 @@ import {
getIsPublishingApplication,
} from "selectors/editorSelectors";
import styled from "styled-components";
import { getIsAnvilEnabledInCurrentApplication } from "layoutSystems/anvil/integrations/selectors";
// This wrapper maintains pointer events for tooltips when the child button is disabled.
// Without this, disabled buttons won't trigger tooltips because they have pointer-events: none
@ -34,7 +35,6 @@ const StyledTooltipTarget = styled.span`
function DeployButton() {
const applicationId = useSelector(getCurrentApplicationId);
const currentApplication = useSelector(getCurrentApplication);
const isPackageUpgrading = useSelector(getIsPackageUpgrading);
const isProtectedMode = useGitProtectedMode();
const isDeployDisabled = isPackageUpgrading || isProtectedMode;
@ -49,42 +49,7 @@ function DeployButton() {
const dispatch = useDispatch();
const handlePublish = useCallback(() => {
if (applicationId) {
dispatch(publishApplication(applicationId));
const appName = currentApplication ? currentApplication.name : "";
const pageCount = currentApplication?.pages?.length;
const navigationSettingsWithPrefix: Record<
string,
NavigationSetting[keyof NavigationSetting]
> = {};
if (currentApplication?.applicationDetail?.navigationSetting) {
const settingKeys = objectKeys(
currentApplication.applicationDetail.navigationSetting,
) as Array<keyof NavigationSetting>;
settingKeys.map((key: keyof NavigationSetting) => {
if (currentApplication?.applicationDetail?.navigationSetting?.[key]) {
const value: NavigationSetting[keyof NavigationSetting] =
currentApplication.applicationDetail.navigationSetting[key];
navigationSettingsWithPrefix[`navigationSetting_${key}`] = value;
}
});
}
AnalyticsUtil.logEvent("PUBLISH_APP", {
appId: applicationId,
appName,
pageCount,
...navigationSettingsWithPrefix,
isPublic: !!currentApplication?.isPublic,
templateTitle: currentApplication?.forkedFromTemplateTitle,
});
}
}, [applicationId, currentApplication, dispatch]);
const isAnvilEnabled = useSelector(getIsAnvilEnabledInCurrentApplication);
const handleClickDeploy = useCallback(() => {
if (isGitConnected) {
@ -97,12 +62,15 @@ function DeployButton() {
AnalyticsUtil.logEvent("GS_DEPLOY_GIT_CLICK", {
source: "Deploy button",
});
} else if (isAnvilEnabled) {
dispatch(publishAnvilApplication(applicationId));
} else {
handlePublish();
dispatch(publishApplication(applicationId));
}
}, [
applicationId,
dispatch,
handlePublish,
isAnvilEnabled,
isGitConnected,
isGitModEnabled,
toggleOpsModal,

View File

@ -42,7 +42,7 @@ import type { Datasource } from "entities/Datasource";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { useQuery } from "../utils";
import ListItemWrapper from "./components/DatasourceListItem";
import { findDefaultPage } from "ee/sagas/ApplicationSagas";
import { findDefaultPage } from "pages/utils";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import {
getOAuthAccessToken,

View File

@ -1,5 +1,6 @@
import { getSearchQuery } from "utils/helpers";
import type { Location } from "history";
import type { ApplicationPagePayload } from "ee/api/ApplicationApi";
export const getIsBranchUpdated = (
prevLocation: Location<unknown>,
@ -29,3 +30,8 @@ export const removeClassFromDocumentRoot = (className: string) => {
element.classList.remove(className);
}
};
export const findDefaultPage = (pages: ApplicationPagePayload[] = []) => {
const defaultPage = pages.find((page) => page.isDefault) ?? pages[0];
return defaultPage;
};

View File

@ -10,7 +10,7 @@ import {
ReduxActionTypes,
} from "ee/constants/ReduxActionConstants";
import urlBuilder from "ee/entities/URLRedirect/URLAssembly";
import { findDefaultPage } from "ee/sagas/ApplicationSagas";
import { findDefaultPage } from "pages/utils";
import { fetchPageDSLSaga } from "ee/sagas/PageSagas";
import { getCurrentWorkspaceId } from "ee/selectors/selectedWorkspaceSelectors";
import { isAirgapped } from "ee/utils/airgapHelpers";

View File

@ -23,9 +23,6 @@ export const getLastJSTab = (state: AppState): FocusEntityInfo | undefined => {
}
};
export const getIsGeneratingSchema = (state: AppState, collectionId: string) =>
state.ui.jsPane.isSchemaGenerating[collectionId];
export const getIsJSCollectionSaving = (
state: AppState,
collectionId: string,