diff --git a/app/client/src/git/components/CtxAwareGitQuickActions/index.tsx b/app/client/src/git/components/CtxAwareGitQuickActions/index.tsx index 9deb2f2d7a55..824350f3b195 100644 --- a/app/client/src/git/components/CtxAwareGitQuickActions/index.tsx +++ b/app/client/src/git/components/CtxAwareGitQuickActions/index.tsx @@ -5,9 +5,13 @@ import useStatusChangeCount from "./hooks/useStatusChangeCount"; function CtxAwareGitQuickActions() { const { + autocommitEnabled, + autocommitPolling, discard, discardLoading, fetchStatusLoading, + gitConnected, + protectedMode, pull, pullError, pullLoading, @@ -17,12 +21,7 @@ function CtxAwareGitQuickActions() { toggleGitSettingsModal, } = useGitContext(); - const isGitConnected = false; - const isAutocommitEnabled = true; - const isAutocommitPolling = false; - const isConnectPermitted = true; - const isProtectedMode = false; - + const connectPermitted = true; const isPullFailing = !!pullError; const isStatusClean = status?.isClean ?? false; const statusBehindCount = status?.behindCount ?? 0; @@ -31,13 +30,13 @@ function CtxAwareGitQuickActions() { return ( ({ artifactType, baseArtifactId }), [artifactType, baseArtifactId], ); + const useGitMetadataReturnValue = useGitMetadata(basePayload); const useGitConnectReturnValue = useGitConnect(basePayload); const useGitOpsReturnValue = useGitOps(basePayload); const useGitBranchesReturnValue = useGitBranches(basePayload); const useGitSettingsReturnValue = useGitSettings(basePayload); return { + ...useGitMetadataReturnValue, ...useGitOpsReturnValue, ...useGitBranchesReturnValue, ...useGitConnectReturnValue, diff --git a/app/client/src/git/components/GitContextProvider/hooks/useGitMetadata.ts b/app/client/src/git/components/GitContextProvider/hooks/useGitMetadata.ts new file mode 100644 index 000000000000..b1a10de4a1a5 --- /dev/null +++ b/app/client/src/git/components/GitContextProvider/hooks/useGitMetadata.ts @@ -0,0 +1,45 @@ +import type { GitArtifactType } from "git/constants/enums"; +import type { FetchGitMetadataResponseData } from "git/requests/fetchGitMetadataRequest.types"; +import { + selectGitConnected, + selectGitMetadata, +} from "git/store/selectors/gitSingleArtifactSelectors"; +import type { GitRootState } from "git/store/types"; +import { useMemo } from "react"; +import { useSelector } from "react-redux"; + +interface UseGitMetadataParams { + artifactType: keyof typeof GitArtifactType; + baseArtifactId: string; +} + +export interface UseGitMetadataReturnValue { + gitMetadata: FetchGitMetadataResponseData | null; + fetchGitMetadataLoading: boolean; + fetchGitMetadataError: string | null; + gitConnected: boolean; +} + +export default function useGitMetadata({ + artifactType, + baseArtifactId, +}: UseGitMetadataParams): UseGitMetadataReturnValue { + const basePayload = useMemo( + () => ({ artifactType, baseArtifactId }), + [artifactType, baseArtifactId], + ); + + const gitMetadataState = useSelector((state: GitRootState) => + selectGitMetadata(state, basePayload), + ); + const gitConnected = useSelector((state: GitRootState) => + selectGitConnected(state, basePayload), + ); + + return { + gitMetadata: gitMetadataState.value, + fetchGitMetadataLoading: gitMetadataState.loading ?? false, + fetchGitMetadataError: gitMetadataState.error, + gitConnected: gitConnected ?? false, + }; +} diff --git a/app/client/src/git/components/GitContextProvider/hooks/useGitOps.ts b/app/client/src/git/components/GitContextProvider/hooks/useGitOps.ts index 3c9f3990d6e9..1d0b59f71cd0 100644 --- a/app/client/src/git/components/GitContextProvider/hooks/useGitOps.ts +++ b/app/client/src/git/components/GitContextProvider/hooks/useGitOps.ts @@ -133,24 +133,24 @@ export default function useGitOps({ return { commitLoading: commitState?.loading ?? false, - commitError: commitState?.error ?? null, + commitError: commitState?.error, commit, discardLoading: discardState?.loading ?? false, - discardError: discardState?.error ?? null, + discardError: discardState?.error, discard, - status: statusState?.value ?? null, + status: statusState?.value, fetchStatusLoading: statusState?.loading ?? false, - fetchStatusError: statusState?.error ?? null, + fetchStatusError: statusState?.error, fetchStatus, mergeLoading: mergeState?.loading ?? false, - mergeError: mergeState?.error ?? null, + mergeError: mergeState?.error, merge, - mergeStatus: mergeStatusState?.value ?? null, + mergeStatus: mergeStatusState?.value, fetchMergeStatusLoading: mergeStatusState?.loading ?? false, - fetchMergeStatusError: mergeStatusState?.error ?? null, + fetchMergeStatusError: mergeStatusState?.error, fetchMergeStatus, pullLoading: pullState?.loading ?? false, - pullError: pullState?.error ?? null, + pullError: pullState?.error, pull, toggleGitOpsModal, }; diff --git a/app/client/src/git/components/GitContextProvider/hooks/useGitSettings.ts b/app/client/src/git/components/GitContextProvider/hooks/useGitSettings.ts index 096e934d7d5e..2e63f8e60325 100644 --- a/app/client/src/git/components/GitContextProvider/hooks/useGitSettings.ts +++ b/app/client/src/git/components/GitContextProvider/hooks/useGitSettings.ts @@ -1,7 +1,15 @@ import type { GitArtifactType, GitSettingsTab } from "git/constants/enums"; +import type { FetchProtectedBranchesResponseData } from "git/requests/fetchProtectedBranchesRequest.types"; import { gitArtifactActions } from "git/store/gitArtifactSlice"; +import { + selectAutocommitEnabled, + selectAutocommitPolling, + selectProtectedBranches, + selectProtectedMode, +} from "git/store/selectors/gitSingleArtifactSelectors"; +import type { GitRootState } from "git/store/types"; import { useMemo } from "react"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; interface UseGitSettingsParams { artifactType: keyof typeof GitArtifactType; @@ -9,6 +17,13 @@ interface UseGitSettingsParams { } export interface UseGitSettingsReturnValue { + autocommitEnabled: boolean; + autocommitPolling: boolean; + protectedBranches: FetchProtectedBranchesResponseData | null; + fetchProtectedBranchesLoading: boolean; + fetchProtectedBranchesError: string | null; + fetchProtectedBranches: () => void; + protectedMode: boolean; toggleGitSettingsModal: ( open: boolean, tab: keyof typeof GitSettingsTab, @@ -25,6 +40,33 @@ export default function useGitSettings({ [artifactType, baseArtifactId], ); + // autocommit + const autocommitEnabled = useSelector((state: GitRootState) => + selectAutocommitEnabled(state, basePayload), + ); + + const autocommitPolling = useSelector((state: GitRootState) => + selectAutocommitPolling(state, basePayload), + ); + + // branch protection + const protectedBranchesState = useSelector((state: GitRootState) => + selectProtectedBranches(state, basePayload), + ); + + const fetchProtectedBranches = () => { + dispatch( + gitArtifactActions.fetchProtectedBranchesInit({ + ...basePayload, + }), + ); + }; + + const protectedMode = useSelector((state: GitRootState) => + selectProtectedMode(state, basePayload), + ); + + // ui const toggleGitSettingsModal = ( open: boolean, tab: keyof typeof GitSettingsTab, @@ -39,6 +81,13 @@ export default function useGitSettings({ }; return { + autocommitEnabled: autocommitEnabled ?? false, + autocommitPolling: autocommitPolling ?? false, + protectedBranches: protectedBranchesState.value, + fetchProtectedBranchesLoading: protectedBranchesState.loading ?? false, + fetchProtectedBranchesError: protectedBranchesState.error, + fetchProtectedBranches, + protectedMode: protectedMode ?? false, toggleGitSettingsModal, }; } diff --git a/app/client/src/git/components/GitContextProvider/index.tsx b/app/client/src/git/components/GitContextProvider/index.tsx index 2d057c30670b..afb9f010a50c 100644 --- a/app/client/src/git/components/GitContextProvider/index.tsx +++ b/app/client/src/git/components/GitContextProvider/index.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useEffect } from "react"; +import React, { createContext, useContext } from "react"; import type { GitArtifactType } from "git/constants/enums"; import type { GitContextValue } from "./hooks/useGitContextValue"; import useGitContextValue from "./hooks/useGitContextValue"; @@ -15,24 +15,18 @@ interface GitContextProviderProps { artifactType: keyof typeof GitArtifactType; baseArtifactId: string; children: React.ReactNode; + // extra + // connectPermitted?: boolean; } export default function GitContextProvider({ artifactType, baseArtifactId, children, + // connectPermitted = true, }: GitContextProviderProps) { const contextValue = useGitContextValue({ artifactType, baseArtifactId }); - const { fetchBranches } = contextValue; - - useEffect( - function gitInitEffect() { - fetchBranches(); - }, - [fetchBranches], - ); - return ( {children} ); diff --git a/app/client/src/git/components/GitQuickActions/QuickActionButton.test.tsx b/app/client/src/git/components/GitQuickActions/QuickActionButton.test.tsx index 6bd94a586f56..037db38a04d2 100644 --- a/app/client/src/git/components/GitQuickActions/QuickActionButton.test.tsx +++ b/app/client/src/git/components/GitQuickActions/QuickActionButton.test.tsx @@ -50,7 +50,7 @@ describe("QuickActionButton", () => { , ); - const btn = container.getElementsByClassName("t--test-btn")[0]; + const btn = container.querySelectorAll(".t--test-btn button")[0]; fireEvent.click(btn); expect(defaultProps.onClick).toHaveBeenCalledTimes(1); diff --git a/app/client/src/git/components/GitQuickActions/QuickActionButton.tsx b/app/client/src/git/components/GitQuickActions/QuickActionButton.tsx index c22306b748fa..71c11dbe65ad 100644 --- a/app/client/src/git/components/GitQuickActions/QuickActionButton.tsx +++ b/app/client/src/git/components/GitQuickActions/QuickActionButton.tsx @@ -19,12 +19,11 @@ const SpinnerContainer = styled.div` padding: 0 10px; `; -const QuickActionButtonContainer = styled.button<{ disabled?: boolean }>` +const QuickActionButtonContainer = styled.div<{ disabled?: boolean }>` margin: 0 ${(props) => props.theme.spaces[1]}px; display: block; position: relative; overflow: visible; - cursor: ${({ disabled = false }) => (disabled ? "not-allowed" : "pointer")}; opacity: ${({ disabled = false }) => (disabled ? 0.6 : 1)}; `; @@ -57,11 +56,7 @@ function QuickActionButton({ const content = capitalizeFirstLetter(tooltipText); return ( - + {loading ? ( @@ -73,6 +68,7 @@ function QuickActionButton({ isDisabled={disabled} isIconButton kind="tertiary" + onClick={onClick} size="md" startIcon={icon} /> diff --git a/app/client/src/git/components/GitQuickActions/index.test.tsx b/app/client/src/git/components/GitQuickActions/index.test.tsx index 06f305fb9644..9beebcc7593f 100644 --- a/app/client/src/git/components/GitQuickActions/index.test.tsx +++ b/app/client/src/git/components/GitQuickActions/index.test.tsx @@ -110,8 +110,8 @@ describe("QuickActions Component", () => { , ); - const commitButton = container.getElementsByClassName( - "t--bottom-bar-commit", + const commitButton = container.querySelectorAll( + ".t--bottom-bar-commit button", )[0]; fireEvent.click(commitButton); @@ -145,8 +145,9 @@ describe("QuickActions Component", () => { , ); - const pullButton = - container.getElementsByClassName("t--bottom-bar-pull")[0]; + const pullButton = container.querySelectorAll( + ".t--bottom-bar-pull button", + )[0]; fireEvent.click(pullButton); expect(AnalyticsUtil.logEvent).toHaveBeenCalledWith("GS_PULL_GIT_CLICK", { @@ -165,8 +166,8 @@ describe("QuickActions Component", () => { , ); - const mergeButton = container.getElementsByClassName( - "t--bottom-bar-merge", + const mergeButton = container.querySelectorAll( + ".t--bottom-bar-merge button", )[0]; fireEvent.click(mergeButton); @@ -190,8 +191,8 @@ describe("QuickActions Component", () => { , ); - const settingsButton = container.getElementsByClassName( - "t--bottom-git-settings", + const settingsButton = container.querySelectorAll( + ".t--bottom-git-settings button", )[0]; fireEvent.click(settingsButton); @@ -216,8 +217,8 @@ describe("QuickActions Component", () => { , ); - const commitButton = container.getElementsByClassName( - "t--bottom-bar-commit", + const commitButton = container.querySelectorAll( + ".t--bottom-bar-commit button", )[0]; expect(commitButton).toBeDisabled(); @@ -298,8 +299,9 @@ describe("QuickActions Component", () => { , ); - const pullButton = - container.getElementsByClassName("t--bottom-bar-pull")[0]; + const pullButton = container.querySelectorAll( + ".t--bottom-bar-pull button", + )[0]; expect(pullButton).toBeDisabled(); }); diff --git a/app/client/src/git/components/GitQuickActions/index.tsx b/app/client/src/git/components/GitQuickActions/index.tsx index 3dabeb95c860..42d172839f11 100644 --- a/app/client/src/git/components/GitQuickActions/index.tsx +++ b/app/client/src/git/components/GitQuickActions/index.tsx @@ -95,6 +95,7 @@ function GitQuickActions({ if (isProtectedMode) { discard(); } else { + // ! case: why is triggeredFromBottomBar this needed? // pull({ triggeredFromBottomBar: true }); pull(); } diff --git a/app/client/src/git/requests/checkoutBranchRequest.ts b/app/client/src/git/requests/checkoutBranchRequest.ts index 4df5da6112d3..e0c2f7e990ac 100644 --- a/app/client/src/git/requests/checkoutBranchRequest.ts +++ b/app/client/src/git/requests/checkoutBranchRequest.ts @@ -1,4 +1,4 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { CheckoutBranchRequestParams, CheckoutBranchResponse, @@ -9,7 +9,7 @@ import Api from "api/Api"; export default async function checkoutBranchRequest( branchedApplicationId: string, params: CheckoutBranchRequestParams, -): Promise> { +): AxiosPromise { return Api.get( `${GIT_BASE_URL}/checkout-branch/app/${branchedApplicationId}`, params, diff --git a/app/client/src/git/requests/commitRequest.ts b/app/client/src/git/requests/commitRequest.ts index 69541d030368..2cbd8aff65a6 100644 --- a/app/client/src/git/requests/commitRequest.ts +++ b/app/client/src/git/requests/commitRequest.ts @@ -4,12 +4,12 @@ import type { CommitResponse, } from "./commitRequest.types"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; export default async function commitRequest( branchedApplicationId: string, params: CommitRequestParams, -): Promise> { +): AxiosPromise { return Api.post( `${GIT_BASE_URL}/commit/app/${branchedApplicationId}`, params, diff --git a/app/client/src/git/requests/createBranchRequest.ts b/app/client/src/git/requests/createBranchRequest.ts index a67b5ee04099..9445ad2c3a55 100644 --- a/app/client/src/git/requests/createBranchRequest.ts +++ b/app/client/src/git/requests/createBranchRequest.ts @@ -1,4 +1,4 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { CreateBranchRequestParams, CreateBranchResponse, @@ -9,7 +9,7 @@ import Api from "api/Api"; export default async function createBranchRequest( branchedApplicationId: string, params: CreateBranchRequestParams, -): Promise> { +): AxiosPromise { return Api.post( `${GIT_BASE_URL}/create-branch/app/${branchedApplicationId}`, params, diff --git a/app/client/src/git/requests/deleteBranchRequest.ts b/app/client/src/git/requests/deleteBranchRequest.ts index 63f718506d48..cba2463d2019 100644 --- a/app/client/src/git/requests/deleteBranchRequest.ts +++ b/app/client/src/git/requests/deleteBranchRequest.ts @@ -1,4 +1,4 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { DeleteBranchRequestParams, DeleteBranchResponse, @@ -9,6 +9,6 @@ import Api from "api/Api"; export default async function deleteBranchRequest( baseApplicationId: string, params: DeleteBranchRequestParams, -): Promise> { +): AxiosPromise { return Api.delete(`${GIT_BASE_URL}/branch/app/${baseApplicationId}`, params); } diff --git a/app/client/src/git/requests/discardRequest.ts b/app/client/src/git/requests/discardRequest.ts index fda452fc206c..6dffe7513caf 100644 --- a/app/client/src/git/requests/discardRequest.ts +++ b/app/client/src/git/requests/discardRequest.ts @@ -1,9 +1,9 @@ import Api from "api/Api"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; export default async function discardRequest( branchedApplicationId: string, -): Promise> { +): AxiosPromise { return Api.put(`${GIT_BASE_URL}/discard/app/${branchedApplicationId}`); } diff --git a/app/client/src/git/requests/disconnectRequest.ts b/app/client/src/git/requests/disconnectRequest.ts index 9ec9b3a4e2b9..93c16be07baa 100644 --- a/app/client/src/git/requests/disconnectRequest.ts +++ b/app/client/src/git/requests/disconnectRequest.ts @@ -1,10 +1,10 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import { GIT_BASE_URL } from "./constants"; import type { DisconnectResponse } from "./disconnectRequest.types"; import Api from "api/Api"; export default async function disconnectRequest( baseApplicationId: string, -): Promise> { +): AxiosPromise { return Api.post(`${GIT_BASE_URL}/disconnect/app/${baseApplicationId}`); } diff --git a/app/client/src/git/requests/fetchAutocommitProgressRequest.ts b/app/client/src/git/requests/fetchAutocommitProgressRequest.ts index 8ad1c71d22c8..1f68624b780c 100644 --- a/app/client/src/git/requests/fetchAutocommitProgressRequest.ts +++ b/app/client/src/git/requests/fetchAutocommitProgressRequest.ts @@ -1,11 +1,11 @@ import Api from "api/Api"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { FetchAutocommitProgressResponse } from "./fetchAutocommitProgressRequest.types"; export default async function fetchAutocommitProgressRequest( baseApplicationId: string, -): Promise> { +): AxiosPromise { return Api.get( `${GIT_BASE_URL}/auto-commit/progress/app/${baseApplicationId}`, ); diff --git a/app/client/src/git/requests/fetchAutocommitProgressRequest.types.ts b/app/client/src/git/requests/fetchAutocommitProgressRequest.types.ts index 60f10b5fc6b3..e59d7a64d547 100644 --- a/app/client/src/git/requests/fetchAutocommitProgressRequest.types.ts +++ b/app/client/src/git/requests/fetchAutocommitProgressRequest.types.ts @@ -1,7 +1,11 @@ +import type { ApiResponse } from "api/types"; import type { AutocommitStatus } from "../constants/enums"; -export interface FetchAutocommitProgressResponse { +export interface FetchAutocommitProgressResponseData { autoCommitResponse: AutocommitStatus; progress: number; branchName: string; } + +export type FetchAutocommitProgressResponse = + ApiResponse; diff --git a/app/client/src/git/requests/fetchBranchesRequest.ts b/app/client/src/git/requests/fetchBranchesRequest.ts index 6dd616e9b0fc..c6f267179096 100644 --- a/app/client/src/git/requests/fetchBranchesRequest.ts +++ b/app/client/src/git/requests/fetchBranchesRequest.ts @@ -8,7 +8,7 @@ import type { AxiosPromise } from "axios"; export default async function fetchBranchesRequest( branchedApplicationId: string, - params: FetchBranchesRequestParams, + params: FetchBranchesRequestParams = { pruneBranches: true }, ): AxiosPromise { const queryParams = {} as FetchBranchesRequestParams; diff --git a/app/client/src/git/requests/fetchBranchesRequest.types.ts b/app/client/src/git/requests/fetchBranchesRequest.types.ts index dadab374a03b..0ea1aeed64cf 100644 --- a/app/client/src/git/requests/fetchBranchesRequest.types.ts +++ b/app/client/src/git/requests/fetchBranchesRequest.types.ts @@ -1,7 +1,7 @@ import type { ApiResponse } from "api/ApiResponses"; export interface FetchBranchesRequestParams { - pruneBranches: boolean; + pruneBranches?: boolean; } interface SingleBranch { diff --git a/app/client/src/git/requests/fetchGitMetadataRequest.ts b/app/client/src/git/requests/fetchGitMetadataRequest.ts index 136f5776f557..7b3376b8ef0e 100644 --- a/app/client/src/git/requests/fetchGitMetadataRequest.ts +++ b/app/client/src/git/requests/fetchGitMetadataRequest.ts @@ -1,10 +1,10 @@ import Api from "api/Api"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { FetchGitMetadataResponse } from "./fetchGitMetadataRequest.types"; export default async function fetchGitMetadataRequest( baseApplicationId: string, -): Promise> { +): AxiosPromise { return Api.get(`${GIT_BASE_URL}/metadata/app/${baseApplicationId}`); } diff --git a/app/client/src/git/requests/fetchGitMetadataRequest.types.ts b/app/client/src/git/requests/fetchGitMetadataRequest.types.ts index 95ef9f6ec3ab..c532052e9793 100644 --- a/app/client/src/git/requests/fetchGitMetadataRequest.types.ts +++ b/app/client/src/git/requests/fetchGitMetadataRequest.types.ts @@ -1,4 +1,6 @@ -export interface FetchGitMetadataResponse { +import type { ApiResponse } from "api/types"; + +export interface FetchGitMetadataResponseData { branchName: string; defaultBranchName: string; remoteUrl: string; @@ -13,3 +15,6 @@ export interface FetchGitMetadataResponse { }; isAutoDeploymentEnabled?: boolean; } + +export type FetchGitMetadataResponse = + ApiResponse; diff --git a/app/client/src/git/requests/fetchMergeStatusRequest.ts b/app/client/src/git/requests/fetchMergeStatusRequest.ts index 95701d5cadc8..402ac66de587 100644 --- a/app/client/src/git/requests/fetchMergeStatusRequest.ts +++ b/app/client/src/git/requests/fetchMergeStatusRequest.ts @@ -1,4 +1,4 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { FetchMergeStatusRequestParams, FetchMergeStatusResponse, @@ -9,7 +9,7 @@ import { GIT_BASE_URL } from "./constants"; export default async function fetchMergeStatusRequest( branchedApplicationId: string, params: FetchMergeStatusRequestParams, -): Promise> { +): AxiosPromise { return Api.post( `${GIT_BASE_URL}/merge/status/app/${branchedApplicationId}`, params, diff --git a/app/client/src/git/requests/fetchProtectedBranchesRequest.ts b/app/client/src/git/requests/fetchProtectedBranchesRequest.ts index 492a23c5eeca..78f54d9d7140 100644 --- a/app/client/src/git/requests/fetchProtectedBranchesRequest.ts +++ b/app/client/src/git/requests/fetchProtectedBranchesRequest.ts @@ -1,10 +1,10 @@ import Api from "api/Api"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; -import type { FetchProtectedBranches } from "./fetchProtectedBranchesRequest.types"; +import type { AxiosPromise } from "axios"; +import type { FetchProtectedBranchesResponse } from "./fetchProtectedBranchesRequest.types"; export default async function fetchProtectedBranchesRequest( baseApplicationId: string, -): Promise> { +): AxiosPromise { return Api.get(`${GIT_BASE_URL}/branch/app/${baseApplicationId}/protected`); } diff --git a/app/client/src/git/requests/fetchProtectedBranchesRequest.types.ts b/app/client/src/git/requests/fetchProtectedBranchesRequest.types.ts index 166cc05322ed..eb298bd43407 100644 --- a/app/client/src/git/requests/fetchProtectedBranchesRequest.types.ts +++ b/app/client/src/git/requests/fetchProtectedBranchesRequest.types.ts @@ -1 +1,6 @@ -export type FetchProtectedBranches = string[]; +import type { ApiResponse } from "api/types"; + +export type FetchProtectedBranchesResponseData = string[]; + +export type FetchProtectedBranchesResponse = + ApiResponse; diff --git a/app/client/src/git/requests/fetchSSHKeyRequest.ts b/app/client/src/git/requests/fetchSSHKeyRequest.ts index e61884e28a3b..29c295dc6d02 100644 --- a/app/client/src/git/requests/fetchSSHKeyRequest.ts +++ b/app/client/src/git/requests/fetchSSHKeyRequest.ts @@ -1,10 +1,10 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { FetchSSHKeyResponse } from "./fetchSSHKeyRequest.types"; import Api from "api/Api"; import { APPLICATION_BASE_URL } from "./constants"; export default async function fetchSSHKeyRequest( baseApplicationId: string, -): Promise> { +): AxiosPromise { return Api.get(`${APPLICATION_BASE_URL}/ssh-keypair/${baseApplicationId}`); } diff --git a/app/client/src/git/requests/fetchStatusRequest.ts b/app/client/src/git/requests/fetchStatusRequest.ts index 587b4f66ea0b..a0a68703f09c 100644 --- a/app/client/src/git/requests/fetchStatusRequest.ts +++ b/app/client/src/git/requests/fetchStatusRequest.ts @@ -4,11 +4,11 @@ import type { FetchStatusResponse, } from "./fetchStatusRequest.types"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; export default async function fetchStatusRequest( branchedApplicationId: string, - params: FetchStatusRequestParams, -): Promise> { + params: FetchStatusRequestParams = { compareRemote: true }, +): AxiosPromise { return Api.get(`${GIT_BASE_URL}/status/app/${branchedApplicationId}`, params); } diff --git a/app/client/src/git/requests/fetchStatusRequest.types.ts b/app/client/src/git/requests/fetchStatusRequest.types.ts index 975f94b2b483..c4400688b794 100644 --- a/app/client/src/git/requests/fetchStatusRequest.types.ts +++ b/app/client/src/git/requests/fetchStatusRequest.types.ts @@ -1,7 +1,7 @@ import type { ApiResponse } from "api/types"; export interface FetchStatusRequestParams { - compareRemote: boolean; + compareRemote?: boolean; } export interface FetchStatusResponseData { added: string[]; diff --git a/app/client/src/git/requests/generateSSHKeyRequest.ts b/app/client/src/git/requests/generateSSHKeyRequest.ts index 525c0423a16f..7c747f94371e 100644 --- a/app/client/src/git/requests/generateSSHKeyRequest.ts +++ b/app/client/src/git/requests/generateSSHKeyRequest.ts @@ -1,4 +1,4 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { GenerateSSHKeyRequestParams, GenerateSSHKeyResponse, @@ -9,7 +9,7 @@ import Api from "api/Api"; export default async function generateSSHKeyRequest( baseApplicationId: string, params: GenerateSSHKeyRequestParams, -): Promise> { +): AxiosPromise { const url = params.isImporting ? `${GIT_BASE_URL}/import/keys?keyType=${params.keyType}` : `${APPLICATION_BASE_URL}/ssh-keypair/${baseApplicationId}?keyType=${params.keyType}`; diff --git a/app/client/src/git/requests/importGitRequest.ts b/app/client/src/git/requests/importGitRequest.ts index e9378361da88..522cd187ebe3 100644 --- a/app/client/src/git/requests/importGitRequest.ts +++ b/app/client/src/git/requests/importGitRequest.ts @@ -4,11 +4,11 @@ import type { ImportGitRequestParams, ImportGitResponse, } from "./importGitRequest.types"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; export default async function importGitRequest( workspaceId: string, params: ImportGitRequestParams, -): Promise> { +): AxiosPromise { return Api.post(`${GIT_BASE_URL}/import/${workspaceId}`, params); } diff --git a/app/client/src/git/requests/mergeRequest.ts b/app/client/src/git/requests/mergeRequest.ts index ee30566c4936..daee44fad21d 100644 --- a/app/client/src/git/requests/mergeRequest.ts +++ b/app/client/src/git/requests/mergeRequest.ts @@ -1,11 +1,11 @@ import Api from "api/Api"; import type { MergeRequestParams, MergeResponse } from "./mergeRequest.types"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; export default async function mergeRequest( branchedApplicationId: string, params: MergeRequestParams, -): Promise> { +): AxiosPromise { return Api.post(`${GIT_BASE_URL}/merge/app/${branchedApplicationId}`, params); } diff --git a/app/client/src/git/requests/pullRequest.ts b/app/client/src/git/requests/pullRequest.ts index 21bd6f4f2a3c..18870918c0cd 100644 --- a/app/client/src/git/requests/pullRequest.ts +++ b/app/client/src/git/requests/pullRequest.ts @@ -1,10 +1,10 @@ import Api from "api/Api"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { PullRequestResponse } from "./pullRequest.types"; export default async function pullRequest( branchedApplicationId: string, -): Promise> { +): AxiosPromise { return Api.get(`${GIT_BASE_URL}/pull/app/${branchedApplicationId}`); } diff --git a/app/client/src/git/requests/toggleAutocommitRequest.ts b/app/client/src/git/requests/toggleAutocommitRequest.ts index deba662ded3f..85131550f108 100644 --- a/app/client/src/git/requests/toggleAutocommitRequest.ts +++ b/app/client/src/git/requests/toggleAutocommitRequest.ts @@ -1,11 +1,11 @@ import Api from "api/Api"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { ToggleAutocommitResponse } from "./toggleAutocommitRequest.types"; export default async function toggleAutocommitRequest( baseApplicationId: string, -): Promise> { +): AxiosPromise { return Api.patch( `${GIT_BASE_URL}/auto-commit/toggle/app/${baseApplicationId}`, ); diff --git a/app/client/src/git/requests/triggerAutocommitRequest.ts b/app/client/src/git/requests/triggerAutocommitRequest.ts index 01c603cb4ce5..7a2085df2d31 100644 --- a/app/client/src/git/requests/triggerAutocommitRequest.ts +++ b/app/client/src/git/requests/triggerAutocommitRequest.ts @@ -1,10 +1,10 @@ import Api from "api/Api"; import { GIT_BASE_URL } from "./constants"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { TriggerAutocommitResponse } from "./triggerAutocommitRequest.types"; export default async function triggerAutocommitRequest( branchedApplicationId: string, -): Promise> { +): AxiosPromise { return Api.post(`${GIT_BASE_URL}/auto-commit/app/${branchedApplicationId}`); } diff --git a/app/client/src/git/requests/triggerAutocommitRequest.types.ts b/app/client/src/git/requests/triggerAutocommitRequest.types.ts index 6abf80ecf226..7a3959478280 100644 --- a/app/client/src/git/requests/triggerAutocommitRequest.types.ts +++ b/app/client/src/git/requests/triggerAutocommitRequest.types.ts @@ -1,7 +1,11 @@ +import type { ApiResponse } from "api/types"; import type { AutocommitStatus } from "../constants/enums"; -export interface TriggerAutocommitResponse { +export interface TriggerAutocommitResponseData { autoCommitResponse: AutocommitStatus; progress: number; branchName: string; } + +export type TriggerAutocommitResponse = + ApiResponse; diff --git a/app/client/src/git/requests/updateGlobalProfileRequest.ts b/app/client/src/git/requests/updateGlobalProfileRequest.ts index 647ff15426f7..62d11a931a70 100644 --- a/app/client/src/git/requests/updateGlobalProfileRequest.ts +++ b/app/client/src/git/requests/updateGlobalProfileRequest.ts @@ -1,4 +1,4 @@ -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; import type { UpdateGlobalProfileRequestParams, UpdateGlobalProfileResponse, @@ -8,6 +8,6 @@ import { GIT_BASE_URL } from "./constants"; export default async function updateGlobalProfileRequest( params: UpdateGlobalProfileRequestParams, -): Promise> { +): AxiosPromise { return Api.post(`${GIT_BASE_URL}/profile/default`, params); } diff --git a/app/client/src/git/requests/updateProtectedBranchesRequest.ts b/app/client/src/git/requests/updateProtectedBranchesRequest.ts index 5af603ecaa7d..aa3822c1b5e2 100644 --- a/app/client/src/git/requests/updateProtectedBranchesRequest.ts +++ b/app/client/src/git/requests/updateProtectedBranchesRequest.ts @@ -4,12 +4,12 @@ import type { UpdateProtectedBranchesRequestParams, UpdateProtectedBranchesResponse, } from "./updateProtectedBranchesRequest.types"; -import type { AxiosResponse } from "axios"; +import type { AxiosPromise } from "axios"; export default async function updateProtectedBranchesRequest( baseApplicationId: string, params: UpdateProtectedBranchesRequestParams, -): Promise> { +): AxiosPromise { return Api.post( `${GIT_BASE_URL}/branch/app/${baseApplicationId}/protected`, params, diff --git a/app/client/src/git/sagas/fetchGitMetadataSaga.ts b/app/client/src/git/sagas/fetchGitMetadataSaga.ts new file mode 100644 index 000000000000..9f694ff10f36 --- /dev/null +++ b/app/client/src/git/sagas/fetchGitMetadataSaga.ts @@ -0,0 +1,35 @@ +import fetchGitMetadataRequest from "git/requests/fetchGitMetadataRequest"; +import type { FetchGitMetadataResponse } from "git/requests/fetchGitMetadataRequest.types"; +import { gitArtifactActions } from "git/store/gitArtifactSlice"; +import type { GitArtifactPayloadAction } from "git/store/types"; +import { call, put } from "redux-saga/effects"; +import { validateResponse } from "sagas/ErrorSagas"; + +export default function* fetchGitMetadataSaga( + action: GitArtifactPayloadAction, +) { + const { artifactType, baseArtifactId } = action.payload; + const basePayload = { artifactType, baseArtifactId }; + let response: FetchGitMetadataResponse | undefined; + + try { + response = yield call(fetchGitMetadataRequest, baseArtifactId); + const isValidResponse: boolean = yield validateResponse(response, false); + + if (response && isValidResponse) { + yield put( + gitArtifactActions.fetchGitMetadataSuccess({ + ...basePayload, + responseData: response.data, + }), + ); + } + } catch (error) { + yield put( + gitArtifactActions.fetchGitMetadataError({ + ...basePayload, + error: error as string, + }), + ); + } +} diff --git a/app/client/src/git/sagas/fetchProtectedBranchesSaga.ts b/app/client/src/git/sagas/fetchProtectedBranchesSaga.ts new file mode 100644 index 000000000000..c0c5d70e7314 --- /dev/null +++ b/app/client/src/git/sagas/fetchProtectedBranchesSaga.ts @@ -0,0 +1,36 @@ +import fetchProtectedBranchesRequest from "git/requests/fetchProtectedBranchesRequest"; +import type { FetchProtectedBranchesResponse } from "git/requests/fetchProtectedBranchesRequest.types"; +import { gitArtifactActions } from "git/store/gitArtifactSlice"; +import type { GitArtifactPayloadAction } from "git/store/types"; +import { call, put } from "redux-saga/effects"; +import { validateResponse } from "sagas/ErrorSagas"; + +export default function* fetchProtectedBranchesSaga( + action: GitArtifactPayloadAction, +) { + const { artifactType, baseArtifactId } = action.payload; + const basePayload = { artifactType, baseArtifactId }; + let response: FetchProtectedBranchesResponse | undefined; + + try { + response = yield call(fetchProtectedBranchesRequest, baseArtifactId); + + const isValidResponse: boolean = yield validateResponse(response); + + if (response && isValidResponse) { + yield put( + gitArtifactActions.fetchProtectedBranchesSuccess({ + ...basePayload, + responseData: response.data, + }), + ); + } + } catch (error) { + yield put( + gitArtifactActions.fetchProtectedBranchesError({ + ...basePayload, + error: error as string, + }), + ); + } +} diff --git a/app/client/src/git/sagas/fetchStatusSaga.ts b/app/client/src/git/sagas/fetchStatusSaga.ts new file mode 100644 index 000000000000..b0bf5d97e4da --- /dev/null +++ b/app/client/src/git/sagas/fetchStatusSaga.ts @@ -0,0 +1,43 @@ +import fetchStatusRequest from "git/requests/fetchStatusRequest"; +import type { FetchStatusResponse } from "git/requests/fetchStatusRequest.types"; +import type { FetchStatusInitPayload } from "git/store/actions/fetchStatusActions"; +import { gitArtifactActions } from "git/store/gitArtifactSlice"; +import type { GitArtifactPayloadAction } from "git/store/types"; +import { call, put } from "redux-saga/effects"; +import { validateResponse } from "sagas/ErrorSagas"; + +export default function* fetchStatusSaga( + action: GitArtifactPayloadAction, +) { + const { artifactType, baseArtifactId } = action.payload; + const basePayload = { artifactType, baseArtifactId }; + let response: FetchStatusResponse | undefined; + + try { + response = yield call(fetchStatusRequest, baseArtifactId); + const isValidResponse: boolean = yield validateResponse(response); + + if (response && isValidResponse) { + yield put( + gitArtifactActions.fetchStatusSuccess({ + ...basePayload, + responseData: response.data, + }), + ); + } + } catch (error) { + yield put( + gitArtifactActions.fetchStatusError({ + ...basePayload, + error: error as string, + }), + ); + + // ! case: BETTER ERROR HANDLING + // if ((error as Error)?.message?.includes("Auth fail")) { + // payload.error = new Error(createMessage(ERROR_GIT_AUTH_FAIL)); + // } else if ((error as Error)?.message?.includes("Invalid remote: origin")) { + // payload.error = new Error(createMessage(ERROR_GIT_INVALID_REMOTE)); + // } + } +} diff --git a/app/client/src/git/sagas/index.ts b/app/client/src/git/sagas/index.ts index 076853a8612a..60108c4d068b 100644 --- a/app/client/src/git/sagas/index.ts +++ b/app/client/src/git/sagas/index.ts @@ -1,37 +1,99 @@ -import { gitArtifactActions } from "git/store/gitArtifactSlice"; -import { all, takeLatest } from "redux-saga/effects"; +import { + actionChannel, + call, + fork, + take, + takeLatest, +} from "redux-saga/effects"; +import type { TakeableChannel } from "redux-saga"; +import type { PayloadAction } from "@reduxjs/toolkit"; +import { objectKeys } from "@appsmith/utils"; +import { gitConfigActions } from "../store/gitConfigSlice"; +import { gitArtifactActions } from "../store/gitArtifactSlice"; import connectSaga from "./connectSaga"; import commitSaga from "./commitSaga"; -import { gitConfigActions } from "git/store/gitConfigSlice"; import fetchGlobalProfileSaga from "./fetchGlobalProfileSaga"; import fetchBranchesSaga from "./fetchBranchesSaga"; import fetchLocalProfileSaga from "./fetchLocalProfileSaga"; import updateLocalProfileSaga from "./updateLocalProfileSaga"; import updateGlobalProfileSaga from "./updateGlobalProfileSaga"; +import initGitForEditorSaga from "./initGitSaga"; +import fetchGitMetadataSaga from "./fetchGitMetadataSaga"; +import triggerAutocommitSaga from "./triggerAutocommitSaga"; +import fetchStatusSaga from "./fetchStatusSaga"; +import fetchProtectedBranchesSaga from "./fetchProtectedBranchesSaga"; -export function* gitSagas() { - yield all([ - takeLatest(gitArtifactActions.connectInit.type, connectSaga), - - // branches - takeLatest(gitArtifactActions.fetchBranchesInit.type, fetchBranchesSaga), - - takeLatest(gitArtifactActions.commitInit.type, commitSaga), - takeLatest( - gitArtifactActions.fetchLocalProfileInit.type, - fetchLocalProfileSaga, - ), - takeLatest( - gitArtifactActions.updateLocalProfileInit.type, - updateLocalProfileSaga, - ), - takeLatest( - gitConfigActions.fetchGlobalProfileInit.type, - fetchGlobalProfileSaga, - ), - takeLatest( - gitConfigActions.updateGlobalProfileInit.type, - updateGlobalProfileSaga, - ), - ]); +const gitRequestBlockingActions: Record< + string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (action: PayloadAction) => Generator +> = { + // init + [gitArtifactActions.fetchGitMetadataInit.type]: fetchGitMetadataSaga, + + // connect + [gitArtifactActions.connectInit.type]: connectSaga, + + // ops + [gitArtifactActions.commitInit.type]: commitSaga, + [gitArtifactActions.fetchStatusInit.type]: fetchStatusSaga, + + // branches + [gitArtifactActions.fetchBranchesInit.type]: fetchBranchesSaga, + + // settings + [gitArtifactActions.fetchLocalProfileInit.type]: fetchLocalProfileSaga, + [gitArtifactActions.updateLocalProfileInit.type]: updateLocalProfileSaga, + [gitConfigActions.fetchGlobalProfileInit.type]: fetchGlobalProfileSaga, + [gitConfigActions.updateGlobalProfileInit.type]: updateGlobalProfileSaga, + + // autocommit + [gitArtifactActions.triggerAutocommitInit.type]: triggerAutocommitSaga, +}; + +const gitRequestNonBlockingActions: Record< + string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (action: PayloadAction) => Generator +> = { + // init + [gitArtifactActions.initGitForEditor.type]: initGitForEditorSaga, + + // settings + [gitArtifactActions.fetchProtectedBranchesInit.type]: + fetchProtectedBranchesSaga, +}; + +/** + * All git actions on the server are behind a lock, + * that means that only one action can be performed at once. + * + * To follow the same principle, we will queue all actions from the client + * as well and only perform one action at a time. + * + * This will ensure that client is not running parallel requests to the server for git + * */ +function* watchGitBlockingRequests() { + const gitActionChannel: TakeableChannel = yield actionChannel( + objectKeys(gitRequestBlockingActions), + ); + + while (true) { + const action: PayloadAction = yield take(gitActionChannel); + + yield call(gitRequestBlockingActions[action.type], action); + } +} + +function* watchGitNonBlockingRequests() { + const keys = objectKeys(gitRequestNonBlockingActions); + + for (const actionType of keys) { + yield takeLatest(actionType, gitRequestNonBlockingActions[actionType]); + } +} + +export default function* gitSagas() { + yield fork(watchGitNonBlockingRequests); + yield fork(watchGitBlockingRequests); } diff --git a/app/client/src/git/sagas/initGitSaga.ts b/app/client/src/git/sagas/initGitSaga.ts new file mode 100644 index 000000000000..c0177f32f5bd --- /dev/null +++ b/app/client/src/git/sagas/initGitSaga.ts @@ -0,0 +1,30 @@ +import { GitArtifactType } from "git/constants/enums"; +import type { InitGitForEditorPayload } from "git/store/actions/initGitActions"; +import { gitArtifactActions } from "git/store/gitArtifactSlice"; +import type { GitArtifactPayloadAction } from "git/store/types"; +import { put, take } from "redux-saga/effects"; + +export default function* initGitForEditorSaga( + action: GitArtifactPayloadAction, +) { + const { artifact, artifactType, baseArtifactId } = action.payload; + const basePayload = { artifactType, baseArtifactId }; + + yield put(gitArtifactActions.mount(basePayload)); + + if (artifactType === GitArtifactType.Application) { + if (!!artifact.gitApplicationMetadata) { + yield put(gitArtifactActions.fetchGitMetadataInit(basePayload)); + yield take(gitArtifactActions.fetchGitMetadataSuccess.type); + yield put( + gitArtifactActions.triggerAutocommitInit({ + ...basePayload, + artifactId: artifact.id, + }), + ); + yield put(gitArtifactActions.fetchBranchesInit(basePayload)); + yield put(gitArtifactActions.fetchProtectedBranchesInit(basePayload)); + yield put(gitArtifactActions.fetchStatusInit(basePayload)); + } + } +} diff --git a/app/client/src/git/sagas/triggerAutocommitSaga.ts b/app/client/src/git/sagas/triggerAutocommitSaga.ts new file mode 100644 index 000000000000..ac50a6b03c08 --- /dev/null +++ b/app/client/src/git/sagas/triggerAutocommitSaga.ts @@ -0,0 +1,131 @@ +import { triggerAutocommitSuccessAction } from "actions/gitSyncActions"; +import { AutocommitStatus, type GitArtifactType } from "git/constants/enums"; +import fetchAutocommitProgressRequest from "git/requests/fetchAutocommitProgressRequest"; +import type { + FetchAutocommitProgressResponse, + FetchAutocommitProgressResponseData, +} from "git/requests/fetchAutocommitProgressRequest.types"; +import triggerAutocommitRequest from "git/requests/triggerAutocommitRequest"; +import type { + TriggerAutocommitResponse, + TriggerAutocommitResponseData, +} from "git/requests/triggerAutocommitRequest.types"; +import type { TriggerAutocommitInitPayload } from "git/store/actions/triggerAutocommitActions"; +import { gitArtifactActions } from "git/store/gitArtifactSlice"; +import { selectAutocommitEnabled } from "git/store/selectors/gitSingleArtifactSelectors"; +import type { GitArtifactPayloadAction } from "git/store/types"; +import { + call, + cancel, + delay, + fork, + put, + select, + take, +} from "redux-saga/effects"; +import type { Task } from "redux-saga"; +import { validateResponse } from "sagas/ErrorSagas"; + +const AUTOCOMMIT_POLL_DELAY = 1000; +const AUTOCOMMIT_WHITELISTED_STATES = [ + AutocommitStatus.PUBLISHED, + AutocommitStatus.IN_PROGRESS, + AutocommitStatus.LOCKED, +]; + +interface PollAutocommitProgressParams { + artifactType: keyof typeof GitArtifactType; + baseArtifactId: string; + artifactId: string; +} + +function isAutocommitHappening( + responseData: + | TriggerAutocommitResponseData + | FetchAutocommitProgressResponseData + | undefined, +): boolean { + return ( + !!responseData && + AUTOCOMMIT_WHITELISTED_STATES.includes(responseData.autoCommitResponse) + ); +} + +function* pollAutocommitProgressSaga(params: PollAutocommitProgressParams) { + const { artifactId, artifactType, baseArtifactId } = params; + const basePayload = { artifactType, baseArtifactId }; + let triggerResponse: TriggerAutocommitResponse | undefined; + + try { + triggerResponse = yield call(triggerAutocommitRequest, artifactId); + const isValidResponse: boolean = yield validateResponse(triggerResponse); + + if (triggerResponse && isValidResponse) { + yield put(gitArtifactActions.triggerAutocommitSuccess(basePayload)); + } + } catch (error) { + yield put( + gitArtifactActions.triggerAutocommitError({ + ...basePayload, + error: error as string, + }), + ); + } + + try { + if (isAutocommitHappening(triggerResponse?.data)) { + yield put(gitArtifactActions.pollAutocommitProgressStart(basePayload)); + + while (true) { + yield put(gitArtifactActions.fetchAutocommitProgressInit(basePayload)); + const progressResponse: FetchAutocommitProgressResponse = yield call( + fetchAutocommitProgressRequest, + baseArtifactId, + ); + const isValidResponse: boolean = + yield validateResponse(progressResponse); + + if (isValidResponse && !isAutocommitHappening(progressResponse?.data)) { + yield put(gitArtifactActions.pollAutocommitProgressStop(basePayload)); + } + + if (!isValidResponse) { + yield put(gitArtifactActions.pollAutocommitProgressStop(basePayload)); + } + + yield delay(AUTOCOMMIT_POLL_DELAY); + } + } else { + yield put(gitArtifactActions.pollAutocommitProgressStop(basePayload)); + } + } catch (error) { + yield put(gitArtifactActions.pollAutocommitProgressStop(basePayload)); + yield put( + gitArtifactActions.fetchAutocommitProgressError({ + ...basePayload, + error: error as string, + }), + ); + } +} + +export default function* triggerAutocommitSaga( + action: GitArtifactPayloadAction, +) { + const { artifactId, artifactType, baseArtifactId } = action.payload; + const basePayload = { artifactType, baseArtifactId }; + const isAutocommitEnabled: boolean = yield select( + selectAutocommitEnabled, + basePayload, + ); + + if (isAutocommitEnabled) { + const params = { artifactType, baseArtifactId, artifactId }; + const pollTask: Task = yield fork(pollAutocommitProgressSaga, params); + + yield take(gitArtifactActions.pollAutocommitProgressStop.type); + yield cancel(pollTask); + } else { + yield put(triggerAutocommitSuccessAction()); + } +} diff --git a/app/client/src/git/store/actions/fetchAutocommitProgressActions.ts b/app/client/src/git/store/actions/fetchAutocommitProgressActions.ts index 2736b0c72cc4..a7d92793e4aa 100644 --- a/app/client/src/git/store/actions/fetchAutocommitProgressActions.ts +++ b/app/client/src/git/store/actions/fetchAutocommitProgressActions.ts @@ -1,8 +1,4 @@ -import type { - GitArtifactPayloadAction, - GitArtifactErrorPayloadAction, - GitAutocommitProgress, -} from "../types"; +import type { GitAsyncErrorPayload } from "../types"; import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; export const fetchAutocommitProgressInitAction = createSingleArtifactAction( @@ -15,27 +11,19 @@ export const fetchAutocommitProgressInitAction = createSingleArtifactAction( ); export const fetchAutocommitProgressSuccessAction = createSingleArtifactAction( - ( - state, - action: GitArtifactPayloadAction<{ - autocommitProgress: GitAutocommitProgress; - }>, - ) => { + (state) => { state.apiResponses.autocommitProgress.loading = false; - state.apiResponses.autocommitProgress.value = - action.payload.autocommitProgress; return state; }, ); -export const fetchAutocommitProgressErrorAction = createSingleArtifactAction( - (state, action: GitArtifactErrorPayloadAction) => { +export const fetchAutocommitProgressErrorAction = + createSingleArtifactAction((state, action) => { const { error } = action.payload; state.apiResponses.autocommitProgress.loading = false; state.apiResponses.autocommitProgress.error = error; return state; - }, -); + }); diff --git a/app/client/src/git/store/actions/fetchGitMetadataActions.ts b/app/client/src/git/store/actions/fetchGitMetadataActions.ts new file mode 100644 index 000000000000..6bbb8d41d380 --- /dev/null +++ b/app/client/src/git/store/actions/fetchGitMetadataActions.ts @@ -0,0 +1,31 @@ +import type { GitAsyncErrorPayload, GitAsyncSuccessPayload } from "../types"; +import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; +import type { FetchGitMetadataResponseData } from "git/requests/fetchGitMetadataRequest.types"; + +export const fetchGitMetadataInitAction = createSingleArtifactAction( + (state) => { + state.apiResponses.metadata.loading = true; + state.apiResponses.metadata.error = null; + + return state; + }, +); + +export const fetchGitMetadataSuccessAction = createSingleArtifactAction< + GitAsyncSuccessPayload +>((state, action) => { + state.apiResponses.metadata.loading = false; + state.apiResponses.metadata.value = action.payload.responseData; + + return state; +}); + +export const fetchGitMetadataErrorAction = + createSingleArtifactAction((state, action) => { + const { error } = action.payload; + + state.apiResponses.metadata.loading = false; + state.apiResponses.metadata.error = error; + + return state; + }); diff --git a/app/client/src/git/store/actions/fetchMetadataActions.ts b/app/client/src/git/store/actions/fetchMetadataActions.ts deleted file mode 100644 index f4d1fd9da2cb..000000000000 --- a/app/client/src/git/store/actions/fetchMetadataActions.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { - GitArtifactPayloadAction, - GitArtifactErrorPayloadAction, - GitMetadata, -} from "../types"; -import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; - -export const fetchMetadataInitAction = createSingleArtifactAction((state) => { - state.apiResponses.metadata.loading = true; - state.apiResponses.metadata.error = null; - - return state; -}); - -export const fetchMetadataSuccessAction = createSingleArtifactAction( - (state, action: GitArtifactPayloadAction<{ metadata: GitMetadata }>) => { - state.apiResponses.metadata.loading = false; - state.apiResponses.metadata.value = action.payload.metadata; - - return state; - }, -); - -export const fetchMetadataErrorAction = createSingleArtifactAction( - (state, action: GitArtifactErrorPayloadAction) => { - const { error } = action.payload; - - state.apiResponses.metadata.loading = false; - state.apiResponses.metadata.error = error; - - return state; - }, -); diff --git a/app/client/src/git/store/actions/fetchProtectedBranchesActions.ts b/app/client/src/git/store/actions/fetchProtectedBranchesActions.ts index 223952a3962b..dc66ff2290c0 100644 --- a/app/client/src/git/store/actions/fetchProtectedBranchesActions.ts +++ b/app/client/src/git/store/actions/fetchProtectedBranchesActions.ts @@ -1,9 +1,6 @@ -import type { - GitArtifactPayloadAction, - GitArtifactErrorPayloadAction, - GitProtectedBranches, -} from "../types"; +import type { GitAsyncSuccessPayload, GitAsyncErrorPayload } from "../types"; import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; +import type { FetchProtectedBranchesResponseData } from "git/requests/fetchProtectedBranchesRequest.types"; export const fetchProtectedBranchesInitAction = createSingleArtifactAction( (state) => { @@ -14,28 +11,21 @@ export const fetchProtectedBranchesInitAction = createSingleArtifactAction( }, ); -export const fetchProtectedBranchesSuccessAction = createSingleArtifactAction( - ( - state, - action: GitArtifactPayloadAction<{ - protectedBranches: GitProtectedBranches; - }>, - ) => { - state.apiResponses.protectedBranches.loading = false; - state.apiResponses.protectedBranches.value = - action.payload.protectedBranches; +export const fetchProtectedBranchesSuccessAction = createSingleArtifactAction< + GitAsyncSuccessPayload +>((state, action) => { + state.apiResponses.protectedBranches.loading = false; + state.apiResponses.protectedBranches.value = action.payload.responseData; - return state; - }, -); + return state; +}); -export const fetchProtectedBranchesErrorAction = createSingleArtifactAction( - (state, action: GitArtifactErrorPayloadAction) => { +export const fetchProtectedBranchesErrorAction = + createSingleArtifactAction((state, action) => { const { error } = action.payload; state.apiResponses.protectedBranches.loading = false; state.apiResponses.protectedBranches.error = error; return state; - }, -); + }); diff --git a/app/client/src/git/store/actions/initGitActions.ts b/app/client/src/git/store/actions/initGitActions.ts new file mode 100644 index 000000000000..5ba453b0edaf --- /dev/null +++ b/app/client/src/git/store/actions/initGitActions.ts @@ -0,0 +1,15 @@ +import type { FetchGitMetadataResponseData } from "git/requests/fetchGitMetadataRequest.types"; +import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; + +export interface InitGitForEditorPayload { + artifact: { + id: string; + baseId: string; + gitApplicationMetadata?: Partial; + }; +} + +export const initGitForEditorAction = + createSingleArtifactAction((state) => { + return state; + }); diff --git a/app/client/src/git/store/actions/triggerAutocommitActions.ts b/app/client/src/git/store/actions/triggerAutocommitActions.ts index 414de9ed68c8..28e88ecd2f16 100644 --- a/app/client/src/git/store/actions/triggerAutocommitActions.ts +++ b/app/client/src/git/store/actions/triggerAutocommitActions.ts @@ -1,14 +1,17 @@ import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; -import type { GitArtifactErrorPayloadAction } from "../types"; +import type { GitAsyncErrorPayload } from "../types"; -export const triggerAutocommitInitAction = createSingleArtifactAction( - (state) => { +export interface TriggerAutocommitInitPayload { + artifactId: string; +} + +export const triggerAutocommitInitAction = + createSingleArtifactAction((state) => { state.apiResponses.triggerAutocommit.loading = true; state.apiResponses.triggerAutocommit.error = null; return state; - }, -); + }); export const triggerAutocommitSuccessAction = createSingleArtifactAction( (state) => { @@ -18,13 +21,28 @@ export const triggerAutocommitSuccessAction = createSingleArtifactAction( }, ); -export const triggerAutocommitErrorAction = createSingleArtifactAction( - (state, action: GitArtifactErrorPayloadAction) => { +export const triggerAutocommitErrorAction = + createSingleArtifactAction((state, action) => { const { error } = action.payload; state.apiResponses.triggerAutocommit.loading = false; state.apiResponses.triggerAutocommit.error = error; + return state; + }); + +export const pollAutocommitProgressStartAction = createSingleArtifactAction( + (state) => { + state.ui.autocommitPolling = true; + + return state; + }, +); + +export const pollAutocommitProgressStopAction = createSingleArtifactAction( + (state) => { + state.ui.autocommitPolling = false; + return state; }, ); diff --git a/app/client/src/git/store/gitArtifactSlice.ts b/app/client/src/git/store/gitArtifactSlice.ts index da29a9f54b4a..425b6421d403 100644 --- a/app/client/src/git/store/gitArtifactSlice.ts +++ b/app/client/src/git/store/gitArtifactSlice.ts @@ -8,10 +8,10 @@ import { connectSuccessAction, } from "./actions/connectActions"; import { - fetchMetadataErrorAction, - fetchMetadataInitAction, - fetchMetadataSuccessAction, -} from "./actions/fetchMetadataActions"; + fetchGitMetadataErrorAction, + fetchGitMetadataInitAction, + fetchGitMetadataSuccessAction, +} from "./actions/fetchGitMetadataActions"; import { fetchBranchesErrorAction, fetchBranchesInitAction, @@ -79,6 +79,34 @@ import { mergeInitAction, mergeSuccessAction, } from "./actions/mergeActions"; +import { + pollAutocommitProgressStopAction, + pollAutocommitProgressStartAction, + triggerAutocommitErrorAction, + triggerAutocommitInitAction, + triggerAutocommitSuccessAction, +} from "./actions/triggerAutocommitActions"; +import { + toggleAutocommitErrorAction, + toggleAutocommitInitAction, + toggleAutocommitSuccessAction, +} from "./actions/toggleAutocommitActions"; +import { + fetchProtectedBranchesErrorAction, + fetchProtectedBranchesInitAction, + fetchProtectedBranchesSuccessAction, +} from "./actions/fetchProtectedBranchesActions"; +import { + updateProtectedBranchesErrorAction, + updateProtectedBranchesInitAction, + updateProtectedBranchesSuccessAction, +} from "./actions/updateProtectedBranchesActions"; +import { initGitForEditorAction } from "./actions/initGitActions"; +import { + fetchAutocommitProgressErrorAction, + fetchAutocommitProgressInitAction, + fetchAutocommitProgressSuccessAction, +} from "./actions/fetchAutocommitProgressActions"; const initialState: GitArtifactReduxState = {}; @@ -87,8 +115,13 @@ export const gitArtifactSlice = createSlice({ reducerPath: "git.artifact", initialState, reducers: { + // init + initGitForEditor: initGitForEditorAction, mount: mountAction, unmount: unmountAction, + fetchGitMetadataInit: fetchGitMetadataInitAction, + fetchGitMetadataSuccess: fetchGitMetadataSuccessAction, + fetchGitMetadataError: fetchGitMetadataErrorAction, // connect connectInit: connectInitAction, @@ -135,17 +168,31 @@ export const gitArtifactSlice = createSlice({ // settings toggleGitSettingsModal: toggleGitSettingsModalAction, - - // metadata - fetchMetadataInit: fetchMetadataInitAction, - fetchMetadataSuccess: fetchMetadataSuccessAction, - fetchMetadataError: fetchMetadataErrorAction, fetchLocalProfileInit: fetchLocalProfileInitAction, fetchLocalProfileSuccess: fetchLocalProfileSuccessAction, fetchLocalProfileError: fetchLocalProfileErrorAction, updateLocalProfileInit: updateLocalProfileInitAction, updateLocalProfileSuccess: updateLocalProfileSuccessAction, updateLocalProfileError: updateLocalProfileErrorAction, + fetchProtectedBranchesInit: fetchProtectedBranchesInitAction, + fetchProtectedBranchesSuccess: fetchProtectedBranchesSuccessAction, + fetchProtectedBranchesError: fetchProtectedBranchesErrorAction, + updateProtectedBranchesInit: updateProtectedBranchesInitAction, + updateProtectedBranchesSuccess: updateProtectedBranchesSuccessAction, + updateProtectedBranchesError: updateProtectedBranchesErrorAction, + + // autocommit + toggleAutocommitInit: toggleAutocommitInitAction, + toggleAutocommitSuccess: toggleAutocommitSuccessAction, + toggleAutocommitError: toggleAutocommitErrorAction, + triggerAutocommitInit: triggerAutocommitInitAction, + triggerAutocommitSuccess: triggerAutocommitSuccessAction, + triggerAutocommitError: triggerAutocommitErrorAction, + fetchAutocommitProgressInit: fetchAutocommitProgressInitAction, + fetchAutocommitProgressSuccess: fetchAutocommitProgressSuccessAction, + fetchAutocommitProgressError: fetchAutocommitProgressErrorAction, + pollAutocommitProgressStart: pollAutocommitProgressStartAction, + pollAutocommitProgressStop: pollAutocommitProgressStopAction, }, }); diff --git a/app/client/src/git/store/helpers/gitSingleArtifactInitialState.ts b/app/client/src/git/store/helpers/gitSingleArtifactInitialState.ts index a194106914a3..589eb44be8de 100644 --- a/app/client/src/git/store/helpers/gitSingleArtifactInitialState.ts +++ b/app/client/src/git/store/helpers/gitSingleArtifactInitialState.ts @@ -33,6 +33,8 @@ const gitSingleArtifactInitialUIState: GitSingleArtifactUIReduxState = { repoLimitErrorModal: { open: false, }, + autocommitModalOpen: false, + autocommitPolling: false, }; const gitSingleArtifactInitialAPIResponses: GitSingleArtifactAPIResponsesReduxState = @@ -112,7 +114,6 @@ const gitSingleArtifactInitialAPIResponses: GitSingleArtifactAPIResponsesReduxSt error: null, }, autocommitProgress: { - value: null, loading: false, error: null, }, diff --git a/app/client/src/git/store/selectors/gitSingleArtifactSelectors.ts b/app/client/src/git/store/selectors/gitSingleArtifactSelectors.ts index e67bcdc99b4a..6e36c250d148 100644 --- a/app/client/src/git/store/selectors/gitSingleArtifactSelectors.ts +++ b/app/client/src/git/store/selectors/gitSingleArtifactSelectors.ts @@ -15,6 +15,17 @@ export const selectSingleArtifact = ( ]; }; +// metadata +export const selectGitMetadata = ( + state: GitRootState, + artifactDef: GitArtifactDef, +) => selectSingleArtifact(state, artifactDef)?.apiResponses.metadata; + +export const selectGitConnected = ( + state: GitRootState, + artifactDef: GitArtifactDef, +) => !!selectGitMetadata(state, artifactDef).value; + // git ops export const selectCommit = ( state: GitRootState, @@ -43,6 +54,16 @@ export const selectPull = (state: GitRootState, artifactDef: GitArtifactDef) => selectSingleArtifact(state, artifactDef)?.apiResponses?.pull; // git branches + +export const selectCurrentBranch = ( + state: GitRootState, + artifactDef: GitArtifactDef, +) => { + const gitMetadataState = selectGitMetadata(state, artifactDef).value; + + return gitMetadataState?.branchName; +}; + export const selectBranches = ( state: GitRootState, artifactDef: GitArtifactDef, @@ -62,3 +83,34 @@ export const selectCheckoutBranch = ( state: GitRootState, artifactDef: GitArtifactDef, ) => selectSingleArtifact(state, artifactDef)?.apiResponses.checkoutBranch; + +// autocommit +export const selectAutocommitEnabled = ( + state: GitRootState, + artifactDef: GitArtifactDef, +) => { + const gitMetadata = selectGitMetadata(state, artifactDef).value; + + return gitMetadata?.autoCommitConfig?.enabled; +}; + +export const selectAutocommitPolling = ( + state: GitRootState, + artifactDef: GitArtifactDef, +) => selectSingleArtifact(state, artifactDef)?.ui.autocommitPolling; + +// protected branches +export const selectProtectedBranches = ( + state: GitRootState, + artifactDef: GitArtifactDef, +) => selectSingleArtifact(state, artifactDef)?.apiResponses.protectedBranches; + +export const selectProtectedMode = ( + state: GitRootState, + artifactDef: GitArtifactDef, +) => { + const currentBranch = selectCurrentBranch(state, artifactDef); + const protectedBranches = selectProtectedBranches(state, artifactDef).value; + + return protectedBranches?.includes(currentBranch ?? ""); +}; diff --git a/app/client/src/git/store/types.ts b/app/client/src/git/store/types.ts index 695572840d93..b3a8c18fc02a 100644 --- a/app/client/src/git/store/types.ts +++ b/app/client/src/git/store/types.ts @@ -11,13 +11,8 @@ import type { FetchBranchesResponseData } from "../requests/fetchBranchesRequest import type { FetchLocalProfileResponseData } from "../requests/fetchLocalProfileRequest.types"; import type { FetchStatusResponseData } from "git/requests/fetchStatusRequest.types"; import type { FetchMergeStatusResponseData } from "git/requests/fetchMergeStatusRequest.types"; - -// These will be updated when contracts are finalized -export type GitMetadata = Record; - -export type GitProtectedBranches = Record; - -export type GitAutocommitProgress = Record; +import type { FetchGitMetadataResponseData } from "git/requests/fetchGitMetadataRequest.types"; +import type { FetchProtectedBranchesResponseData } from "git/requests/fetchProtectedBranchesRequest.types"; export type GitSSHKey = Record; @@ -32,7 +27,7 @@ interface AsyncStateWithoutValue { error: string | null; } export interface GitSingleArtifactAPIResponsesReduxState { - metadata: AsyncState; + metadata: AsyncState; connect: AsyncStateWithoutValue; status: AsyncState; commit: AsyncStateWithoutValue; @@ -47,9 +42,9 @@ export interface GitSingleArtifactAPIResponsesReduxState { localProfile: AsyncState; updateLocalProfile: AsyncStateWithoutValue; disconnect: AsyncStateWithoutValue; - protectedBranches: AsyncState; + protectedBranches: AsyncState; updateProtectedBranches: AsyncStateWithoutValue; - autocommitProgress: AsyncState; + autocommitProgress: AsyncStateWithoutValue; toggleAutocommit: AsyncStateWithoutValue; triggerAutocommit: AsyncStateWithoutValue; sshKey: AsyncState; @@ -79,6 +74,8 @@ export interface GitSingleArtifactUIReduxState { repoLimitErrorModal: { open: boolean; }; + autocommitPolling: boolean; + autocommitModalOpen: boolean; } export interface GitSingleArtifactReduxState { ui: GitSingleArtifactUIReduxState;