diff --git a/app/client/src/ce/api/ApplicationApi.tsx b/app/client/src/ce/api/ApplicationApi.tsx
index dd95be90be..bd3d302cfd 100644
--- a/app/client/src/ce/api/ApplicationApi.tsx
+++ b/app/client/src/ce/api/ApplicationApi.tsx
@@ -179,6 +179,7 @@ export interface ImportApplicationRequest {
applicationFile?: File;
progress?: (progressEvent: ProgressEvent) => void;
onSuccessCallback?: () => void;
+ appId?: string;
}
export interface AppEmbedSetting {
@@ -341,7 +342,9 @@ export class ApplicationApi extends Api {
formData.append("file", request.applicationFile);
}
return Api.post(
- ApplicationApi.baseURL + "/import/" + request.workspaceId,
+ `${ApplicationApi.baseURL}/import/${request.workspaceId}${
+ request.appId ? `?applicationId=${request.appId}` : ""
+ }`,
formData,
null,
{
diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts
index acfe8ec7a3..4272db8b48 100644
--- a/app/client/src/ce/constants/messages.ts
+++ b/app/client/src/ce/constants/messages.ts
@@ -560,6 +560,8 @@ export const NO_JS_FUNCTION_RETURN_VALUE = (JSFunctionName: string) =>
`${JSFunctionName} did not return any data. Did you add a return statement?`;
// Import/Export Application features
+export const ERROR_IMPORTING_APPLICATION_TO_WORKSPACE = () =>
+ "Error importing application. No workspace found";
export const IMPORT_APPLICATION_MODAL_TITLE = () => "Import application";
export const IMPORT_APPLICATION_MODAL_LABEL = () =>
"Where would you like to import your application from?";
@@ -1476,8 +1478,7 @@ export const APP_SETTINGS_CLOSE_TOOLTIP = () => "Close settings panel";
export const GENERAL_SETTINGS_SECTION_HEADER = () => "General";
export const GENERAL_SETTINGS_SECTION_CONTENT_HEADER = () => "General Settings";
-export const GENERAL_SETTINGS_SECTION_HEADER_DESC = () =>
- "App name, icon and share";
+export const GENERAL_SETTINGS_SECTION_HEADER_DESC = () => "App name and icon";
export const GENERAL_SETTINGS_APP_NAME_LABEL = () => "App Name";
export const GENERAL_SETTINGS_NAME_EMPTY_MESSAGE = () =>
"App name cannot be empty";
@@ -1513,6 +1514,17 @@ export const PAGE_SETTINGS_SET_AS_HOMEPAGE_TOOLTIP_NON_HOME_PAGE = () =>
export const PAGE_SETTINGS_ACTION_NAME_CONFLICT_ERROR = (name: string) =>
`${name} is already being used.`;
+export const UPDATE_VIA_IMPORT_SETTING = {
+ settingHeader: () => "Update through file import",
+ settingDesc: () => "Update app by importing file",
+ settingLabel: () => "Import",
+ settingContent: () =>
+ "This action will override your existing application. Please exercise caution while selecting the file to import.",
+ settingActionButtonTxt: () => "Import",
+ disabledForGit: () =>
+ "This feature is not supported for apps connected to Git version control. Please use Git Pull to update and sync your app.",
+};
+
export const IN_APP_EMBED_SETTING = {
applicationUrl: () => "application url",
allowEmbeddingLabel: () => "Embedding enabled",
diff --git a/app/client/src/ce/sagas/ApplicationSagas.tsx b/app/client/src/ce/sagas/ApplicationSagas.tsx
index 7abfa0cd30..b54168a512 100644
--- a/app/client/src/ce/sagas/ApplicationSagas.tsx
+++ b/app/client/src/ce/sagas/ApplicationSagas.tsx
@@ -59,6 +59,7 @@ import {
DELETING_APPLICATION,
DISCARD_SUCCESS,
DUPLICATING_APPLICATION,
+ ERROR_IMPORTING_APPLICATION_TO_WORKSPACE,
} from "@appsmith/constants/messages";
import type { AppIconName } from "design-system-old";
import { Toaster, Variant } from "design-system-old";
@@ -81,7 +82,10 @@ import {
reconnectAppLevelWebsocket,
reconnectPageLevelWebsocket,
} from "actions/websocketActions";
-import { getCurrentWorkspace } from "@appsmith/selectors/workspaceSelectors";
+import {
+ getCurrentWorkspace,
+ getCurrentWorkspaceId,
+} from "@appsmith/selectors/workspaceSelectors";
import {
getCurrentStep,
@@ -727,20 +731,22 @@ export function* importApplicationSaga(
ApplicationApi.importApplicationToWorkspace,
action.payload,
);
+ const urlObject = new URL(window.location.href);
+ const isApplicationUrl = urlObject.pathname.includes("/app/");
const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) {
+ const currentWorkspaceId: string = yield select(getCurrentWorkspaceId);
const allWorkspaces: Workspace[] = yield select(getCurrentWorkspace);
const currentWorkspace = allWorkspaces.filter(
(el: Workspace) => el.id === action.payload.workspaceId,
);
- if (currentWorkspace.length > 0) {
+ if (currentWorkspaceId || currentWorkspace.length > 0) {
const {
// @ts-expect-error: response is of type unknown
application: { pages },
// @ts-expect-error: response is of type unknown
isPartialImport,
} = response.data;
-
// @ts-expect-error: response is of type unknown
yield put(importApplicationSuccess(response.data?.application));
@@ -758,10 +764,24 @@ export function* importApplicationSaga(
} else {
// @ts-expect-error: pages is of type any
// TODO: Update route params here
- const defaultPage = pages.filter((eachPage) => !!eachPage.isDefault);
+ const { application } = response.data;
+ const defaultPage = pages.filter(
+ (eachPage: any) => !!eachPage.isDefault,
+ );
const pageURL = builderURL({
pageId: defaultPage[0].id,
});
+ if (isApplicationUrl) {
+ const appId = application.id;
+ const pageId = application.defaultPageId;
+ yield put({
+ type: ReduxActionTypes.FETCH_APPLICATION_INIT,
+ payload: {
+ applicationId: appId,
+ pageId,
+ },
+ });
+ }
history.push(pageURL);
const guidedTour: boolean = yield select(inGuidedTour);
@@ -772,6 +792,13 @@ export function* importApplicationSaga(
variant: Variant.success,
});
}
+ } else {
+ yield put({
+ type: ReduxActionErrorTypes.IMPORT_APPLICATION_ERROR,
+ payload: {
+ error: createMessage(ERROR_IMPORTING_APPLICATION_TO_WORKSPACE),
+ },
+ });
}
}
} catch (error) {
diff --git a/app/client/src/pages/Applications/ImportApplicationModal.tsx b/app/client/src/pages/Applications/ImportApplicationModal.tsx
index 22beb1fb18..491884a664 100644
--- a/app/client/src/pages/Applications/ImportApplicationModal.tsx
+++ b/app/client/src/pages/Applications/ImportApplicationModal.tsx
@@ -85,8 +85,8 @@ const Row = styled.div`
}
`;
-const FileImportCard = styled.div`
- width: 320px;
+const FileImportCard = styled.div<{ fillCardWidth: boolean }>`
+ width: ${(props) => (props.fillCardWidth ? "100%" : "320px")};
height: 200px;
border: 1px solid ${Colors.GREY_4};
display: flex;
@@ -234,10 +234,12 @@ type ImportApplicationModalProps = {
workspaceId?: string;
isModalOpen?: boolean;
onClose?: () => void;
+ appId?: string;
+ toApp?: boolean;
};
function ImportApplicationModal(props: ImportApplicationModalProps) {
- const { isModalOpen, onClose, workspaceId } = props;
+ const { appId, isModalOpen, onClose, toApp = false, workspaceId } = props;
const [appFileToBeUploaded, setAppFileToBeUploaded] = useState<{
file: File;
setProgress: SetProgress;
@@ -272,6 +274,7 @@ function ImportApplicationModal(props: ImportApplicationModalProps) {
});
dispatch(
importApplication({
+ appId: appId as string,
workspaceId: workspaceId as string,
applicationFile: file,
}),
@@ -310,16 +313,18 @@ function ImportApplicationModal(props: ImportApplicationModalProps) {
>
- {createMessage(
- importingApplication
- ? UPLOADING_JSON
- : IMPORT_APPLICATION_MODAL_LABEL,
- )}
+ {toApp
+ ? null
+ : createMessage(
+ importingApplication
+ ? UPLOADING_JSON
+ : IMPORT_APPLICATION_MODAL_LABEL,
+ )}
{!importingApplication && (
-
+
-
+ {!toApp && }
)}
{importingApplication && (
diff --git a/app/client/src/pages/Applications/ImportApplicationModalOld.tsx b/app/client/src/pages/Applications/ImportApplicationModalOld.tsx
index 30bd75f235..fb0e666754 100644
--- a/app/client/src/pages/Applications/ImportApplicationModalOld.tsx
+++ b/app/client/src/pages/Applications/ImportApplicationModalOld.tsx
@@ -39,10 +39,11 @@ type ImportApplicationModalProps = {
workspaceId?: string;
isModalOpen?: boolean;
onClose?: () => void;
+ appId?: string;
};
function ImportApplicationModal(props: ImportApplicationModalProps) {
- const { isModalOpen, onClose, workspaceId } = props;
+ const { appId, isModalOpen, onClose, workspaceId } = props;
const [appFileToBeUploaded, setAppFileToBeUploaded] = useState<{
file: File;
setProgress: SetProgress;
@@ -77,6 +78,7 @@ function ImportApplicationModal(props: ImportApplicationModalProps) {
dispatch(
importApplication({
+ appId: appId as string,
workspaceId: workspaceId as string,
applicationFile: file,
}),
diff --git a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/ImportAppSettings.tsx b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/ImportAppSettings.tsx
new file mode 100644
index 0000000000..8b7ec90382
--- /dev/null
+++ b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/ImportAppSettings.tsx
@@ -0,0 +1,75 @@
+import {
+ UPDATE_VIA_IMPORT_SETTING,
+ createMessage,
+} from "@appsmith/constants/messages";
+import { getCurrentAppWorkspace } from "@appsmith/selectors/workspaceSelectors";
+import { Button, Text, TextType } from "design-system-old";
+import ImportApplicationModal from "pages/Applications/ImportApplicationModal";
+import React from "react";
+import { useSelector } from "react-redux";
+import { getCurrentApplicationId } from "selectors/editorSelectors";
+import { getIsGitConnected } from "selectors/gitSyncSelectors";
+import styled from "styled-components";
+
+const SettingWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 16px;
+ color: var(--appsmith-color-black-800);
+
+ .import-btn {
+ width: 40%;
+ }
+`;
+
+const StyledText = styled(Text)`
+ color: var(--appsmith-color-black-800);
+
+ &.setting-header {
+ font-weight: 500;
+ }
+`;
+
+export function ImportAppSettings() {
+ const appId = useSelector(getCurrentApplicationId);
+ const workspace = useSelector(getCurrentAppWorkspace);
+ const isGitConnected = useSelector(getIsGitConnected);
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
+
+ function handleClose() {
+ setIsModalOpen(false);
+ }
+ return (
+ <>
+
+
+ {createMessage(UPDATE_VIA_IMPORT_SETTING.settingHeader)}
+
+
+ {isGitConnected
+ ? createMessage(UPDATE_VIA_IMPORT_SETTING.disabledForGit)
+ : createMessage(UPDATE_VIA_IMPORT_SETTING.settingContent)}
+
+
+
+ >
+ );
+}
diff --git a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/index.tsx b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/index.tsx
index 70cac97eac..f72235a0dd 100644
--- a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/index.tsx
+++ b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/index.tsx
@@ -22,12 +22,14 @@ import {
THEME_SETTINGS_SECTION_CONTENT_HEADER,
THEME_SETTINGS_SECTION_HEADER,
THEME_SETTINGS_SECTION_HEADER_DESC,
+ UPDATE_VIA_IMPORT_SETTING,
} from "@appsmith/constants/messages";
import { Colors } from "constants/Colors";
import EmbedSettings from "./EmbedSettings";
import NavigationSettings from "./NavigationSettings";
import { updateAppSettingsPaneSelectedTabAction } from "actions/appSettingsPaneActions";
import AnalyticsUtil from "utils/AnalyticsUtil";
+import { ImportAppSettings } from "./ImportAppSettings";
export enum AppSettingsTabs {
General,
@@ -35,6 +37,7 @@ export enum AppSettingsTabs {
Theme,
Navigation,
Page,
+ Import,
}
export interface SelectedTab {
@@ -147,6 +150,19 @@ function AppSettings() {
},
subText: createMessage(APP_NAVIGATION_SETTING.sectionHeaderDesc),
},
+ {
+ id: "t--update-via-import",
+ icon: "download-line",
+ isSelected: selectedTab.type === AppSettingsTabs.Import,
+ name: createMessage(UPDATE_VIA_IMPORT_SETTING.settingLabel),
+ onClick: () => {
+ setSelectedTab({ type: AppSettingsTabs.Import });
+ AnalyticsUtil.logEvent("APP_SETTINGS_SECTION_CLICK", {
+ section: "Import",
+ });
+ },
+ subText: createMessage(UPDATE_VIA_IMPORT_SETTING.settingDesc),
+ },
];
return (
@@ -214,6 +230,8 @@ function AppSettings() {
return ;
case AppSettingsTabs.Navigation:
return ;
+ case AppSettingsTabs.Import:
+ return ;
}
})()}
diff --git a/app/client/src/pages/Editor/AppSettingsPane/index.tsx b/app/client/src/pages/Editor/AppSettingsPane/index.tsx
index d14373b5b0..44c541e38e 100644
--- a/app/client/src/pages/Editor/AppSettingsPane/index.tsx
+++ b/app/client/src/pages/Editor/AppSettingsPane/index.tsx
@@ -9,11 +9,14 @@ function AppSettingsPane() {
const dispatch = useDispatch();
const paneRef = useRef(null);
const portalRef = useRef(null);
-
useOnClickOutside([paneRef, portalRef], () => {
if (document.getElementById("save-theme-modal")) return;
if (document.getElementById("delete-theme-modal")) return;
if (document.getElementById("manual-upgrades-modal")) return;
+ // No id property for `Dialog` component, so using class name
+ if (document.querySelector(".t--import-application-modal")) {
+ return;
+ }
dispatch(closeAppSettingsPaneAction());
});