feat: update app via import from within application (#22793)

This commit is contained in:
Sangeeth Sivan 2023-05-01 18:59:26 +05:30 committed by GitHub
parent 68f8d97e19
commit 51e44e6605
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 164 additions and 19 deletions

View File

@ -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,
{

View File

@ -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",

View File

@ -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) {

View File

@ -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) {
>
<TextWrapper>
<Text color={Colors.COD_GRAY} type={TextType.P1}>
{createMessage(
importingApplication
? UPLOADING_JSON
: IMPORT_APPLICATION_MODAL_LABEL,
)}
{toApp
? null
: createMessage(
importingApplication
? UPLOADING_JSON
: IMPORT_APPLICATION_MODAL_LABEL,
)}
</Text>
</TextWrapper>
{!importingApplication && (
<Row>
<FileImportCard className="t--import-json-card">
<FileImportCard className="t--import-json-card" fillCardWidth={toApp}>
<FilePickerV2
containerClickable
description={createMessage(IMPORT_APP_FROM_FILE_MESSAGE)}
@ -331,7 +336,7 @@ function ImportApplicationModal(props: ImportApplicationModalProps) {
uploadIcon="file-line"
/>
</FileImportCard>
<GitImportCard handler={onGitImport} />
{!toApp && <GitImportCard handler={onGitImport} />}
</Row>
)}
{importingApplication && (

View File

@ -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,
}),

View File

@ -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 (
<>
<SettingWrapper>
<StyledText className="setting-header" type={TextType.P1}>
{createMessage(UPDATE_VIA_IMPORT_SETTING.settingHeader)}
</StyledText>
<StyledText type={TextType.P3}>
{isGitConnected
? createMessage(UPDATE_VIA_IMPORT_SETTING.disabledForGit)
: createMessage(UPDATE_VIA_IMPORT_SETTING.settingContent)}
</StyledText>
<Button
className="import-btn"
cypressSelector="t--app-setting-import-btn"
disabled={isGitConnected}
onClick={() => setIsModalOpen(true)}
size="small"
tag="button"
text={createMessage(
UPDATE_VIA_IMPORT_SETTING.settingActionButtonTxt,
).toLocaleUpperCase()}
/>
</SettingWrapper>
<ImportApplicationModal
appId={appId}
isModalOpen={isModalOpen}
onClose={handleClose}
toApp
workspaceId={workspace?.id}
/>
</>
);
}

View File

@ -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 <EmbedSettings />;
case AppSettingsTabs.Navigation:
return <NavigationSettings />;
case AppSettingsTabs.Import:
return <ImportAppSettings />;
}
})()}
</SectionContent>

View File

@ -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());
});