chore: added import override CE (#40462)

## Description
- Adds override feature when importing artifacts

EE PR https://github.com/appsmithorg/appsmith-ee/pull/7251

## Automation

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

### 🔍 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/14727350382>
> Commit: 79ceda02e6aee9a078548572f27dcb0ea9b58b52
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14727350382&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Git`
> Spec:
> <hr>Tue, 29 Apr 2025 09:54:34 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 import override modal for handling duplicate artifact
imports from Git, allowing users to override existing artifacts with new
ones.
- Enhanced user messaging with detailed prompts and localized strings
for import override scenarios.

- **Bug Fixes**
- Improved error handling during artifact import, specifically
distinguishing duplicate key errors and providing clearer feedback.

- **Improvements**
- Streamlined artifact import flow, including better state management
and more robust error reporting.
- Refined backend service interfaces and reduced unnecessary
dependencies for improved maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: sondermanish <manish@appsmith.com>
This commit is contained in:
Rudraprasad Das 2025-04-29 14:49:55 +02:00 committed by GitHub
parent 76540c3171
commit 61d48018e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 386 additions and 306 deletions

View File

@ -135,6 +135,7 @@ import { useGitModEnabled } from "pages/Editor/gitSync/hooks/modHooks";
import { import {
GitRepoLimitErrorModal as NewGitRepoLimitErrorModal, GitRepoLimitErrorModal as NewGitRepoLimitErrorModal,
GitImportModal as NewGitImportModal, GitImportModal as NewGitImportModal,
GitImportOverrideModal,
} from "git"; } from "git";
import OldRepoLimitExceededErrorModal from "pages/Editor/gitSync/RepoLimitExceededErrorModal"; import OldRepoLimitExceededErrorModal from "pages/Editor/gitSync/RepoLimitExceededErrorModal";
@ -145,6 +146,7 @@ function GitModals() {
<> <>
<NewGitImportModal /> <NewGitImportModal />
<NewGitRepoLimitErrorModal /> <NewGitRepoLimitErrorModal />
<GitImportOverrideModal />
</> </>
) : ( ) : (
<> <>

View File

@ -4,6 +4,14 @@ export const IMPORT_GIT = {
WAIT_TEXT: "Please wait while we import via Git..", WAIT_TEXT: "Please wait while we import via Git..",
}; };
export const IMPORT_OVERRIDE_MODAL = {
TITLE: "Override existing {{artifactType}}?",
DESCRIPTION:
"{{newArtifactName}} already exists in this workspace as {{oldArtifactName}}. Do you want to override it with the imported {{artifactType}}?",
CANCEL_BTN: "Cancel",
OVERRIDE_BTN: "Override",
};
export const CONNECT_GIT = { export const CONNECT_GIT = {
MODAL_TITLE: "Configure Git", MODAL_TITLE: "Configure Git",
CHOOSE_PROVIDER_CTA: "Configure Git", CHOOSE_PROVIDER_CTA: "Configure Git",

View File

@ -11,6 +11,7 @@ function ImportModal() {
gitImportError, gitImportError,
isGitImportLoading, isGitImportLoading,
isImportModalOpen, isImportModalOpen,
resetGitImport,
toggleImportModal, toggleImportModal,
} = useImport(); } = useImport();
const { const {
@ -24,11 +25,16 @@ function ImportModal() {
const onSubmit = useCallback( const onSubmit = useCallback(
(params: GitImportRequestParams) => { (params: GitImportRequestParams) => {
gitImport(params); gitImport({ ...params, override: false });
}, },
[gitImport], [gitImport],
); );
const resetConnectState = useCallback(() => {
resetGlobalSSHKey();
resetGitImport();
}, [resetGitImport, resetGlobalSSHKey]);
return ( return (
<ConnectModalView <ConnectModalView
artifactType="artifact" artifactType="artifact"
@ -41,7 +47,7 @@ function ImportModal() {
onGenerateSSHKey={fetchGlobalSSHKey} onGenerateSSHKey={fetchGlobalSSHKey}
onOpenImport={null} onOpenImport={null}
onSubmit={onSubmit} onSubmit={onSubmit}
resetConnectState={resetGlobalSSHKey} resetConnectState={resetConnectState}
sshPublicKey={sshPublicKey} sshPublicKey={sshPublicKey}
toggleModalOpen={toggleImportModal} toggleModalOpen={toggleImportModal}
/> />

View File

@ -0,0 +1,86 @@
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Text,
} from "@appsmith/ads";
import { IMPORT_OVERRIDE_MODAL } from "git/ee/constants/messages";
import useMessage from "git/hooks/useMessage";
import noop from "lodash/noop";
import React, { useCallback } from "react";
import styled from "styled-components";
const StyledModalContent = styled(ModalContent)`
width: 640px;
`;
interface ImportOverrideModalViewProps {
artifactType?: string;
isImportLoading: boolean;
isOpen: boolean;
newArtifactName: string | null;
oldArtifactName: string | null;
onImport: () => void;
onOpenChange: (open: boolean) => void;
}
function ImportOverrideModalView({
artifactType = "artifact",
isImportLoading = false,
isOpen = false,
newArtifactName = null,
oldArtifactName = null,
onImport = noop,
onOpenChange = noop,
}: ImportOverrideModalViewProps) {
const modalTitle = useMessage(IMPORT_OVERRIDE_MODAL.TITLE, { artifactType });
const modalDescription = useMessage(IMPORT_OVERRIDE_MODAL.DESCRIPTION, {
newArtifactName: newArtifactName ?? "",
oldArtifactName: oldArtifactName ?? "",
artifactType,
});
const ctaBtnText = useMessage(IMPORT_OVERRIDE_MODAL.OVERRIDE_BTN, {
artifactType,
});
const handleCancel = useCallback(() => {
onOpenChange(false);
}, [onOpenChange]);
const handleImport = useCallback(() => {
if (!isImportLoading) {
onImport();
}
}, [isImportLoading, onImport]);
return (
<Modal onOpenChange={onOpenChange} open={isOpen}>
<StyledModalContent>
<ModalHeader>{modalTitle}</ModalHeader>
<ModalBody>
<Text kind="body-m" renderAs="p">
{modalDescription}
</Text>
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={handleCancel} size="md">
{IMPORT_OVERRIDE_MODAL.CANCEL_BTN}
</Button>
<Button
isLoading={isImportLoading}
onClick={handleImport}
size="md"
type="submit"
>
{ctaBtnText}
</Button>
</ModalFooter>
</StyledModalContent>
</Modal>
);
}
export default ImportOverrideModalView;

View File

@ -0,0 +1,46 @@
import React, { useCallback } from "react";
import ImportOverrideModalView from "./ImportOverrideModalView";
import useImport from "git/hooks/useImport";
function ImportOverrideModal() {
const {
gitImport,
importOverrideDetails,
isGitImportLoading,
isImportOverrideModalOpen,
resetGitImport,
resetImportOverrideDetails,
} = useImport();
const handleOpenChange = useCallback(
(open: boolean) => {
if (!open && !isGitImportLoading) {
resetImportOverrideDetails();
resetGitImport();
}
},
[isGitImportLoading, resetGitImport, resetImportOverrideDetails],
);
const handleImport = useCallback(() => {
if (importOverrideDetails) {
const params = { ...importOverrideDetails.params, override: true };
gitImport(params);
}
}, [gitImport, importOverrideDetails]);
return (
<ImportOverrideModalView
artifactType={"package"}
isImportLoading={isGitImportLoading}
isOpen={isImportOverrideModalOpen}
newArtifactName={importOverrideDetails?.newArtifactName ?? null}
oldArtifactName={importOverrideDetails?.oldArtifactName ?? null}
onImport={handleImport}
onOpenChange={handleOpenChange}
/>
);
}
export default ImportOverrideModal;

View File

@ -37,4 +37,5 @@ export enum GitErrorCodes {
REPO_NOT_EMPTY = "AE-GIT-4033", REPO_NOT_EMPTY = "AE-GIT-4033",
REPO_LIMIT_REACHED = "AE-GIT-4043", REPO_LIMIT_REACHED = "AE-GIT-4043",
PUSH_FAILED_REMOTE_COUNTERPART_IS_AHEAD = "AE-GIT-4048", PUSH_FAILED_REMOTE_COUNTERPART_IS_AHEAD = "AE-GIT-4048",
DUPLICATE_ARTIFACT_OVERRIDE = "AE-GIT-5004",
} }

View File

@ -3,9 +3,12 @@ import { useDispatch, useSelector } from "react-redux";
import { import {
selectGitImportState, selectGitImportState,
selectImportModalOpen, selectImportModalOpen,
selectImportOverrideDetails,
selectImportOverrideModalOpen,
} from "git/store/selectors/gitGlobalSelectors"; } from "git/store/selectors/gitGlobalSelectors";
import { gitGlobalActions } from "git/store/gitGlobalSlice"; import { gitGlobalActions } from "git/store/gitGlobalSlice";
import type { GitImportRequestParams } from "git/requests/gitImportRequest.types"; import type { GitImportRequestParams } from "git/requests/gitImportRequest.types";
import type { SetImportOverrideDetailsPayload } from "git/store/actions/uiActions";
export default function useImport() { export default function useImport() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -19,6 +22,10 @@ export default function useImport() {
[dispatch], [dispatch],
); );
const resetGitImport = useCallback(() => {
dispatch(gitGlobalActions.resetGitImport());
}, [dispatch]);
const isImportModalOpen = useSelector(selectImportModalOpen); const isImportModalOpen = useSelector(selectImportModalOpen);
const toggleImportModal = useCallback( const toggleImportModal = useCallback(
@ -28,11 +35,31 @@ export default function useImport() {
[dispatch], [dispatch],
); );
const isImportOverrideModalOpen = useSelector(selectImportOverrideModalOpen);
const importOverrideDetails = useSelector(selectImportOverrideDetails);
const setImportOverrideDetails = useCallback(
(details: SetImportOverrideDetailsPayload) => {
dispatch(gitGlobalActions.setImportOverrideDetails(details));
},
[dispatch],
);
const resetImportOverrideDetails = useCallback(() => {
dispatch(gitGlobalActions.resetImportOverrideDetails());
}, [dispatch]);
return { return {
isGitImportLoading: gitImportState?.loading ?? false, isGitImportLoading: gitImportState?.loading ?? false,
gitImportError: gitImportState?.error ?? null, gitImportError: gitImportState?.error ?? null,
gitImport, gitImport,
resetGitImport,
isImportModalOpen: isImportModalOpen ?? false, isImportModalOpen: isImportModalOpen ?? false,
toggleImportModal, toggleImportModal,
isImportOverrideModalOpen: isImportOverrideModalOpen ?? false,
importOverrideDetails,
setImportOverrideDetails,
resetImportOverrideDetails,
}; };
} }

View File

@ -0,0 +1,12 @@
import { useMemo } from "react";
export default function useMessage(msg: string, args: Record<string, string>) {
return useMemo(() => {
const msgWithArgs = msg.replace(/\{\{([^}]+)\}\}/g, (match, p1) => {
// p1 is the key from {{key}} in the message
return args[p1] || match;
});
return msgWithArgs;
}, [msg, args]);
}

View File

@ -6,6 +6,7 @@ export { useHotKeys } from "./components/HotKeys";
export { default as GitContextProvider } from "./components/GitContextProvider"; export { default as GitContextProvider } from "./components/GitContextProvider";
export { default as GitModals } from "./ee/components/GitModals"; export { default as GitModals } from "./ee/components/GitModals";
export { default as GitImportModal } from "./components/ImportModal"; export { default as GitImportModal } from "./components/ImportModal";
export { default as GitImportOverrideModal } from "./components/ImportOverrideModal";
export { default as GitRepoLimitErrorModal } from "./components/RepoLimitErrorModal"; export { default as GitRepoLimitErrorModal } from "./components/RepoLimitErrorModal";
export { default as GitQuickActions } from "./components/QuickActions"; export { default as GitQuickActions } from "./components/QuickActions";
export { default as GitProtectedBranchCallout } from "./components/ProtectedBranchCallout"; export { default as GitProtectedBranchCallout } from "./components/ProtectedBranchCallout";

View File

@ -17,7 +17,10 @@ async function gitImportRequestNew(
workspaceId: string, workspaceId: string,
params: GitImportRequestParams, params: GitImportRequestParams,
): AxiosPromise<GitImportResponse> { ): AxiosPromise<GitImportResponse> {
return Api.post(`${GIT_BASE_URL}/artifacts/import`, params, { workspaceId }); const { override = false, ...restParams } = params;
const body = { override, ...restParams };
return Api.post(`${GIT_BASE_URL}/artifacts/import`, body, { workspaceId });
} }
export default async function gitImportRequest( export default async function gitImportRequest(

View File

@ -9,6 +9,7 @@ export interface GitImportRequestParams {
authorEmail: string; authorEmail: string;
useDefaultProfile?: boolean; useDefaultProfile?: boolean;
}; };
override?: boolean;
} }
export interface GitImportResponseData { export interface GitImportResponseData {

View File

@ -2,13 +2,17 @@ import { call, put, select } from "redux-saga/effects";
import { validateResponse } from "sagas/ErrorSagas"; import { validateResponse } from "sagas/ErrorSagas";
import type { PayloadAction } from "@reduxjs/toolkit"; import type { PayloadAction } from "@reduxjs/toolkit";
import gitImportRequest from "git/requests/gitImportRequest"; import gitImportRequest from "git/requests/gitImportRequest";
import type { GitImportResponse } from "git/requests/gitImportRequest.types"; import type {
GitImportRequestParams,
GitImportResponse,
} from "git/requests/gitImportRequest.types";
import type { GitImportInitPayload } from "git/store/actions/gitImportActions"; import type { GitImportInitPayload } from "git/store/actions/gitImportActions";
import { gitGlobalActions } from "git/store/gitGlobalSlice"; import { gitGlobalActions } from "git/store/gitGlobalSlice";
import { getWorkspaceIdForImport } from "ee/selectors/applicationSelectors"; import { getWorkspaceIdForImport } from "ee/selectors/applicationSelectors";
import { GitErrorCodes } from "git/constants/enums"; import { GitErrorCodes } from "git/constants/enums";
import { selectGitApiContractsEnabled } from "git/store/selectors/gitFeatureFlagSelectors"; import { selectGitApiContractsEnabled } from "git/store/selectors/gitFeatureFlagSelectors";
import handleApiErrors from "./helpers/handleApiErrors"; import handleApiErrors from "./helpers/handleApiErrors";
import type { GitApiError } from "git/store/types";
export default function* gitImportSaga( export default function* gitImportSaga(
action: PayloadAction<GitImportInitPayload>, action: PayloadAction<GitImportInitPayload>,
@ -36,6 +40,7 @@ export default function* gitImportSaga(
gitGlobalActions.gitImportSuccess({ responseData: response.data }), gitGlobalActions.gitImportSuccess({ responseData: response.data }),
); );
yield put(gitGlobalActions.toggleImportModal({ open: false })); yield put(gitGlobalActions.toggleImportModal({ open: false }));
yield put(gitGlobalActions.resetImportOverrideDetails());
} }
} catch (e) { } catch (e) {
const error = handleApiErrors(e as Error, response); const error = handleApiErrors(e as Error, response);
@ -47,6 +52,38 @@ export default function* gitImportSaga(
yield put(gitGlobalActions.toggleImportModal({ open: false })); yield put(gitGlobalActions.toggleImportModal({ open: false }));
yield put(gitGlobalActions.toggleRepoLimitErrorModal({ open: true })); yield put(gitGlobalActions.toggleRepoLimitErrorModal({ open: true }));
} }
if (GitErrorCodes.DUPLICATE_ARTIFACT_OVERRIDE === error.code) {
yield call(handleDuplicateArtifactOverride, error, params);
}
} }
} }
} }
function* handleDuplicateArtifactOverride(
error: GitApiError,
params: GitImportRequestParams,
) {
yield put(gitGlobalActions.toggleImportModal({ open: false }));
let artifactNames = { newArtifactName: null, oldArtifactName: null };
if (error?.message) {
const jsonMatch = error.message.match(/\{.*\}/);
const jsonStr = jsonMatch ? jsonMatch[0] : null;
if (jsonStr) {
try {
artifactNames = JSON.parse(jsonStr);
} catch {}
}
}
yield put(
gitGlobalActions.setImportOverrideDetails({
params,
oldArtifactName: artifactNames.oldArtifactName ?? "",
newArtifactName: artifactNames.newArtifactName ?? "",
}),
);
}

View File

@ -48,3 +48,10 @@ export const gitImportErrorAction = (
return state; return state;
}; };
export const resetGitImportAction = (state: GitGlobalReduxState) => {
state.gitImport.loading = false;
state.gitImport.error = null;
return state;
};

View File

@ -3,6 +3,7 @@ import { createArtifactAction } from "../helpers/createArtifactAction";
import type { GitGlobalReduxState } from "../types"; import type { GitGlobalReduxState } from "../types";
import type { PayloadAction } from "@reduxjs/toolkit"; import type { PayloadAction } from "@reduxjs/toolkit";
import type { GitArtifactDef } from "git/types"; import type { GitArtifactDef } from "git/types";
import type { GitImportRequestParams } from "git/requests/gitImportRequest.types";
// connect modal // connect modal
export interface ToggleConnectModalPayload { export interface ToggleConnectModalPayload {
@ -31,6 +32,7 @@ export const toggleConnectSuccessModalAction =
return state; return state;
}); });
// import
export interface ToggleImportModalPayload { export interface ToggleImportModalPayload {
open: boolean; open: boolean;
} }
@ -46,6 +48,29 @@ export const toggleImportModalAction = (
return state; return state;
}; };
export const resetImportOverrideDetailsAction = (
state: GitGlobalReduxState,
) => {
state.importOverrideDetails = null;
return state;
};
export interface SetImportOverrideDetailsPayload {
params: GitImportRequestParams;
oldArtifactName: string;
newArtifactName: string;
}
export const setImportOverrideDetailsAction = (
state: GitGlobalReduxState,
action: PayloadAction<SetImportOverrideDetailsPayload>,
) => {
state.importOverrideDetails = { ...action.payload };
return state;
};
// disconnect modal // disconnect modal
export interface OpenDisconnectModalPayload { export interface OpenDisconnectModalPayload {
targetArtifactDef: GitArtifactDef; targetArtifactDef: GitArtifactDef;

View File

@ -10,11 +10,16 @@ import {
updateGlobalProfileSuccessAction, updateGlobalProfileSuccessAction,
} from "./actions/updateGlobalProfileActions"; } from "./actions/updateGlobalProfileActions";
import { gitGlobalInitialState } from "./helpers/initialState"; import { gitGlobalInitialState } from "./helpers/initialState";
import { toggleImportModalAction } from "./actions/uiActions"; import {
resetImportOverrideDetailsAction,
setImportOverrideDetailsAction,
toggleImportModalAction,
} from "./actions/uiActions";
import { import {
gitImportErrorAction, gitImportErrorAction,
gitImportInitAction, gitImportInitAction,
gitImportSuccessAction, gitImportSuccessAction,
resetGitImportAction,
} from "./actions/gitImportActions"; } from "./actions/gitImportActions";
import { import {
fetchGlobalSSHKeyErrorAction, fetchGlobalSSHKeyErrorAction,
@ -41,7 +46,10 @@ export const gitGlobalSlice = createSlice({
gitImportInit: gitImportInitAction, gitImportInit: gitImportInitAction,
gitImportSuccess: gitImportSuccessAction, gitImportSuccess: gitImportSuccessAction,
gitImportError: gitImportErrorAction, gitImportError: gitImportErrorAction,
resetGitImport: resetGitImportAction,
toggleImportModal: toggleImportModalAction, toggleImportModal: toggleImportModalAction,
resetImportOverrideDetails: resetImportOverrideDetailsAction,
setImportOverrideDetails: setImportOverrideDetailsAction,
toggleRepoLimitErrorModal: toggleRepoLimitErrorModalAction, toggleRepoLimitErrorModal: toggleRepoLimitErrorModalAction,
}, },
}); });

View File

@ -168,5 +168,6 @@ export const gitGlobalInitialState: GitGlobalReduxState = {
error: null, error: null,
}, },
isImportModalOpen: false, isImportModalOpen: false,
importOverrideDetails: null,
repoLimitErrorModalOpen: false, repoLimitErrorModalOpen: false,
}; };

View File

@ -14,6 +14,12 @@ export const selectUpdateGlobalProfileState = (state: GitRootState) =>
export const selectImportModalOpen = (state: GitRootState) => export const selectImportModalOpen = (state: GitRootState) =>
selectGitGlobal(state).isImportModalOpen; selectGitGlobal(state).isImportModalOpen;
export const selectImportOverrideModalOpen = (state: GitRootState) =>
!!selectGitGlobal(state).importOverrideDetails;
export const selectImportOverrideDetails = (state: GitRootState) =>
selectGitGlobal(state).importOverrideDetails ?? null;
export const selectGitImportState = (state: GitRootState) => export const selectGitImportState = (state: GitRootState) =>
selectGitGlobal(state).gitImport; selectGitGlobal(state).gitImport;

View File

@ -20,6 +20,7 @@ import type { FetchGlobalSSHKeyResponseData } from "git/requests/fetchGlobalSSHK
import type { FetchRefsResponseData } from "git/requests/fetchRefsRequest.types"; import type { FetchRefsResponseData } from "git/requests/fetchRefsRequest.types";
import type { GitArtifactDef } from "git/types"; import type { GitArtifactDef } from "git/types";
import type { PretagResponseData } from "git/requests/pretagRequest.types"; import type { PretagResponseData } from "git/requests/pretagRequest.types";
import type { GitImportRequestParams } from "git/requests/gitImportRequest.types";
export interface GitApiError extends ApiResponseError { export interface GitApiError extends ApiResponseError {
errorType?: string; errorType?: string;
@ -98,6 +99,11 @@ export interface GitGlobalReduxState {
globalSSHKey: GitAsyncState<FetchGlobalSSHKeyResponseData>; globalSSHKey: GitAsyncState<FetchGlobalSSHKeyResponseData>;
// ui // ui
isImportModalOpen: boolean; isImportModalOpen: boolean;
importOverrideDetails: {
params: GitImportRequestParams;
oldArtifactName: string;
newArtifactName: string;
} | null;
repoLimitErrorModalOpen: boolean; repoLimitErrorModalOpen: boolean;
} }

View File

@ -84,7 +84,7 @@ import static com.appsmith.git.constants.ce.CommonConstantsCE.DELIMITER_PATH;
@Import({GitServiceConfig.class}) @Import({GitServiceConfig.class})
public class FileUtilsCEImpl implements FileInterface { public class FileUtilsCEImpl implements FileInterface {
private final GitServiceConfig gitServiceConfig; protected final GitServiceConfig gitServiceConfig;
protected final FSGitHandler fsGitHandler; protected final FSGitHandler fsGitHandler;
private final GitExecutor gitExecutor; private final GitExecutor gitExecutor;
protected final FileOperations fileOperations; protected final FileOperations fileOperations;
@ -98,7 +98,7 @@ public class FileUtilsCEImpl implements FileInterface {
private static final Pattern ALLOWED_FILE_EXTENSION_PATTERN = private static final Pattern ALLOWED_FILE_EXTENSION_PATTERN =
Pattern.compile("(.*?)\\.(md|MD|git|gitignore|github|yml|yaml)$"); Pattern.compile("(.*?)\\.(md|MD|git|gitignore|github|yml|yaml)$");
private final Scheduler scheduler = Schedulers.boundedElastic(); protected final Scheduler scheduler = Schedulers.boundedElastic();
private static final String CANVAS_WIDGET = "(Canvas)[0-9]*."; private static final String CANVAS_WIDGET = "(Canvas)[0-9]*.";
@ -1250,6 +1250,12 @@ public class FileUtilsCEImpl implements FileInterface {
return metadataMono.subscribeOn(scheduler); return metadataMono.subscribeOn(scheduler);
} }
@Override
public Mono<Object> reconstructPackageJsonFromGitRepository(Path repoSuffix) {
return Mono.error(
new AppsmithPluginException(AppsmithPluginError.PLUGIN_UNSUPPORTED_OPERATION, "package json creation"));
}
@Override @Override
public Mono<Object> reconstructMetadataFromGitRepository(Path repoSuffix) { public Mono<Object> reconstructMetadataFromGitRepository(Path repoSuffix) {
Mono<Object> metadataMono = Mono.fromCallable(() -> { Mono<Object> metadataMono = Mono.fromCallable(() -> {

View File

@ -77,6 +77,8 @@ public interface FileInterface {
Mono<Object> reconstructMetadataFromGitRepository(Path repoSuffix); Mono<Object> reconstructMetadataFromGitRepository(Path repoSuffix);
Mono<Object> reconstructPackageJsonFromGitRepository(Path repoSuffix);
Mono<Object> reconstructPageFromGitRepo( Mono<Object> reconstructPageFromGitRepo(
String pageName, String branchName, Path repoSuffixPath, Boolean checkoutRequired); String pageName, String branchName, Path repoSuffixPath, Boolean checkoutRequired);

View File

@ -12,6 +12,7 @@ public enum ErrorType {
BAD_REQUEST, BAD_REQUEST,
INTERNAL_ERROR, INTERNAL_ERROR,
ACTION_CONFIGURATION_ERROR, ACTION_CONFIGURATION_ERROR,
ARTIFACT_IMPORT_ERROR,
GIT_CONFIGURATION_ERROR, GIT_CONFIGURATION_ERROR,
GIT_ACTION_EXECUTION_ERROR, GIT_ACTION_EXECUTION_ERROR,
GIT_UPSTREAM_CHANGES_PUSH_EXECUTION_ERROR, GIT_UPSTREAM_CHANGES_PUSH_EXECUTION_ERROR,

View File

@ -11,4 +11,13 @@ public class GitConnectDTO {
String remoteUrl; String remoteUrl;
GitProfile gitProfile; GitProfile gitProfile;
/**
* This attribute has been placed specifically for packages,
* In multi instance setup, the packages in PROD are present in non git connected state.
* Once the DEV package connects to git, the prod would also require the git connected package
* however importing a new package in PROD workspace would cause problems, and we can't delete existing
* consumed packages, hence we would override prod package with same UUID.
*/
Boolean override;
} }

View File

@ -876,6 +876,14 @@ public enum AppsmithError {
"Duplicate Configuration", "Duplicate Configuration",
ErrorType.BAD_REQUEST, ErrorType.BAD_REQUEST,
null), null),
ARTIFACT_IMPORT_DUPLICATE_KEY_WRITE_ERROR(
500,
AppsmithErrorCode.ARTIFACT_IMPORT_DUPLICATE_KEY_WRITE_ERROR.getCode(),
"Duplicate key detected while importing artifact into the workspace during a write operation to the server.. Details: {0}",
AppsmithErrorAction.DEFAULT,
"Import operation failed because of a duplicate key error.",
ErrorType.ARTIFACT_IMPORT_ERROR,
null),
INVALID_PROPERTIES_CONFIGURATION( INVALID_PROPERTIES_CONFIGURATION(
500, 500,
AppsmithErrorCode.INVALID_PROPERTIES_CONFIGURATION.getCode(), AppsmithErrorCode.INVALID_PROPERTIES_CONFIGURATION.getCode(),

View File

@ -103,6 +103,8 @@ public enum AppsmithErrorCode {
JSON_PROCESSING_ERROR("AE-JSN-4001", "Json processing error"), JSON_PROCESSING_ERROR("AE-JSN-4001", "Json processing error"),
INCOMPATIBLE_IMPORTED_JSON("AE-JSN-4045", "Incompatible imported json"), INCOMPATIBLE_IMPORTED_JSON("AE-JSN-4045", "Incompatible imported json"),
GENERIC_JSON_IMPORT_ERROR("AE-JSN-4049", "Generic json import error"), GENERIC_JSON_IMPORT_ERROR("AE-JSN-4049", "Generic json import error"),
ARTIFACT_IMPORT_DUPLICATE_KEY_WRITE_ERROR(
"AE-JSN-5001", "Artifact import failed due to a duplicate key conflict during write operation"),
INVALID_LOGIN_METHOD("AE-LGN-4000", "Invalid login method"), INVALID_LOGIN_METHOD("AE-LGN-4000", "Invalid login method"),
PLUGIN_NOT_INSTALLED("AE-PLG-4001", "Plugin not installed"), PLUGIN_NOT_INSTALLED("AE-PLG-4001", "Plugin not installed"),
PLUGIN_ID_NOT_GIVEN("AE-PLG-4002", "Plugin id not given"), PLUGIN_ID_NOT_GIVEN("AE-PLG-4002", "Plugin id not given"),

View File

@ -21,9 +21,6 @@ import java.util.List;
public interface CentralGitServiceCE { public interface CentralGitServiceCE {
Mono<? extends ArtifactImportDTO> importArtifactFromGit(
String workspaceId, GitConnectDTO gitConnectDTO, ArtifactType artifactType, GitType gitType);
Mono<? extends ArtifactImportDTO> importArtifactFromGit( Mono<? extends ArtifactImportDTO> importArtifactFromGit(
String workspaceId, GitConnectDTO gitConnectDTO, GitType gitType); String workspaceId, GitConnectDTO gitConnectDTO, GitType gitType);

View File

@ -161,11 +161,11 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
final String repoName = gitHandlingService.getRepoName(gitConnectDTO); final String repoName = gitHandlingService.getRepoName(gitConnectDTO);
// since at this point in the import flow, there is no context about the artifact type // since at this point in the import flow, there is no context about the artifact type
// it needs to be retrieved from the fetched repository itself. however, in order to retrieve // it needs to be retrieved from the fetched repository itself. however, to retrieve
// the artifact type from repository, the repository needs to be saved. // the artifact type from the repository, the repository needs to be saved.
// for saving the repo an identifier is required (which usually is the artifact id); // for saving the repository, an identifier is required (which usually is the artifact id);
// however, the artifact could only be generated after the artifact type is known. // however, the artifact could only be generated after the artifact type is known.
// hence this is a temporary placeholder to hold the repository and it's components // hence this is a temporary placeholder to hold the repository and its components
String placeholder = "temp" + UUID.randomUUID(); String placeholder = "temp" + UUID.randomUUID();
ArtifactJsonTransformationDTO tempJsonTransformationDTO = ArtifactJsonTransformationDTO tempJsonTransformationDTO =
new ArtifactJsonTransformationDTO(workspaceId, placeholder, repoName); new ArtifactJsonTransformationDTO(workspaceId, placeholder, repoName);
@ -203,7 +203,7 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
.flatMap(defaultBranch -> { .flatMap(defaultBranch -> {
return Mono.zip( return Mono.zip(
Mono.just(defaultBranch), Mono.just(defaultBranch),
gitHandlingService.obtainArtifactTypeFromGitRepository( gitHandlingService.obtainArtifactTypeAndIdentifierFromGitRepository(
tempJsonTransformationDTO), tempJsonTransformationDTO),
isRepositoryPrivateMonoCached, isRepositoryPrivateMonoCached,
Mono.just(gitAuth)); Mono.just(gitAuth));
@ -211,7 +211,8 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
}) })
.flatMap(tuple4 -> { .flatMap(tuple4 -> {
String defaultBranch = tuple4.getT1(); String defaultBranch = tuple4.getT1();
ArtifactType artifactType = tuple4.getT2(); ArtifactType artifactType = tuple4.getT2().getT1();
String uniqueIdentifier = tuple4.getT2().getT2();
Boolean isRepoPrivate = tuple4.getT3(); Boolean isRepoPrivate = tuple4.getT3();
GitAuth gitAuth = tuple4.getT4(); GitAuth gitAuth = tuple4.getT4();
@ -224,7 +225,8 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
AppsmithError.NO_RESOURCE_FOUND, FieldName.WORKSPACE, workspaceId))); AppsmithError.NO_RESOURCE_FOUND, FieldName.WORKSPACE, workspaceId)));
return workspaceMono return workspaceMono
.flatMap(workspace -> contextHelper.createArtifactForImport(workspaceId, repoName)) .flatMap(workspace -> createArtifactForGitConnect(
gitConnectDTO, artifactType, workspaceId, repoName, uniqueIdentifier))
.map(baseArtifact -> { .map(baseArtifact -> {
GitArtifactMetadata gitArtifactMetadata = new GitArtifactMetadata(); GitArtifactMetadata gitArtifactMetadata = new GitArtifactMetadata();
gitArtifactMetadata.setGitAuth(gitAuth); gitArtifactMetadata.setGitAuth(gitAuth);
@ -240,6 +242,12 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
baseArtifact.setGitArtifactMetadata(gitArtifactMetadata); baseArtifact.setGitArtifactMetadata(gitArtifactMetadata);
return baseArtifact; return baseArtifact;
}); });
})
.onErrorResume(error -> {
Mono<Boolean> removeRepositoryMono =
gitHandlingService.removeRepository(tempJsonTransformationDTO, TRUE);
return removeRepositoryMono.then(Mono.error(error));
}); });
Mono<? extends Artifact> containerArtifactForImport = Mono.usingWhen( Mono<? extends Artifact> containerArtifactForImport = Mono.usingWhen(
@ -267,7 +275,7 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
GitArtifactHelper<?> gitArtifactHelper = GitArtifactHelper<?> gitArtifactHelper =
gitArtifactHelperResolver.getArtifactHelper(baseArtifact.getArtifactType()); gitArtifactHelperResolver.getArtifactHelper(baseArtifact.getArtifactType());
Mono<List<Datasource>> datasourceMono = datasourceService Mono<List<Datasource>> existingWorkspaceDatasourceMono = datasourceService
.getAllByWorkspaceIdWithStorages(workspaceId, datasourcePermission.getEditPermission()) .getAllByWorkspaceIdWithStorages(workspaceId, datasourcePermission.getEditPermission())
.collectList(); .collectList();
@ -282,7 +290,7 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
new AppsmithException(AppsmithError.GIT_FILE_SYSTEM_ERROR, error.getMessage())); new AppsmithException(AppsmithError.GIT_FILE_SYSTEM_ERROR, error.getMessage()));
}); });
return Mono.zip(artifactExchangeJsonMono, datasourceMono, pluginMono) return Mono.zip(artifactExchangeJsonMono, existingWorkspaceDatasourceMono, pluginMono)
.flatMap(data -> { .flatMap(data -> {
ArtifactExchangeJson artifactExchangeJson = data.getT1(); ArtifactExchangeJson artifactExchangeJson = data.getT1();
List<Datasource> datasourceList = data.getT2(); List<Datasource> datasourceList = data.getT2();
@ -312,7 +320,14 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
.importArtifactInWorkspaceFromGit( .importArtifactInWorkspaceFromGit(
workspaceId, baseArtifact.getId(), artifactExchangeJson, defaultBranch) workspaceId, baseArtifact.getId(), artifactExchangeJson, defaultBranch)
.onErrorResume(throwable -> { .onErrorResume(throwable -> {
log.error("Error in importing the artifact {}", baseArtifact.getId()); log.error(
"Error in importing the artifact {}",
baseArtifact.getId(),
throwable);
if (throwable instanceof AppsmithException) {
return Mono.error(throwable);
}
return Mono.error(new AppsmithException( return Mono.error(new AppsmithException(
AppsmithError.GIT_FILE_SYSTEM_ERROR, throwable.getMessage())); AppsmithError.GIT_FILE_SYSTEM_ERROR, throwable.getMessage()));
}); });
@ -343,183 +358,18 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
sink -> importGitArtifactMono.subscribe(sink::success, sink::error, null, sink.currentContext())); sink -> importGitArtifactMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
} }
protected Mono<Artifact> hydrateLatest(Artifact artifact) { protected Mono<? extends Artifact> createArtifactForGitConnect(
return Mono.just(artifact); GitConnectDTO gitConnectDTO,
ArtifactType artifactType,
String workspaceId,
String repoName,
String uniqueIdentifier) {
GitArtifactHelper<?> contextHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType);
return contextHelper.createArtifactForImport(workspaceId, repoName);
} }
@Override protected Mono<Artifact> hydrateLatest(Artifact artifact) {
public Mono<? extends ArtifactImportDTO> importArtifactFromGit( return Mono.just(artifact);
String workspaceId, GitConnectDTO gitConnectDTO, ArtifactType artifactType, GitType gitType) {
// 1. Check private repo limit for workspace
// 2. Create dummy artifact, clone repo from remote
// 3. Re-hydrate artifact to DB from local repo
// a. Save the ssh keys in artifact object with other details
// b. During import-export need to handle the DS(empty vs non-empty)
// 4. Return artifact
if (!StringUtils.hasText(workspaceId)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "Invalid workspace id"));
}
GitHandlingService gitHandlingService = gitHandlingServiceResolver.getGitHandlingService(gitType);
Set<String> errors = gitHandlingService.validateGitConnectDTO(gitConnectDTO);
if (!CollectionUtils.isEmpty(errors)) {
return Mono.error(new AppsmithException(
AppsmithError.INVALID_PARAMETER, errors.stream().findAny().get()));
}
GitArtifactHelper<?> gitArtifactHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType);
AclPermission artifactCreatePermission = gitArtifactHelper.getWorkspaceArtifactCreationPermission();
// TODO: permission bit deferred to gitArtifactHelper
Mono<Workspace> workspaceMono = workspaceService
.findById(workspaceId, artifactCreatePermission)
.switchIfEmpty(Mono.error(
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.WORKSPACE, workspaceId)));
final String repoName = gitHandlingService.getRepoName(gitConnectDTO);
Mono<Boolean> isRepositoryPrivateMono =
gitHandlingService.isRepoPrivate(gitConnectDTO).cache();
Mono<Boolean> isRepositoryLimitReachedForWorkspaceMono = isRepositoryPrivateMono.flatMap(
isRepositoryPrivate -> isRepositoryLimitReachedForWorkspace(workspaceId, isRepositoryPrivate));
Mono<? extends ArtifactImportDTO> importedArtifactMono = workspaceMono
.then(Mono.defer(() -> isRepositoryLimitReachedForWorkspaceMono))
.flatMap(isRepositoryLimitReached -> {
Mono<GitAuth> gitAuthForUserMono =
gitHandlingService.getGitAuthForUser().cache();
Mono<? extends Artifact> createArtifactMono = gitArtifactHelper
.createArtifactForImport(workspaceId, repoName)
.cache();
if (FALSE.equals(isRepositoryLimitReached)) {
return gitAuthForUserMono.zipWith(createArtifactMono);
}
// TODO: Change errors to artifact level.
return gitAnalyticsUtils
.addAnalyticsForGitOperation(
AnalyticsEvents.GIT_IMPORT,
gitArtifactHelper.getNewArtifact(workspaceId, repoName),
AppsmithError.GIT_APPLICATION_LIMIT_ERROR.getErrorType(),
AppsmithError.GIT_APPLICATION_LIMIT_ERROR.getMessage(),
true)
.then(Mono.error(new AppsmithException(AppsmithError.GIT_APPLICATION_LIMIT_ERROR)));
})
.flatMap(tuple2 -> {
GitAuth gitAuth = tuple2.getT1();
Artifact artifact = tuple2.getT2();
Mono<Map<String, GitProfile>> profileMono = gitProfileUtils.updateOrCreateGitProfileForCurrentUser(
gitConnectDTO.getGitProfile(), artifact.getId());
Mono<String> fetchRemoteRepository =
gitHandlingService.fetchRemoteRepository(gitConnectDTO, gitAuth, artifact, repoName);
return fetchRemoteRepository
.zipWith(isRepositoryPrivateMono)
.flatMap(tuple -> {
String defaultBranch = tuple.getT1();
Boolean isRepoPrivate = tuple.getT2();
GitArtifactMetadata gitArtifactMetadata = new GitArtifactMetadata();
gitArtifactMetadata.setGitAuth(gitAuth);
gitArtifactMetadata.setDefaultArtifactId(artifact.getId());
gitArtifactMetadata.setDefaultBranchName(defaultBranch);
gitArtifactMetadata.setRefName(defaultBranch);
gitArtifactMetadata.setRepoName(repoName);
gitArtifactMetadata.setIsRepoPrivate(isRepoPrivate);
gitArtifactMetadata.setLastCommittedAt(Instant.now());
gitHandlingService.setRepositoryDetailsInGitArtifactMetadata(
gitConnectDTO, gitArtifactMetadata);
artifact.setGitArtifactMetadata(gitArtifactMetadata);
return Mono.just(artifact).zipWith(profileMono);
});
})
.flatMap(tuple2 -> {
Artifact artifact = tuple2.getT1();
GitArtifactMetadata gitArtifactMetadata = artifact.getGitArtifactMetadata();
String defaultBranch = gitArtifactMetadata.getDefaultBranchName();
Mono<List<Datasource>> datasourceMono = datasourceService
.getAllByWorkspaceIdWithStorages(workspaceId, datasourcePermission.getEditPermission())
.collectList();
Mono<List<Plugin>> pluginMono =
pluginService.getDefaultPlugins().collectList();
ArtifactJsonTransformationDTO jsonMorphDTO = new ArtifactJsonTransformationDTO();
jsonMorphDTO.setWorkspaceId(workspaceId);
jsonMorphDTO.setBaseArtifactId(artifact.getId());
jsonMorphDTO.setArtifactType(artifactType);
jsonMorphDTO.setRepoName(gitArtifactMetadata.getRepoName());
jsonMorphDTO.setRefType(RefType.branch);
jsonMorphDTO.setRefName(defaultBranch);
Mono<? extends ArtifactExchangeJson> artifactExchangeJsonMono = gitHandlingService
.reconstructArtifactJsonFromGitRepository(jsonMorphDTO)
.onErrorResume(error -> {
log.error("Error while constructing artifact from git repo", error);
return deleteArtifactCreatedFromGitImport(jsonMorphDTO, gitType)
.then(Mono.error(new AppsmithException(
AppsmithError.GIT_FILE_SYSTEM_ERROR, error.getMessage())));
});
return Mono.zip(artifactExchangeJsonMono, datasourceMono, pluginMono)
.flatMap(data -> {
ArtifactExchangeJson artifactExchangeJson = data.getT1();
List<Datasource> datasourceList = data.getT2();
List<Plugin> pluginList = data.getT3();
if (artifactExchangeJson.getArtifact() == null
|| gitArtifactHelper.isContextInArtifactEmpty(artifactExchangeJson)) {
return deleteArtifactCreatedFromGitImport(jsonMorphDTO, gitType)
.then(Mono.error(new AppsmithException(
AppsmithError.GIT_ACTION_FAILED,
"import",
"Cannot import artifact from an empty repo")));
}
// If there is an existing datasource with the same name but a different type from that
// in the repo, the import api should fail
// TODO: change the implementation to compare datasource with gitSyncIds instead.
if (checkIsDatasourceNameConflict(
datasourceList, artifactExchangeJson.getDatasourceList(), pluginList)) {
return deleteArtifactCreatedFromGitImport(jsonMorphDTO, gitType)
.then(Mono.error(new AppsmithException(
AppsmithError.GIT_ACTION_FAILED,
"import",
"Datasource already exists with the same name")));
}
artifactExchangeJson.getArtifact().setGitArtifactMetadata(gitArtifactMetadata);
return importService
.importArtifactInWorkspaceFromGit(
workspaceId, artifact.getId(), artifactExchangeJson, defaultBranch)
.onErrorResume(throwable -> deleteArtifactCreatedFromGitImport(
jsonMorphDTO, gitType)
.then(Mono.error(new AppsmithException(
AppsmithError.GIT_FILE_SYSTEM_ERROR, throwable.getMessage()))));
});
})
.flatMap(artifact -> gitArtifactHelper.publishArtifact(artifact, false))
// Add un-configured datasource to the list to response
.flatMap(artifact -> importService.getArtifactImportDTO(
artifact.getWorkspaceId(), artifact.getId(), artifact, artifactType))
// Add analytics event
.flatMap(artifactImportDTO -> {
Artifact artifact = artifactImportDTO.getArtifact();
return gitAnalyticsUtils
.addAnalyticsForGitOperation(
AnalyticsEvents.GIT_IMPORT,
artifact,
artifact.getGitArtifactMetadata().getIsRepoPrivate())
.thenReturn(artifactImportDTO);
});
return Mono.create(
sink -> importedArtifactMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
} }
private Mono<? extends Artifact> deleteArtifactCreatedFromGitImport( private Mono<? extends Artifact> deleteArtifactCreatedFromGitImport(

View File

@ -41,6 +41,9 @@ public interface GitHandlingServiceCE {
Mono<ArtifactType> obtainArtifactTypeFromGitRepository(ArtifactJsonTransformationDTO jsonTransformationDTO); Mono<ArtifactType> obtainArtifactTypeFromGitRepository(ArtifactJsonTransformationDTO jsonTransformationDTO);
Mono<Tuple2<ArtifactType, String>> obtainArtifactTypeAndIdentifierFromGitRepository(
ArtifactJsonTransformationDTO jsonTransformationDTO);
Mono<String> fetchRemoteRepository( Mono<String> fetchRemoteRepository(
GitConnectDTO gitConnectDTO, GitAuth gitAuth, ArtifactJsonTransformationDTO jsonTransformationDTO); GitConnectDTO gitConnectDTO, GitAuth gitAuth, ArtifactJsonTransformationDTO jsonTransformationDTO);
@ -53,6 +56,9 @@ public interface GitHandlingServiceCE {
void setRepositoryDetailsInGitArtifactMetadata( void setRepositoryDetailsInGitArtifactMetadata(
GitConnectDTO gitConnectDTO, GitArtifactMetadata gitArtifactMetadata); GitConnectDTO gitConnectDTO, GitArtifactMetadata gitArtifactMetadata);
Mono<Boolean> removeRepository(
ArtifactJsonTransformationDTO artifactJsonTransformationDTO, Boolean isArtifactTypeUnknown);
Mono<Boolean> removeRepository(ArtifactJsonTransformationDTO artifactJsonTransformationDTO); Mono<Boolean> removeRepository(ArtifactJsonTransformationDTO artifactJsonTransformationDTO);
Mono<List<GitRefDTO>> listBranches( Mono<List<GitRefDTO>> listBranches(

View File

@ -1,31 +1,18 @@
package com.appsmith.server.git.fs; package com.appsmith.server.git.fs;
import com.appsmith.external.git.handler.FSGitHandler; import com.appsmith.external.git.handler.FSGitHandler;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.datasources.base.DatasourceService;
import com.appsmith.server.exports.internal.ExportService;
import com.appsmith.server.git.GitRedisUtils; import com.appsmith.server.git.GitRedisUtils;
import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelper;
import com.appsmith.server.git.central.GitHandlingServiceCECompatible; import com.appsmith.server.git.central.GitHandlingServiceCECompatible;
import com.appsmith.server.git.resolver.GitArtifactHelperResolver; import com.appsmith.server.git.resolver.GitArtifactHelperResolver;
import com.appsmith.server.git.utils.GitAnalyticsUtils; import com.appsmith.server.git.utils.GitAnalyticsUtils;
import com.appsmith.server.git.utils.GitProfileUtils;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.GitPrivateRepoHelper;
import com.appsmith.server.imports.internal.ImportService;
import com.appsmith.server.plugins.base.PluginService;
import com.appsmith.server.repositories.GitDeployKeysRepository; import com.appsmith.server.repositories.GitDeployKeysRepository;
import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.FeatureFlagService; import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.services.UserService;
import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.solutions.DatasourcePermission;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.reactive.TransactionalOperator;
@Slf4j @Slf4j
@Service @Service
@ -33,49 +20,23 @@ public class GitFSServiceCECompatibleImpl extends GitFSServiceCEImpl implements
public GitFSServiceCECompatibleImpl( public GitFSServiceCECompatibleImpl(
GitDeployKeysRepository gitDeployKeysRepository, GitDeployKeysRepository gitDeployKeysRepository,
GitPrivateRepoHelper gitPrivateRepoHelper,
CommonGitFileUtils commonGitFileUtils, CommonGitFileUtils commonGitFileUtils,
GitRedisUtils gitRedisUtils, GitRedisUtils gitRedisUtils,
SessionUserService sessionUserService, SessionUserService sessionUserService,
UserDataService userDataService,
UserService userService,
EmailConfig emailConfig,
TransactionalOperator transactionalOperator,
AnalyticsService analyticsService, AnalyticsService analyticsService,
ObservationRegistry observationRegistry, ObservationRegistry observationRegistry,
WorkspaceService workspaceService,
DatasourceService datasourceService,
DatasourcePermission datasourcePermission,
PluginService pluginService,
ExportService exportService,
ImportService importService,
FSGitHandler fsGitHandler, FSGitHandler fsGitHandler,
GitAutoCommitHelper gitAutoCommitHelper,
GitProfileUtils gitProfileUtils,
GitAnalyticsUtils gitAnalyticsUtils, GitAnalyticsUtils gitAnalyticsUtils,
GitArtifactHelperResolver gitArtifactHelperResolver, GitArtifactHelperResolver gitArtifactHelperResolver,
FeatureFlagService featureFlagService) { FeatureFlagService featureFlagService) {
super( super(
gitDeployKeysRepository, gitDeployKeysRepository,
gitPrivateRepoHelper,
commonGitFileUtils, commonGitFileUtils,
gitRedisUtils, gitRedisUtils,
sessionUserService, sessionUserService,
userDataService,
userService,
emailConfig,
transactionalOperator,
analyticsService, analyticsService,
observationRegistry, observationRegistry,
workspaceService,
datasourceService,
datasourcePermission,
pluginService,
exportService,
importService,
fsGitHandler, fsGitHandler,
gitAutoCommitHelper,
gitProfileUtils,
gitAnalyticsUtils, gitAnalyticsUtils,
gitArtifactHelperResolver, gitArtifactHelperResolver,
featureFlagService); featureFlagService);

View File

@ -11,9 +11,7 @@ import com.appsmith.external.git.constants.ce.RefType;
import com.appsmith.external.git.dtos.FetchRemoteDTO; import com.appsmith.external.git.dtos.FetchRemoteDTO;
import com.appsmith.external.git.handler.FSGitHandler; import com.appsmith.external.git.handler.FSGitHandler;
import com.appsmith.git.dto.CommitDTO; import com.appsmith.git.dto.CommitDTO;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.constants.ArtifactType; import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.datasources.base.DatasourceService;
import com.appsmith.server.domains.Artifact; import com.appsmith.server.domains.Artifact;
import com.appsmith.server.domains.GitArtifactMetadata; import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.GitAuth;
@ -23,29 +21,19 @@ import com.appsmith.server.dtos.GitConnectDTO;
import com.appsmith.server.dtos.GitMergeDTO; import com.appsmith.server.dtos.GitMergeDTO;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.exports.internal.ExportService;
import com.appsmith.server.git.GitRedisUtils; import com.appsmith.server.git.GitRedisUtils;
import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelper;
import com.appsmith.server.git.central.GitHandlingServiceCE; import com.appsmith.server.git.central.GitHandlingServiceCE;
import com.appsmith.server.git.dtos.ArtifactJsonTransformationDTO; import com.appsmith.server.git.dtos.ArtifactJsonTransformationDTO;
import com.appsmith.server.git.resolver.GitArtifactHelperResolver; import com.appsmith.server.git.resolver.GitArtifactHelperResolver;
import com.appsmith.server.git.utils.GitAnalyticsUtils; import com.appsmith.server.git.utils.GitAnalyticsUtils;
import com.appsmith.server.git.utils.GitProfileUtils;
import com.appsmith.server.helpers.CollectionUtils; import com.appsmith.server.helpers.CollectionUtils;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.GitPrivateRepoHelper;
import com.appsmith.server.helpers.GitUtils; import com.appsmith.server.helpers.GitUtils;
import com.appsmith.server.imports.internal.ImportService;
import com.appsmith.server.plugins.base.PluginService;
import com.appsmith.server.repositories.GitDeployKeysRepository; import com.appsmith.server.repositories.GitDeployKeysRepository;
import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.FeatureFlagService; import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.GitArtifactHelper; import com.appsmith.server.services.GitArtifactHelper;
import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.services.UserService;
import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.solutions.DatasourcePermission;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -55,7 +43,6 @@ import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import reactor.core.observability.micrometer.Micrometer; import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -79,30 +66,14 @@ import static java.lang.Boolean.TRUE;
public class GitFSServiceCEImpl implements GitHandlingServiceCE { public class GitFSServiceCEImpl implements GitHandlingServiceCE {
private final GitDeployKeysRepository gitDeployKeysRepository; private final GitDeployKeysRepository gitDeployKeysRepository;
private final GitPrivateRepoHelper gitPrivateRepoHelper; protected final CommonGitFileUtils commonGitFileUtils;
private final CommonGitFileUtils commonGitFileUtils; protected final GitRedisUtils gitRedisUtils;
private final GitRedisUtils gitRedisUtils;
protected final SessionUserService sessionUserService; protected final SessionUserService sessionUserService;
private final UserDataService userDataService;
protected final UserService userService;
private final EmailConfig emailConfig;
private final TransactionalOperator transactionalOperator;
protected final AnalyticsService analyticsService; protected final AnalyticsService analyticsService;
private final ObservationRegistry observationRegistry; private final ObservationRegistry observationRegistry;
private final WorkspaceService workspaceService;
private final DatasourceService datasourceService;
private final DatasourcePermission datasourcePermission;
private final PluginService pluginService;
private final ExportService exportService;
private final ImportService importService;
protected final FSGitHandler fsGitHandler; protected final FSGitHandler fsGitHandler;
private final GitAutoCommitHelper gitAutoCommitHelper;
private final GitProfileUtils gitProfileUtils;
private final GitAnalyticsUtils gitAnalyticsUtils; private final GitAnalyticsUtils gitAnalyticsUtils;
protected final GitArtifactHelperResolver gitArtifactHelperResolver; protected final GitArtifactHelperResolver gitArtifactHelperResolver;
@ -202,6 +173,12 @@ public class GitFSServiceCEImpl implements GitHandlingServiceCE {
}); });
} }
@Override
public Mono<Tuple2<ArtifactType, String>> obtainArtifactTypeAndIdentifierFromGitRepository(
ArtifactJsonTransformationDTO jsonTransformationDTO) {
return obtainArtifactTypeFromGitRepository(jsonTransformationDTO).zipWith(Mono.just(""));
}
public Mono<ArtifactType> obtainArtifactTypeFromGitRepository(ArtifactJsonTransformationDTO jsonTransformationDTO) { public Mono<ArtifactType> obtainArtifactTypeFromGitRepository(ArtifactJsonTransformationDTO jsonTransformationDTO) {
String workspaceId = jsonTransformationDTO.getWorkspaceId(); String workspaceId = jsonTransformationDTO.getWorkspaceId();
String placeHolder = jsonTransformationDTO.getBaseArtifactId(); String placeHolder = jsonTransformationDTO.getBaseArtifactId();
@ -292,6 +269,17 @@ public class GitFSServiceCEImpl implements GitHandlingServiceCE {
artifactJsonTransformationDTO)); artifactJsonTransformationDTO));
} }
@Override
public Mono<Boolean> removeRepository(
ArtifactJsonTransformationDTO artifactJsonTransformationDTO, Boolean isArtifactTypeUnknown) {
// Since the artifact type is unknown, we can assume that the repository is yet to be
if (TRUE.equals(isArtifactTypeUnknown)) {
artifactJsonTransformationDTO.setArtifactType(ArtifactType.APPLICATION);
}
return removeRepository(artifactJsonTransformationDTO);
}
@Override @Override
public Mono<Boolean> removeRepository(ArtifactJsonTransformationDTO artifactJsonTransformationDTO) { public Mono<Boolean> removeRepository(ArtifactJsonTransformationDTO artifactJsonTransformationDTO) {
GitArtifactHelper<?> gitArtifactHelper = GitArtifactHelper<?> gitArtifactHelper =

View File

@ -1,31 +1,18 @@
package com.appsmith.server.git.fs; package com.appsmith.server.git.fs;
import com.appsmith.external.git.handler.FSGitHandler; import com.appsmith.external.git.handler.FSGitHandler;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.datasources.base.DatasourceService;
import com.appsmith.server.exports.internal.ExportService;
import com.appsmith.server.git.GitRedisUtils; import com.appsmith.server.git.GitRedisUtils;
import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelper;
import com.appsmith.server.git.central.GitHandlingService; import com.appsmith.server.git.central.GitHandlingService;
import com.appsmith.server.git.resolver.GitArtifactHelperResolver; import com.appsmith.server.git.resolver.GitArtifactHelperResolver;
import com.appsmith.server.git.utils.GitAnalyticsUtils; import com.appsmith.server.git.utils.GitAnalyticsUtils;
import com.appsmith.server.git.utils.GitProfileUtils;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.GitPrivateRepoHelper;
import com.appsmith.server.imports.internal.ImportService;
import com.appsmith.server.plugins.base.PluginService;
import com.appsmith.server.repositories.GitDeployKeysRepository; import com.appsmith.server.repositories.GitDeployKeysRepository;
import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.FeatureFlagService; import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.services.UserService;
import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.solutions.DatasourcePermission;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.reactive.TransactionalOperator;
@Slf4j @Slf4j
@Service @Service
@ -33,49 +20,23 @@ public class GitFSServiceImpl extends GitFSServiceCECompatibleImpl implements Gi
public GitFSServiceImpl( public GitFSServiceImpl(
GitDeployKeysRepository gitDeployKeysRepository, GitDeployKeysRepository gitDeployKeysRepository,
GitPrivateRepoHelper gitPrivateRepoHelper,
CommonGitFileUtils commonGitFileUtils, CommonGitFileUtils commonGitFileUtils,
GitRedisUtils gitRedisUtils, GitRedisUtils gitRedisUtils,
SessionUserService sessionUserService, SessionUserService sessionUserService,
UserDataService userDataService,
UserService userService,
EmailConfig emailConfig,
TransactionalOperator transactionalOperator,
AnalyticsService analyticsService, AnalyticsService analyticsService,
ObservationRegistry observationRegistry, ObservationRegistry observationRegistry,
WorkspaceService workspaceService,
DatasourceService datasourceService,
DatasourcePermission datasourcePermission,
PluginService pluginService,
ExportService exportService,
ImportService importService,
FSGitHandler fsGitHandler, FSGitHandler fsGitHandler,
GitAutoCommitHelper gitAutoCommitHelper,
GitProfileUtils gitProfileUtils,
GitAnalyticsUtils gitAnalyticsUtils, GitAnalyticsUtils gitAnalyticsUtils,
GitArtifactHelperResolver gitArtifactHelperResolver, GitArtifactHelperResolver gitArtifactHelperResolver,
FeatureFlagService featureFlagService) { FeatureFlagService featureFlagService) {
super( super(
gitDeployKeysRepository, gitDeployKeysRepository,
gitPrivateRepoHelper,
commonGitFileUtils, commonGitFileUtils,
gitRedisUtils, gitRedisUtils,
sessionUserService, sessionUserService,
userDataService,
userService,
emailConfig,
transactionalOperator,
analyticsService, analyticsService,
observationRegistry, observationRegistry,
workspaceService,
datasourceService,
datasourcePermission,
pluginService,
exportService,
importService,
fsGitHandler, fsGitHandler,
gitAutoCommitHelper,
gitProfileUtils,
gitAnalyticsUtils, gitAnalyticsUtils,
gitArtifactHelperResolver, gitArtifactHelperResolver,
featureFlagService); featureFlagService);

View File

@ -95,7 +95,7 @@ public class CommonGitFileUtilsCE {
protected final ArtifactGitFileUtils<ApplicationJson> applicationGitFileUtils; protected final ArtifactGitFileUtils<ApplicationJson> applicationGitFileUtils;
protected final GitServiceConfig gitServiceConfig; protected final GitServiceConfig gitServiceConfig;
private final FileInterface fileUtils; protected final FileInterface fileUtils;
private final FileOperations fileOperations; private final FileOperations fileOperations;
private final AnalyticsService analyticsService; private final AnalyticsService analyticsService;
private final SessionUserService sessionUserService; private final SessionUserService sessionUserService;
@ -830,7 +830,7 @@ public class CommonGitFileUtilsCE {
"Error while moving repository from temporary storage {} to permanent storage {}", "Error while moving repository from temporary storage {} to permanent storage {}",
currentGitPath, currentGitPath,
targetPath, targetPath,
error.getMessage()); error);
return Mono.error(error); return Mono.error(error);
}) })
.subscribeOn(Schedulers.boundedElastic()); .subscribeOn(Schedulers.boundedElastic());

View File

@ -37,6 +37,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.Part; import org.springframework.http.codec.multipart.Part;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -530,6 +531,11 @@ public class ImportServiceCEImpl implements ImportServiceCE {
.onErrorResume(throwable -> { .onErrorResume(throwable -> {
String errorMessage = ImportExportUtils.getErrorMessage(throwable); String errorMessage = ImportExportUtils.getErrorMessage(throwable);
log.error("Error importing {}. Error: {}", artifactContextString, errorMessage, throwable); log.error("Error importing {}. Error: {}", artifactContextString, errorMessage, throwable);
if (throwable instanceof DuplicateKeyException) {
return Mono.error(new AppsmithException(
AppsmithError.ARTIFACT_IMPORT_DUPLICATE_KEY_WRITE_ERROR, "Duplicate artifact key"));
}
return Mono.error(new AppsmithException( return Mono.error(new AppsmithException(
AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, errorMessage)); AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, errorMessage));
}) })