chore: git mod - adding init saga and connecting components to ctx (#38088)

## Description
- Adds more selectors
- Adds more sagas
- Introduces init steps for git
- Improvements upon CtxAwareGitQuickActions

Fixes #37800 
Fixes #36814 


## 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/12295168481>
> Commit: ea1ab497cad677aac36f044462e623e526d421d1
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12295168481&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Git`
> Spec:
> <hr>Thu, 12 Dec 2024 12:06:09 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

## Summary by CodeRabbit

- **New Features**
  - Introduced a context provider for managing Git-related state.
  - Added a `GitQuickActions` component for various Git actions.
- Implemented custom hooks for managing Git branches, metadata,
operations, and settings.
- Added Redux Saga functions for fetching Git metadata and protected
branches.
  - Enhanced autocommit functionality with polling for progress.
- Introduced actions for managing the autocommit process and fetching
protected branches.
- Added new actions to initialize Git for editor context and manage
loading states.

- **Bug Fixes**
  - Improved error handling in various action creators and sagas.

- **Chores**
- Updated action creators and types for better type safety and clarity.
- Refined test cases for components to ensure accurate button selection.
- Modified request functions to utilize Axios promises directly for
better consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Rudraprasad Das 2024-12-12 23:18:09 +08:00 committed by GitHub
parent 8e30a389e4
commit 0808b279fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 782 additions and 230 deletions

View File

@ -5,9 +5,13 @@ import useStatusChangeCount from "./hooks/useStatusChangeCount";
function CtxAwareGitQuickActions() { function CtxAwareGitQuickActions() {
const { const {
autocommitEnabled,
autocommitPolling,
discard, discard,
discardLoading, discardLoading,
fetchStatusLoading, fetchStatusLoading,
gitConnected,
protectedMode,
pull, pull,
pullError, pullError,
pullLoading, pullLoading,
@ -17,12 +21,7 @@ function CtxAwareGitQuickActions() {
toggleGitSettingsModal, toggleGitSettingsModal,
} = useGitContext(); } = useGitContext();
const isGitConnected = false; const connectPermitted = true;
const isAutocommitEnabled = true;
const isAutocommitPolling = false;
const isConnectPermitted = true;
const isProtectedMode = false;
const isPullFailing = !!pullError; const isPullFailing = !!pullError;
const isStatusClean = status?.isClean ?? false; const isStatusClean = status?.isClean ?? false;
const statusBehindCount = status?.behindCount ?? 0; const statusBehindCount = status?.behindCount ?? 0;
@ -31,13 +30,13 @@ function CtxAwareGitQuickActions() {
return ( return (
<GitQuickActions <GitQuickActions
discard={discard} discard={discard}
isAutocommitEnabled={isAutocommitEnabled} isAutocommitEnabled={autocommitEnabled}
isAutocommitPolling={isAutocommitPolling} isAutocommitPolling={autocommitPolling}
isConnectPermitted={isConnectPermitted} isConnectPermitted={connectPermitted}
isDiscardLoading={discardLoading} isDiscardLoading={discardLoading}
isFetchStatusLoading={fetchStatusLoading} isFetchStatusLoading={fetchStatusLoading}
isGitConnected={isGitConnected} isGitConnected={gitConnected}
isProtectedMode={isProtectedMode} isProtectedMode={protectedMode}
isPullFailing={isPullFailing} isPullFailing={isPullFailing}
isPullLoading={pullLoading} isPullLoading={pullLoading}
isStatusClean={isStatusClean} isStatusClean={isStatusClean}

View File

@ -113,18 +113,18 @@ export default function useGitBranches({
}; };
return { return {
branches: branchesState?.value ?? null, branches: branchesState?.value,
fetchBranchesLoading: branchesState?.loading ?? false, fetchBranchesLoading: branchesState?.loading ?? false,
fetchBranchesError: branchesState?.error ?? null, fetchBranchesError: branchesState?.error,
fetchBranches, fetchBranches,
createBranchLoading: createBranchState?.loading ?? false, createBranchLoading: createBranchState?.loading ?? false,
createBranchError: createBranchState?.error ?? null, createBranchError: createBranchState?.error,
createBranch, createBranch,
deleteBranchLoading: deleteBranchState?.loading ?? false, deleteBranchLoading: deleteBranchState?.loading ?? false,
deleteBranchError: deleteBranchState?.error ?? null, deleteBranchError: deleteBranchState?.error,
deleteBranch, deleteBranch,
checkoutBranchLoading: checkoutBranchState?.loading ?? false, checkoutBranchLoading: checkoutBranchState?.loading ?? false,
checkoutBranchError: checkoutBranchState?.error ?? null, checkoutBranchError: checkoutBranchState?.error,
checkoutBranch, checkoutBranch,
toggleGitBranchListPopup, toggleGitBranchListPopup,
}; };

View File

@ -8,6 +8,8 @@ import useGitOps from "./useGitOps";
import useGitBranches from "./useGitBranches"; import useGitBranches from "./useGitBranches";
import useGitSettings from "./useGitSettings"; import useGitSettings from "./useGitSettings";
import { useMemo } from "react"; import { useMemo } from "react";
import type { UseGitMetadataReturnValue } from "./useGitMetadata";
import useGitMetadata from "./useGitMetadata";
interface UseGitContextValueParams { interface UseGitContextValueParams {
artifactType: keyof typeof GitArtifactType; artifactType: keyof typeof GitArtifactType;
@ -15,7 +17,8 @@ interface UseGitContextValueParams {
} }
export interface GitContextValue export interface GitContextValue
extends UseGitConnectReturnValue, extends UseGitMetadataReturnValue,
UseGitConnectReturnValue,
UseGitOpsReturnValue, UseGitOpsReturnValue,
UseGitSettingsReturnValue, UseGitSettingsReturnValue,
UseGitBranchesReturnValue {} UseGitBranchesReturnValue {}
@ -28,12 +31,14 @@ export default function useGitContextValue({
() => ({ artifactType, baseArtifactId }), () => ({ artifactType, baseArtifactId }),
[artifactType, baseArtifactId], [artifactType, baseArtifactId],
); );
const useGitMetadataReturnValue = useGitMetadata(basePayload);
const useGitConnectReturnValue = useGitConnect(basePayload); const useGitConnectReturnValue = useGitConnect(basePayload);
const useGitOpsReturnValue = useGitOps(basePayload); const useGitOpsReturnValue = useGitOps(basePayload);
const useGitBranchesReturnValue = useGitBranches(basePayload); const useGitBranchesReturnValue = useGitBranches(basePayload);
const useGitSettingsReturnValue = useGitSettings(basePayload); const useGitSettingsReturnValue = useGitSettings(basePayload);
return { return {
...useGitMetadataReturnValue,
...useGitOpsReturnValue, ...useGitOpsReturnValue,
...useGitBranchesReturnValue, ...useGitBranchesReturnValue,
...useGitConnectReturnValue, ...useGitConnectReturnValue,

View File

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

View File

@ -133,24 +133,24 @@ export default function useGitOps({
return { return {
commitLoading: commitState?.loading ?? false, commitLoading: commitState?.loading ?? false,
commitError: commitState?.error ?? null, commitError: commitState?.error,
commit, commit,
discardLoading: discardState?.loading ?? false, discardLoading: discardState?.loading ?? false,
discardError: discardState?.error ?? null, discardError: discardState?.error,
discard, discard,
status: statusState?.value ?? null, status: statusState?.value,
fetchStatusLoading: statusState?.loading ?? false, fetchStatusLoading: statusState?.loading ?? false,
fetchStatusError: statusState?.error ?? null, fetchStatusError: statusState?.error,
fetchStatus, fetchStatus,
mergeLoading: mergeState?.loading ?? false, mergeLoading: mergeState?.loading ?? false,
mergeError: mergeState?.error ?? null, mergeError: mergeState?.error,
merge, merge,
mergeStatus: mergeStatusState?.value ?? null, mergeStatus: mergeStatusState?.value,
fetchMergeStatusLoading: mergeStatusState?.loading ?? false, fetchMergeStatusLoading: mergeStatusState?.loading ?? false,
fetchMergeStatusError: mergeStatusState?.error ?? null, fetchMergeStatusError: mergeStatusState?.error,
fetchMergeStatus, fetchMergeStatus,
pullLoading: pullState?.loading ?? false, pullLoading: pullState?.loading ?? false,
pullError: pullState?.error ?? null, pullError: pullState?.error,
pull, pull,
toggleGitOpsModal, toggleGitOpsModal,
}; };

View File

@ -1,7 +1,15 @@
import type { GitArtifactType, GitSettingsTab } from "git/constants/enums"; import type { GitArtifactType, GitSettingsTab } from "git/constants/enums";
import type { FetchProtectedBranchesResponseData } from "git/requests/fetchProtectedBranchesRequest.types";
import { gitArtifactActions } from "git/store/gitArtifactSlice"; 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 { useMemo } from "react";
import { useDispatch } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
interface UseGitSettingsParams { interface UseGitSettingsParams {
artifactType: keyof typeof GitArtifactType; artifactType: keyof typeof GitArtifactType;
@ -9,6 +17,13 @@ interface UseGitSettingsParams {
} }
export interface UseGitSettingsReturnValue { export interface UseGitSettingsReturnValue {
autocommitEnabled: boolean;
autocommitPolling: boolean;
protectedBranches: FetchProtectedBranchesResponseData | null;
fetchProtectedBranchesLoading: boolean;
fetchProtectedBranchesError: string | null;
fetchProtectedBranches: () => void;
protectedMode: boolean;
toggleGitSettingsModal: ( toggleGitSettingsModal: (
open: boolean, open: boolean,
tab: keyof typeof GitSettingsTab, tab: keyof typeof GitSettingsTab,
@ -25,6 +40,33 @@ export default function useGitSettings({
[artifactType, baseArtifactId], [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 = ( const toggleGitSettingsModal = (
open: boolean, open: boolean,
tab: keyof typeof GitSettingsTab, tab: keyof typeof GitSettingsTab,
@ -39,6 +81,13 @@ export default function useGitSettings({
}; };
return { return {
autocommitEnabled: autocommitEnabled ?? false,
autocommitPolling: autocommitPolling ?? false,
protectedBranches: protectedBranchesState.value,
fetchProtectedBranchesLoading: protectedBranchesState.loading ?? false,
fetchProtectedBranchesError: protectedBranchesState.error,
fetchProtectedBranches,
protectedMode: protectedMode ?? false,
toggleGitSettingsModal, toggleGitSettingsModal,
}; };
} }

View File

@ -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 { GitArtifactType } from "git/constants/enums";
import type { GitContextValue } from "./hooks/useGitContextValue"; import type { GitContextValue } from "./hooks/useGitContextValue";
import useGitContextValue from "./hooks/useGitContextValue"; import useGitContextValue from "./hooks/useGitContextValue";
@ -15,24 +15,18 @@ interface GitContextProviderProps {
artifactType: keyof typeof GitArtifactType; artifactType: keyof typeof GitArtifactType;
baseArtifactId: string; baseArtifactId: string;
children: React.ReactNode; children: React.ReactNode;
// extra
// connectPermitted?: boolean;
} }
export default function GitContextProvider({ export default function GitContextProvider({
artifactType, artifactType,
baseArtifactId, baseArtifactId,
children, children,
// connectPermitted = true,
}: GitContextProviderProps) { }: GitContextProviderProps) {
const contextValue = useGitContextValue({ artifactType, baseArtifactId }); const contextValue = useGitContextValue({ artifactType, baseArtifactId });
const { fetchBranches } = contextValue;
useEffect(
function gitInitEffect() {
fetchBranches();
},
[fetchBranches],
);
return ( return (
<GitContext.Provider value={contextValue}>{children}</GitContext.Provider> <GitContext.Provider value={contextValue}>{children}</GitContext.Provider>
); );

View File

@ -50,7 +50,7 @@ describe("QuickActionButton", () => {
<QuickActionButton {...defaultProps} /> <QuickActionButton {...defaultProps} />
</ThemeProvider>, </ThemeProvider>,
); );
const btn = container.getElementsByClassName("t--test-btn")[0]; const btn = container.querySelectorAll(".t--test-btn button")[0];
fireEvent.click(btn); fireEvent.click(btn);
expect(defaultProps.onClick).toHaveBeenCalledTimes(1); expect(defaultProps.onClick).toHaveBeenCalledTimes(1);

View File

@ -19,12 +19,11 @@ const SpinnerContainer = styled.div`
padding: 0 10px; padding: 0 10px;
`; `;
const QuickActionButtonContainer = styled.button<{ disabled?: boolean }>` const QuickActionButtonContainer = styled.div<{ disabled?: boolean }>`
margin: 0 ${(props) => props.theme.spaces[1]}px; margin: 0 ${(props) => props.theme.spaces[1]}px;
display: block; display: block;
position: relative; position: relative;
overflow: visible; overflow: visible;
cursor: ${({ disabled = false }) => (disabled ? "not-allowed" : "pointer")};
opacity: ${({ disabled = false }) => (disabled ? 0.6 : 1)}; opacity: ${({ disabled = false }) => (disabled ? 0.6 : 1)};
`; `;
@ -57,11 +56,7 @@ function QuickActionButton({
const content = capitalizeFirstLetter(tooltipText); const content = capitalizeFirstLetter(tooltipText);
return ( return (
<QuickActionButtonContainer <QuickActionButtonContainer className={className} disabled={disabled}>
className={className}
disabled={disabled}
onClick={onClick}
>
{loading ? ( {loading ? (
<SpinnerContainer className="t--loader-quick-git-action"> <SpinnerContainer className="t--loader-quick-git-action">
<SpinnerLoader size="md" /> <SpinnerLoader size="md" />
@ -73,6 +68,7 @@ function QuickActionButton({
isDisabled={disabled} isDisabled={disabled}
isIconButton isIconButton
kind="tertiary" kind="tertiary"
onClick={onClick}
size="md" size="md"
startIcon={icon} startIcon={icon}
/> />

View File

@ -110,8 +110,8 @@ describe("QuickActions Component", () => {
<QuickActions {...props} /> <QuickActions {...props} />
</ThemeProvider>, </ThemeProvider>,
); );
const commitButton = container.getElementsByClassName( const commitButton = container.querySelectorAll(
"t--bottom-bar-commit", ".t--bottom-bar-commit button",
)[0]; )[0];
fireEvent.click(commitButton); fireEvent.click(commitButton);
@ -145,8 +145,9 @@ describe("QuickActions Component", () => {
<QuickActions {...props} /> <QuickActions {...props} />
</ThemeProvider>, </ThemeProvider>,
); );
const pullButton = const pullButton = container.querySelectorAll(
container.getElementsByClassName("t--bottom-bar-pull")[0]; ".t--bottom-bar-pull button",
)[0];
fireEvent.click(pullButton); fireEvent.click(pullButton);
expect(AnalyticsUtil.logEvent).toHaveBeenCalledWith("GS_PULL_GIT_CLICK", { expect(AnalyticsUtil.logEvent).toHaveBeenCalledWith("GS_PULL_GIT_CLICK", {
@ -165,8 +166,8 @@ describe("QuickActions Component", () => {
<QuickActions {...props} /> <QuickActions {...props} />
</ThemeProvider>, </ThemeProvider>,
); );
const mergeButton = container.getElementsByClassName( const mergeButton = container.querySelectorAll(
"t--bottom-bar-merge", ".t--bottom-bar-merge button",
)[0]; )[0];
fireEvent.click(mergeButton); fireEvent.click(mergeButton);
@ -190,8 +191,8 @@ describe("QuickActions Component", () => {
<QuickActions {...props} /> <QuickActions {...props} />
</ThemeProvider>, </ThemeProvider>,
); );
const settingsButton = container.getElementsByClassName( const settingsButton = container.querySelectorAll(
"t--bottom-git-settings", ".t--bottom-git-settings button",
)[0]; )[0];
fireEvent.click(settingsButton); fireEvent.click(settingsButton);
@ -216,8 +217,8 @@ describe("QuickActions Component", () => {
<QuickActions {...props} /> <QuickActions {...props} />
</ThemeProvider>, </ThemeProvider>,
); );
const commitButton = container.getElementsByClassName( const commitButton = container.querySelectorAll(
"t--bottom-bar-commit", ".t--bottom-bar-commit button",
)[0]; )[0];
expect(commitButton).toBeDisabled(); expect(commitButton).toBeDisabled();
@ -298,8 +299,9 @@ describe("QuickActions Component", () => {
<QuickActions {...props} /> <QuickActions {...props} />
</ThemeProvider>, </ThemeProvider>,
); );
const pullButton = const pullButton = container.querySelectorAll(
container.getElementsByClassName("t--bottom-bar-pull")[0]; ".t--bottom-bar-pull button",
)[0];
expect(pullButton).toBeDisabled(); expect(pullButton).toBeDisabled();
}); });

View File

@ -95,6 +95,7 @@ function GitQuickActions({
if (isProtectedMode) { if (isProtectedMode) {
discard(); discard();
} else { } else {
// ! case: why is triggeredFromBottomBar this needed?
// pull({ triggeredFromBottomBar: true }); // pull({ triggeredFromBottomBar: true });
pull(); pull();
} }

View File

@ -1,4 +1,4 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { import type {
CheckoutBranchRequestParams, CheckoutBranchRequestParams,
CheckoutBranchResponse, CheckoutBranchResponse,
@ -9,7 +9,7 @@ import Api from "api/Api";
export default async function checkoutBranchRequest( export default async function checkoutBranchRequest(
branchedApplicationId: string, branchedApplicationId: string,
params: CheckoutBranchRequestParams, params: CheckoutBranchRequestParams,
): Promise<AxiosResponse<CheckoutBranchResponse>> { ): AxiosPromise<CheckoutBranchResponse> {
return Api.get( return Api.get(
`${GIT_BASE_URL}/checkout-branch/app/${branchedApplicationId}`, `${GIT_BASE_URL}/checkout-branch/app/${branchedApplicationId}`,
params, params,

View File

@ -4,12 +4,12 @@ import type {
CommitResponse, CommitResponse,
} from "./commitRequest.types"; } from "./commitRequest.types";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
export default async function commitRequest( export default async function commitRequest(
branchedApplicationId: string, branchedApplicationId: string,
params: CommitRequestParams, params: CommitRequestParams,
): Promise<AxiosResponse<CommitResponse>> { ): AxiosPromise<CommitResponse> {
return Api.post( return Api.post(
`${GIT_BASE_URL}/commit/app/${branchedApplicationId}`, `${GIT_BASE_URL}/commit/app/${branchedApplicationId}`,
params, params,

View File

@ -1,4 +1,4 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { import type {
CreateBranchRequestParams, CreateBranchRequestParams,
CreateBranchResponse, CreateBranchResponse,
@ -9,7 +9,7 @@ import Api from "api/Api";
export default async function createBranchRequest( export default async function createBranchRequest(
branchedApplicationId: string, branchedApplicationId: string,
params: CreateBranchRequestParams, params: CreateBranchRequestParams,
): Promise<AxiosResponse<CreateBranchResponse>> { ): AxiosPromise<CreateBranchResponse> {
return Api.post( return Api.post(
`${GIT_BASE_URL}/create-branch/app/${branchedApplicationId}`, `${GIT_BASE_URL}/create-branch/app/${branchedApplicationId}`,
params, params,

View File

@ -1,4 +1,4 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { import type {
DeleteBranchRequestParams, DeleteBranchRequestParams,
DeleteBranchResponse, DeleteBranchResponse,
@ -9,6 +9,6 @@ import Api from "api/Api";
export default async function deleteBranchRequest( export default async function deleteBranchRequest(
baseApplicationId: string, baseApplicationId: string,
params: DeleteBranchRequestParams, params: DeleteBranchRequestParams,
): Promise<AxiosResponse<DeleteBranchResponse>> { ): AxiosPromise<DeleteBranchResponse> {
return Api.delete(`${GIT_BASE_URL}/branch/app/${baseApplicationId}`, params); return Api.delete(`${GIT_BASE_URL}/branch/app/${baseApplicationId}`, params);
} }

View File

@ -1,9 +1,9 @@
import Api from "api/Api"; import Api from "api/Api";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
export default async function discardRequest( export default async function discardRequest(
branchedApplicationId: string, branchedApplicationId: string,
): Promise<AxiosResponse<void>> { ): AxiosPromise<void> {
return Api.put(`${GIT_BASE_URL}/discard/app/${branchedApplicationId}`); return Api.put(`${GIT_BASE_URL}/discard/app/${branchedApplicationId}`);
} }

View File

@ -1,10 +1,10 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { DisconnectResponse } from "./disconnectRequest.types"; import type { DisconnectResponse } from "./disconnectRequest.types";
import Api from "api/Api"; import Api from "api/Api";
export default async function disconnectRequest( export default async function disconnectRequest(
baseApplicationId: string, baseApplicationId: string,
): Promise<AxiosResponse<DisconnectResponse>> { ): AxiosPromise<DisconnectResponse> {
return Api.post(`${GIT_BASE_URL}/disconnect/app/${baseApplicationId}`); return Api.post(`${GIT_BASE_URL}/disconnect/app/${baseApplicationId}`);
} }

View File

@ -1,11 +1,11 @@
import Api from "api/Api"; import Api from "api/Api";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { FetchAutocommitProgressResponse } from "./fetchAutocommitProgressRequest.types"; import type { FetchAutocommitProgressResponse } from "./fetchAutocommitProgressRequest.types";
export default async function fetchAutocommitProgressRequest( export default async function fetchAutocommitProgressRequest(
baseApplicationId: string, baseApplicationId: string,
): Promise<AxiosResponse<FetchAutocommitProgressResponse>> { ): AxiosPromise<FetchAutocommitProgressResponse> {
return Api.get( return Api.get(
`${GIT_BASE_URL}/auto-commit/progress/app/${baseApplicationId}`, `${GIT_BASE_URL}/auto-commit/progress/app/${baseApplicationId}`,
); );

View File

@ -1,7 +1,11 @@
import type { ApiResponse } from "api/types";
import type { AutocommitStatus } from "../constants/enums"; import type { AutocommitStatus } from "../constants/enums";
export interface FetchAutocommitProgressResponse { export interface FetchAutocommitProgressResponseData {
autoCommitResponse: AutocommitStatus; autoCommitResponse: AutocommitStatus;
progress: number; progress: number;
branchName: string; branchName: string;
} }
export type FetchAutocommitProgressResponse =
ApiResponse<FetchAutocommitProgressResponseData>;

View File

@ -8,7 +8,7 @@ import type { AxiosPromise } from "axios";
export default async function fetchBranchesRequest( export default async function fetchBranchesRequest(
branchedApplicationId: string, branchedApplicationId: string,
params: FetchBranchesRequestParams, params: FetchBranchesRequestParams = { pruneBranches: true },
): AxiosPromise<FetchBranchesResponse> { ): AxiosPromise<FetchBranchesResponse> {
const queryParams = {} as FetchBranchesRequestParams; const queryParams = {} as FetchBranchesRequestParams;

View File

@ -1,7 +1,7 @@
import type { ApiResponse } from "api/ApiResponses"; import type { ApiResponse } from "api/ApiResponses";
export interface FetchBranchesRequestParams { export interface FetchBranchesRequestParams {
pruneBranches: boolean; pruneBranches?: boolean;
} }
interface SingleBranch { interface SingleBranch {

View File

@ -1,10 +1,10 @@
import Api from "api/Api"; import Api from "api/Api";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { FetchGitMetadataResponse } from "./fetchGitMetadataRequest.types"; import type { FetchGitMetadataResponse } from "./fetchGitMetadataRequest.types";
export default async function fetchGitMetadataRequest( export default async function fetchGitMetadataRequest(
baseApplicationId: string, baseApplicationId: string,
): Promise<AxiosResponse<FetchGitMetadataResponse>> { ): AxiosPromise<FetchGitMetadataResponse> {
return Api.get(`${GIT_BASE_URL}/metadata/app/${baseApplicationId}`); return Api.get(`${GIT_BASE_URL}/metadata/app/${baseApplicationId}`);
} }

View File

@ -1,4 +1,6 @@
export interface FetchGitMetadataResponse { import type { ApiResponse } from "api/types";
export interface FetchGitMetadataResponseData {
branchName: string; branchName: string;
defaultBranchName: string; defaultBranchName: string;
remoteUrl: string; remoteUrl: string;
@ -13,3 +15,6 @@ export interface FetchGitMetadataResponse {
}; };
isAutoDeploymentEnabled?: boolean; isAutoDeploymentEnabled?: boolean;
} }
export type FetchGitMetadataResponse =
ApiResponse<FetchGitMetadataResponseData>;

View File

@ -1,4 +1,4 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { import type {
FetchMergeStatusRequestParams, FetchMergeStatusRequestParams,
FetchMergeStatusResponse, FetchMergeStatusResponse,
@ -9,7 +9,7 @@ import { GIT_BASE_URL } from "./constants";
export default async function fetchMergeStatusRequest( export default async function fetchMergeStatusRequest(
branchedApplicationId: string, branchedApplicationId: string,
params: FetchMergeStatusRequestParams, params: FetchMergeStatusRequestParams,
): Promise<AxiosResponse<FetchMergeStatusResponse>> { ): AxiosPromise<FetchMergeStatusResponse> {
return Api.post( return Api.post(
`${GIT_BASE_URL}/merge/status/app/${branchedApplicationId}`, `${GIT_BASE_URL}/merge/status/app/${branchedApplicationId}`,
params, params,

View File

@ -1,10 +1,10 @@
import Api from "api/Api"; import Api from "api/Api";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { FetchProtectedBranches } from "./fetchProtectedBranchesRequest.types"; import type { FetchProtectedBranchesResponse } from "./fetchProtectedBranchesRequest.types";
export default async function fetchProtectedBranchesRequest( export default async function fetchProtectedBranchesRequest(
baseApplicationId: string, baseApplicationId: string,
): Promise<AxiosResponse<FetchProtectedBranches>> { ): AxiosPromise<FetchProtectedBranchesResponse> {
return Api.get(`${GIT_BASE_URL}/branch/app/${baseApplicationId}/protected`); return Api.get(`${GIT_BASE_URL}/branch/app/${baseApplicationId}/protected`);
} }

View File

@ -1 +1,6 @@
export type FetchProtectedBranches = string[]; import type { ApiResponse } from "api/types";
export type FetchProtectedBranchesResponseData = string[];
export type FetchProtectedBranchesResponse =
ApiResponse<FetchProtectedBranchesResponseData>;

View File

@ -1,10 +1,10 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { FetchSSHKeyResponse } from "./fetchSSHKeyRequest.types"; import type { FetchSSHKeyResponse } from "./fetchSSHKeyRequest.types";
import Api from "api/Api"; import Api from "api/Api";
import { APPLICATION_BASE_URL } from "./constants"; import { APPLICATION_BASE_URL } from "./constants";
export default async function fetchSSHKeyRequest( export default async function fetchSSHKeyRequest(
baseApplicationId: string, baseApplicationId: string,
): Promise<AxiosResponse<FetchSSHKeyResponse>> { ): AxiosPromise<FetchSSHKeyResponse> {
return Api.get(`${APPLICATION_BASE_URL}/ssh-keypair/${baseApplicationId}`); return Api.get(`${APPLICATION_BASE_URL}/ssh-keypair/${baseApplicationId}`);
} }

View File

@ -4,11 +4,11 @@ import type {
FetchStatusResponse, FetchStatusResponse,
} from "./fetchStatusRequest.types"; } from "./fetchStatusRequest.types";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
export default async function fetchStatusRequest( export default async function fetchStatusRequest(
branchedApplicationId: string, branchedApplicationId: string,
params: FetchStatusRequestParams, params: FetchStatusRequestParams = { compareRemote: true },
): Promise<AxiosResponse<FetchStatusResponse>> { ): AxiosPromise<FetchStatusResponse> {
return Api.get(`${GIT_BASE_URL}/status/app/${branchedApplicationId}`, params); return Api.get(`${GIT_BASE_URL}/status/app/${branchedApplicationId}`, params);
} }

View File

@ -1,7 +1,7 @@
import type { ApiResponse } from "api/types"; import type { ApiResponse } from "api/types";
export interface FetchStatusRequestParams { export interface FetchStatusRequestParams {
compareRemote: boolean; compareRemote?: boolean;
} }
export interface FetchStatusResponseData { export interface FetchStatusResponseData {
added: string[]; added: string[];

View File

@ -1,4 +1,4 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { import type {
GenerateSSHKeyRequestParams, GenerateSSHKeyRequestParams,
GenerateSSHKeyResponse, GenerateSSHKeyResponse,
@ -9,7 +9,7 @@ import Api from "api/Api";
export default async function generateSSHKeyRequest( export default async function generateSSHKeyRequest(
baseApplicationId: string, baseApplicationId: string,
params: GenerateSSHKeyRequestParams, params: GenerateSSHKeyRequestParams,
): Promise<AxiosResponse<GenerateSSHKeyResponse>> { ): AxiosPromise<GenerateSSHKeyResponse> {
const url = params.isImporting const url = params.isImporting
? `${GIT_BASE_URL}/import/keys?keyType=${params.keyType}` ? `${GIT_BASE_URL}/import/keys?keyType=${params.keyType}`
: `${APPLICATION_BASE_URL}/ssh-keypair/${baseApplicationId}?keyType=${params.keyType}`; : `${APPLICATION_BASE_URL}/ssh-keypair/${baseApplicationId}?keyType=${params.keyType}`;

View File

@ -4,11 +4,11 @@ import type {
ImportGitRequestParams, ImportGitRequestParams,
ImportGitResponse, ImportGitResponse,
} from "./importGitRequest.types"; } from "./importGitRequest.types";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
export default async function importGitRequest( export default async function importGitRequest(
workspaceId: string, workspaceId: string,
params: ImportGitRequestParams, params: ImportGitRequestParams,
): Promise<AxiosResponse<ImportGitResponse>> { ): AxiosPromise<ImportGitResponse> {
return Api.post(`${GIT_BASE_URL}/import/${workspaceId}`, params); return Api.post(`${GIT_BASE_URL}/import/${workspaceId}`, params);
} }

View File

@ -1,11 +1,11 @@
import Api from "api/Api"; import Api from "api/Api";
import type { MergeRequestParams, MergeResponse } from "./mergeRequest.types"; import type { MergeRequestParams, MergeResponse } from "./mergeRequest.types";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
export default async function mergeRequest( export default async function mergeRequest(
branchedApplicationId: string, branchedApplicationId: string,
params: MergeRequestParams, params: MergeRequestParams,
): Promise<AxiosResponse<MergeResponse>> { ): AxiosPromise<MergeResponse> {
return Api.post(`${GIT_BASE_URL}/merge/app/${branchedApplicationId}`, params); return Api.post(`${GIT_BASE_URL}/merge/app/${branchedApplicationId}`, params);
} }

View File

@ -1,10 +1,10 @@
import Api from "api/Api"; import Api from "api/Api";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { PullRequestResponse } from "./pullRequest.types"; import type { PullRequestResponse } from "./pullRequest.types";
export default async function pullRequest( export default async function pullRequest(
branchedApplicationId: string, branchedApplicationId: string,
): Promise<AxiosResponse<PullRequestResponse>> { ): AxiosPromise<PullRequestResponse> {
return Api.get(`${GIT_BASE_URL}/pull/app/${branchedApplicationId}`); return Api.get(`${GIT_BASE_URL}/pull/app/${branchedApplicationId}`);
} }

View File

@ -1,11 +1,11 @@
import Api from "api/Api"; import Api from "api/Api";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { ToggleAutocommitResponse } from "./toggleAutocommitRequest.types"; import type { ToggleAutocommitResponse } from "./toggleAutocommitRequest.types";
export default async function toggleAutocommitRequest( export default async function toggleAutocommitRequest(
baseApplicationId: string, baseApplicationId: string,
): Promise<AxiosResponse<ToggleAutocommitResponse>> { ): AxiosPromise<ToggleAutocommitResponse> {
return Api.patch( return Api.patch(
`${GIT_BASE_URL}/auto-commit/toggle/app/${baseApplicationId}`, `${GIT_BASE_URL}/auto-commit/toggle/app/${baseApplicationId}`,
); );

View File

@ -1,10 +1,10 @@
import Api from "api/Api"; import Api from "api/Api";
import { GIT_BASE_URL } from "./constants"; import { GIT_BASE_URL } from "./constants";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { TriggerAutocommitResponse } from "./triggerAutocommitRequest.types"; import type { TriggerAutocommitResponse } from "./triggerAutocommitRequest.types";
export default async function triggerAutocommitRequest( export default async function triggerAutocommitRequest(
branchedApplicationId: string, branchedApplicationId: string,
): Promise<AxiosResponse<TriggerAutocommitResponse>> { ): AxiosPromise<TriggerAutocommitResponse> {
return Api.post(`${GIT_BASE_URL}/auto-commit/app/${branchedApplicationId}`); return Api.post(`${GIT_BASE_URL}/auto-commit/app/${branchedApplicationId}`);
} }

View File

@ -1,7 +1,11 @@
import type { ApiResponse } from "api/types";
import type { AutocommitStatus } from "../constants/enums"; import type { AutocommitStatus } from "../constants/enums";
export interface TriggerAutocommitResponse { export interface TriggerAutocommitResponseData {
autoCommitResponse: AutocommitStatus; autoCommitResponse: AutocommitStatus;
progress: number; progress: number;
branchName: string; branchName: string;
} }
export type TriggerAutocommitResponse =
ApiResponse<TriggerAutocommitResponseData>;

View File

@ -1,4 +1,4 @@
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
import type { import type {
UpdateGlobalProfileRequestParams, UpdateGlobalProfileRequestParams,
UpdateGlobalProfileResponse, UpdateGlobalProfileResponse,
@ -8,6 +8,6 @@ import { GIT_BASE_URL } from "./constants";
export default async function updateGlobalProfileRequest( export default async function updateGlobalProfileRequest(
params: UpdateGlobalProfileRequestParams, params: UpdateGlobalProfileRequestParams,
): Promise<AxiosResponse<UpdateGlobalProfileResponse>> { ): AxiosPromise<UpdateGlobalProfileResponse> {
return Api.post(`${GIT_BASE_URL}/profile/default`, params); return Api.post(`${GIT_BASE_URL}/profile/default`, params);
} }

View File

@ -4,12 +4,12 @@ import type {
UpdateProtectedBranchesRequestParams, UpdateProtectedBranchesRequestParams,
UpdateProtectedBranchesResponse, UpdateProtectedBranchesResponse,
} from "./updateProtectedBranchesRequest.types"; } from "./updateProtectedBranchesRequest.types";
import type { AxiosResponse } from "axios"; import type { AxiosPromise } from "axios";
export default async function updateProtectedBranchesRequest( export default async function updateProtectedBranchesRequest(
baseApplicationId: string, baseApplicationId: string,
params: UpdateProtectedBranchesRequestParams, params: UpdateProtectedBranchesRequestParams,
): Promise<AxiosResponse<UpdateProtectedBranchesResponse>> { ): AxiosPromise<UpdateProtectedBranchesResponse> {
return Api.post( return Api.post(
`${GIT_BASE_URL}/branch/app/${baseApplicationId}/protected`, `${GIT_BASE_URL}/branch/app/${baseApplicationId}/protected`,
params, params,

View File

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

View File

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

View File

@ -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<FetchStatusInitPayload>,
) {
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));
// }
}
}

View File

@ -1,37 +1,99 @@
import { gitArtifactActions } from "git/store/gitArtifactSlice"; import {
import { all, takeLatest } from "redux-saga/effects"; 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 connectSaga from "./connectSaga";
import commitSaga from "./commitSaga"; import commitSaga from "./commitSaga";
import { gitConfigActions } from "git/store/gitConfigSlice";
import fetchGlobalProfileSaga from "./fetchGlobalProfileSaga"; import fetchGlobalProfileSaga from "./fetchGlobalProfileSaga";
import fetchBranchesSaga from "./fetchBranchesSaga"; import fetchBranchesSaga from "./fetchBranchesSaga";
import fetchLocalProfileSaga from "./fetchLocalProfileSaga"; import fetchLocalProfileSaga from "./fetchLocalProfileSaga";
import updateLocalProfileSaga from "./updateLocalProfileSaga"; import updateLocalProfileSaga from "./updateLocalProfileSaga";
import updateGlobalProfileSaga from "./updateGlobalProfileSaga"; 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() { const gitRequestBlockingActions: Record<
yield all([ string,
takeLatest(gitArtifactActions.connectInit.type, connectSaga), // eslint-disable-next-line @typescript-eslint/no-explicit-any
(action: PayloadAction<any>) => Generator<any>
> = {
// init
[gitArtifactActions.fetchGitMetadataInit.type]: fetchGitMetadataSaga,
// connect
[gitArtifactActions.connectInit.type]: connectSaga,
// ops
[gitArtifactActions.commitInit.type]: commitSaga,
[gitArtifactActions.fetchStatusInit.type]: fetchStatusSaga,
// branches // branches
takeLatest(gitArtifactActions.fetchBranchesInit.type, fetchBranchesSaga), [gitArtifactActions.fetchBranchesInit.type]: fetchBranchesSaga,
takeLatest(gitArtifactActions.commitInit.type, commitSaga), // settings
takeLatest( [gitArtifactActions.fetchLocalProfileInit.type]: fetchLocalProfileSaga,
gitArtifactActions.fetchLocalProfileInit.type, [gitArtifactActions.updateLocalProfileInit.type]: updateLocalProfileSaga,
fetchLocalProfileSaga, [gitConfigActions.fetchGlobalProfileInit.type]: fetchGlobalProfileSaga,
), [gitConfigActions.updateGlobalProfileInit.type]: updateGlobalProfileSaga,
takeLatest(
gitArtifactActions.updateLocalProfileInit.type, // autocommit
updateLocalProfileSaga, [gitArtifactActions.triggerAutocommitInit.type]: triggerAutocommitSaga,
), };
takeLatest(
gitConfigActions.fetchGlobalProfileInit.type, const gitRequestNonBlockingActions: Record<
fetchGlobalProfileSaga, string,
), // eslint-disable-next-line @typescript-eslint/no-explicit-any
takeLatest( (action: PayloadAction<any>) => Generator<any>
gitConfigActions.updateGlobalProfileInit.type, > = {
updateGlobalProfileSaga, // 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<unknown> = yield actionChannel(
objectKeys(gitRequestBlockingActions),
);
while (true) {
const action: PayloadAction<unknown> = 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);
} }

View File

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

View File

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

View File

@ -1,8 +1,4 @@
import type { import type { GitAsyncErrorPayload } from "../types";
GitArtifactPayloadAction,
GitArtifactErrorPayloadAction,
GitAutocommitProgress,
} from "../types";
import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction";
export const fetchAutocommitProgressInitAction = createSingleArtifactAction( export const fetchAutocommitProgressInitAction = createSingleArtifactAction(
@ -15,27 +11,19 @@ export const fetchAutocommitProgressInitAction = createSingleArtifactAction(
); );
export const fetchAutocommitProgressSuccessAction = createSingleArtifactAction( export const fetchAutocommitProgressSuccessAction = createSingleArtifactAction(
( (state) => {
state,
action: GitArtifactPayloadAction<{
autocommitProgress: GitAutocommitProgress;
}>,
) => {
state.apiResponses.autocommitProgress.loading = false; state.apiResponses.autocommitProgress.loading = false;
state.apiResponses.autocommitProgress.value =
action.payload.autocommitProgress;
return state; return state;
}, },
); );
export const fetchAutocommitProgressErrorAction = createSingleArtifactAction( export const fetchAutocommitProgressErrorAction =
(state, action: GitArtifactErrorPayloadAction) => { createSingleArtifactAction<GitAsyncErrorPayload>((state, action) => {
const { error } = action.payload; const { error } = action.payload;
state.apiResponses.autocommitProgress.loading = false; state.apiResponses.autocommitProgress.loading = false;
state.apiResponses.autocommitProgress.error = error; state.apiResponses.autocommitProgress.error = error;
return state; return state;
}, });
);

View File

@ -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<FetchGitMetadataResponseData>
>((state, action) => {
state.apiResponses.metadata.loading = false;
state.apiResponses.metadata.value = action.payload.responseData;
return state;
});
export const fetchGitMetadataErrorAction =
createSingleArtifactAction<GitAsyncErrorPayload>((state, action) => {
const { error } = action.payload;
state.apiResponses.metadata.loading = false;
state.apiResponses.metadata.error = error;
return state;
});

View File

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

View File

@ -1,9 +1,6 @@
import type { import type { GitAsyncSuccessPayload, GitAsyncErrorPayload } from "../types";
GitArtifactPayloadAction,
GitArtifactErrorPayloadAction,
GitProtectedBranches,
} from "../types";
import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction";
import type { FetchProtectedBranchesResponseData } from "git/requests/fetchProtectedBranchesRequest.types";
export const fetchProtectedBranchesInitAction = createSingleArtifactAction( export const fetchProtectedBranchesInitAction = createSingleArtifactAction(
(state) => { (state) => {
@ -14,28 +11,21 @@ export const fetchProtectedBranchesInitAction = createSingleArtifactAction(
}, },
); );
export const fetchProtectedBranchesSuccessAction = createSingleArtifactAction( export const fetchProtectedBranchesSuccessAction = createSingleArtifactAction<
( GitAsyncSuccessPayload<FetchProtectedBranchesResponseData>
state, >((state, action) => {
action: GitArtifactPayloadAction<{
protectedBranches: GitProtectedBranches;
}>,
) => {
state.apiResponses.protectedBranches.loading = false; state.apiResponses.protectedBranches.loading = false;
state.apiResponses.protectedBranches.value = state.apiResponses.protectedBranches.value = action.payload.responseData;
action.payload.protectedBranches;
return state; return state;
}, });
);
export const fetchProtectedBranchesErrorAction = createSingleArtifactAction( export const fetchProtectedBranchesErrorAction =
(state, action: GitArtifactErrorPayloadAction) => { createSingleArtifactAction<GitAsyncErrorPayload>((state, action) => {
const { error } = action.payload; const { error } = action.payload;
state.apiResponses.protectedBranches.loading = false; state.apiResponses.protectedBranches.loading = false;
state.apiResponses.protectedBranches.error = error; state.apiResponses.protectedBranches.error = error;
return state; return state;
}, });
);

View File

@ -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<FetchGitMetadataResponseData>;
};
}
export const initGitForEditorAction =
createSingleArtifactAction<InitGitForEditorPayload>((state) => {
return state;
});

View File

@ -1,14 +1,17 @@
import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction"; import { createSingleArtifactAction } from "../helpers/createSingleArtifactAction";
import type { GitArtifactErrorPayloadAction } from "../types"; import type { GitAsyncErrorPayload } from "../types";
export const triggerAutocommitInitAction = createSingleArtifactAction( export interface TriggerAutocommitInitPayload {
(state) => { artifactId: string;
}
export const triggerAutocommitInitAction =
createSingleArtifactAction<TriggerAutocommitInitPayload>((state) => {
state.apiResponses.triggerAutocommit.loading = true; state.apiResponses.triggerAutocommit.loading = true;
state.apiResponses.triggerAutocommit.error = null; state.apiResponses.triggerAutocommit.error = null;
return state; return state;
}, });
);
export const triggerAutocommitSuccessAction = createSingleArtifactAction( export const triggerAutocommitSuccessAction = createSingleArtifactAction(
(state) => { (state) => {
@ -18,13 +21,28 @@ export const triggerAutocommitSuccessAction = createSingleArtifactAction(
}, },
); );
export const triggerAutocommitErrorAction = createSingleArtifactAction( export const triggerAutocommitErrorAction =
(state, action: GitArtifactErrorPayloadAction) => { createSingleArtifactAction<GitAsyncErrorPayload>((state, action) => {
const { error } = action.payload; const { error } = action.payload;
state.apiResponses.triggerAutocommit.loading = false; state.apiResponses.triggerAutocommit.loading = false;
state.apiResponses.triggerAutocommit.error = error; 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; return state;
}, },
); );

View File

@ -8,10 +8,10 @@ import {
connectSuccessAction, connectSuccessAction,
} from "./actions/connectActions"; } from "./actions/connectActions";
import { import {
fetchMetadataErrorAction, fetchGitMetadataErrorAction,
fetchMetadataInitAction, fetchGitMetadataInitAction,
fetchMetadataSuccessAction, fetchGitMetadataSuccessAction,
} from "./actions/fetchMetadataActions"; } from "./actions/fetchGitMetadataActions";
import { import {
fetchBranchesErrorAction, fetchBranchesErrorAction,
fetchBranchesInitAction, fetchBranchesInitAction,
@ -79,6 +79,34 @@ import {
mergeInitAction, mergeInitAction,
mergeSuccessAction, mergeSuccessAction,
} from "./actions/mergeActions"; } 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 = {}; const initialState: GitArtifactReduxState = {};
@ -87,8 +115,13 @@ export const gitArtifactSlice = createSlice({
reducerPath: "git.artifact", reducerPath: "git.artifact",
initialState, initialState,
reducers: { reducers: {
// init
initGitForEditor: initGitForEditorAction,
mount: mountAction, mount: mountAction,
unmount: unmountAction, unmount: unmountAction,
fetchGitMetadataInit: fetchGitMetadataInitAction,
fetchGitMetadataSuccess: fetchGitMetadataSuccessAction,
fetchGitMetadataError: fetchGitMetadataErrorAction,
// connect // connect
connectInit: connectInitAction, connectInit: connectInitAction,
@ -135,17 +168,31 @@ export const gitArtifactSlice = createSlice({
// settings // settings
toggleGitSettingsModal: toggleGitSettingsModalAction, toggleGitSettingsModal: toggleGitSettingsModalAction,
// metadata
fetchMetadataInit: fetchMetadataInitAction,
fetchMetadataSuccess: fetchMetadataSuccessAction,
fetchMetadataError: fetchMetadataErrorAction,
fetchLocalProfileInit: fetchLocalProfileInitAction, fetchLocalProfileInit: fetchLocalProfileInitAction,
fetchLocalProfileSuccess: fetchLocalProfileSuccessAction, fetchLocalProfileSuccess: fetchLocalProfileSuccessAction,
fetchLocalProfileError: fetchLocalProfileErrorAction, fetchLocalProfileError: fetchLocalProfileErrorAction,
updateLocalProfileInit: updateLocalProfileInitAction, updateLocalProfileInit: updateLocalProfileInitAction,
updateLocalProfileSuccess: updateLocalProfileSuccessAction, updateLocalProfileSuccess: updateLocalProfileSuccessAction,
updateLocalProfileError: updateLocalProfileErrorAction, 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,
}, },
}); });

View File

@ -33,6 +33,8 @@ const gitSingleArtifactInitialUIState: GitSingleArtifactUIReduxState = {
repoLimitErrorModal: { repoLimitErrorModal: {
open: false, open: false,
}, },
autocommitModalOpen: false,
autocommitPolling: false,
}; };
const gitSingleArtifactInitialAPIResponses: GitSingleArtifactAPIResponsesReduxState = const gitSingleArtifactInitialAPIResponses: GitSingleArtifactAPIResponsesReduxState =
@ -112,7 +114,6 @@ const gitSingleArtifactInitialAPIResponses: GitSingleArtifactAPIResponsesReduxSt
error: null, error: null,
}, },
autocommitProgress: { autocommitProgress: {
value: null,
loading: false, loading: false,
error: null, error: null,
}, },

View File

@ -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 // git ops
export const selectCommit = ( export const selectCommit = (
state: GitRootState, state: GitRootState,
@ -43,6 +54,16 @@ export const selectPull = (state: GitRootState, artifactDef: GitArtifactDef) =>
selectSingleArtifact(state, artifactDef)?.apiResponses?.pull; selectSingleArtifact(state, artifactDef)?.apiResponses?.pull;
// git branches // git branches
export const selectCurrentBranch = (
state: GitRootState,
artifactDef: GitArtifactDef,
) => {
const gitMetadataState = selectGitMetadata(state, artifactDef).value;
return gitMetadataState?.branchName;
};
export const selectBranches = ( export const selectBranches = (
state: GitRootState, state: GitRootState,
artifactDef: GitArtifactDef, artifactDef: GitArtifactDef,
@ -62,3 +83,34 @@ export const selectCheckoutBranch = (
state: GitRootState, state: GitRootState,
artifactDef: GitArtifactDef, artifactDef: GitArtifactDef,
) => selectSingleArtifact(state, artifactDef)?.apiResponses.checkoutBranch; ) => 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 ?? "");
};

View File

@ -11,13 +11,8 @@ import type { FetchBranchesResponseData } from "../requests/fetchBranchesRequest
import type { FetchLocalProfileResponseData } from "../requests/fetchLocalProfileRequest.types"; import type { FetchLocalProfileResponseData } from "../requests/fetchLocalProfileRequest.types";
import type { FetchStatusResponseData } from "git/requests/fetchStatusRequest.types"; import type { FetchStatusResponseData } from "git/requests/fetchStatusRequest.types";
import type { FetchMergeStatusResponseData } from "git/requests/fetchMergeStatusRequest.types"; import type { FetchMergeStatusResponseData } from "git/requests/fetchMergeStatusRequest.types";
import type { FetchGitMetadataResponseData } from "git/requests/fetchGitMetadataRequest.types";
// These will be updated when contracts are finalized import type { FetchProtectedBranchesResponseData } from "git/requests/fetchProtectedBranchesRequest.types";
export type GitMetadata = Record<string, unknown>;
export type GitProtectedBranches = Record<string, unknown>;
export type GitAutocommitProgress = Record<string, unknown>;
export type GitSSHKey = Record<string, unknown>; export type GitSSHKey = Record<string, unknown>;
@ -32,7 +27,7 @@ interface AsyncStateWithoutValue {
error: string | null; error: string | null;
} }
export interface GitSingleArtifactAPIResponsesReduxState { export interface GitSingleArtifactAPIResponsesReduxState {
metadata: AsyncState<GitMetadata>; metadata: AsyncState<FetchGitMetadataResponseData>;
connect: AsyncStateWithoutValue; connect: AsyncStateWithoutValue;
status: AsyncState<FetchStatusResponseData>; status: AsyncState<FetchStatusResponseData>;
commit: AsyncStateWithoutValue; commit: AsyncStateWithoutValue;
@ -47,9 +42,9 @@ export interface GitSingleArtifactAPIResponsesReduxState {
localProfile: AsyncState<FetchLocalProfileResponseData>; localProfile: AsyncState<FetchLocalProfileResponseData>;
updateLocalProfile: AsyncStateWithoutValue; updateLocalProfile: AsyncStateWithoutValue;
disconnect: AsyncStateWithoutValue; disconnect: AsyncStateWithoutValue;
protectedBranches: AsyncState<GitProtectedBranches>; protectedBranches: AsyncState<FetchProtectedBranchesResponseData>;
updateProtectedBranches: AsyncStateWithoutValue; updateProtectedBranches: AsyncStateWithoutValue;
autocommitProgress: AsyncState<GitAutocommitProgress>; autocommitProgress: AsyncStateWithoutValue;
toggleAutocommit: AsyncStateWithoutValue; toggleAutocommit: AsyncStateWithoutValue;
triggerAutocommit: AsyncStateWithoutValue; triggerAutocommit: AsyncStateWithoutValue;
sshKey: AsyncState<GitSSHKey>; sshKey: AsyncState<GitSSHKey>;
@ -79,6 +74,8 @@ export interface GitSingleArtifactUIReduxState {
repoLimitErrorModal: { repoLimitErrorModal: {
open: boolean; open: boolean;
}; };
autocommitPolling: boolean;
autocommitModalOpen: boolean;
} }
export interface GitSingleArtifactReduxState { export interface GitSingleArtifactReduxState {
ui: GitSingleArtifactUIReduxState; ui: GitSingleArtifactUIReduxState;