From 51e44e6605e927a504b128b48e178999b7eb512c Mon Sep 17 00:00:00 2001 From: Sangeeth Sivan <74818788+berzerkeer@users.noreply.github.com> Date: Mon, 1 May 2023 18:59:26 +0530 Subject: [PATCH] feat: update app via import from within application (#22793) --- app/client/src/ce/api/ApplicationApi.tsx | 5 +- app/client/src/ce/constants/messages.ts | 16 +++- app/client/src/ce/sagas/ApplicationSagas.tsx | 35 ++++++++- .../Applications/ImportApplicationModal.tsx | 25 ++++--- .../ImportApplicationModalOld.tsx | 4 +- .../AppSettings/ImportAppSettings.tsx | 75 +++++++++++++++++++ .../AppSettingsPane/AppSettings/index.tsx | 18 +++++ .../pages/Editor/AppSettingsPane/index.tsx | 5 +- 8 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 app/client/src/pages/Editor/AppSettingsPane/AppSettings/ImportAppSettings.tsx 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)} + +