feat: branch protection (#28526)
- Adds server endpoints for getting and setting protected branches - Adds protected canvas view for branch protection - Adds default branch and protected branch in git modal settings Fixes #28434, #28056 Protected View - <img width="1728" alt="image" src="https://github.com/appsmithorg/appsmith/assets/8724051/4fb26450-61e1-4fc0-a66d-0ebaa28ff90c"> Branch Protection Settings - <img width="1728" alt="image" src="https://github.com/appsmithorg/appsmith/assets/8724051/fb6d16b6-0a3c-42fd-be1a-9b3677048663"> - New feature (non-breaking change which adds functionality) > > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > > Add Testsmith test cases links that relate to this PR > > > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: Nayan <nayan@appsmith.com>
This commit is contained in:
parent
5aa5318727
commit
d24a0bf4b9
|
|
@ -474,6 +474,25 @@ export const fetchGitProtectedBranchesInit = () => {
|
|||
};
|
||||
};
|
||||
|
||||
export const fetchGitProtectedBranchesSuccess = (
|
||||
protectedBranches: string[],
|
||||
) => {
|
||||
return {
|
||||
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_SUCCESS,
|
||||
payload: { protectedBranches },
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchGitProtectedBranchesError = (
|
||||
error: any,
|
||||
show: boolean = true,
|
||||
) => {
|
||||
return {
|
||||
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_ERROR,
|
||||
payload: { error, show },
|
||||
};
|
||||
};
|
||||
|
||||
export const updateGitProtectedBranchesInit = (payload: {
|
||||
protectedBranches: string[];
|
||||
}) => {
|
||||
|
|
@ -482,3 +501,19 @@ export const updateGitProtectedBranchesInit = (payload: {
|
|||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateGitProtectedBranchesSuccess = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_SUCCESS,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateGitProtectedBranchesError = (
|
||||
error: any,
|
||||
show: boolean = true,
|
||||
) => {
|
||||
return {
|
||||
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_ERROR,
|
||||
payload: { error, show },
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -508,7 +508,6 @@ export const PAGE_SERVER_UNAVAILABLE_ERROR_MESSAGES = (
|
|||
export const POST = () => "Post";
|
||||
export const CANCEL = () => "Cancel";
|
||||
export const REMOVE = () => "Remove";
|
||||
export const CREATE = () => "Create";
|
||||
|
||||
// Showcase Carousel
|
||||
export const NEXT = () => "NEXT";
|
||||
|
|
@ -1038,7 +1037,7 @@ export const PREVIOUS_STEP = () => "Previous step";
|
|||
export const GIT_CONNECT_SUCCESS_TITLE = () =>
|
||||
"Successfully connected to your Git remote repository";
|
||||
export const GIT_CONNECT_SUCCESS_MESSAGE = () =>
|
||||
"Now you can start collaborating with your team members by committing, merging and deploying your app";
|
||||
"Right now, {branch} is set as the default branch and it is protected.";
|
||||
export const START_USING_GIT = () => "Start using Git";
|
||||
export const OPEN_GIT_SETTINGS = () => "Open Git settings";
|
||||
export const GIT_AUTHOR = () => "Git author";
|
||||
|
|
@ -1087,8 +1086,6 @@ export const GO_TO_SETTINGS = () => "Go to settings";
|
|||
export const NOW_PROTECT_BRANCH = () =>
|
||||
"You can now protect your default branch.";
|
||||
export const APPSMITH_ENTERPRISE = () => "Appsmith Enterprise";
|
||||
export const PROTECT_BRANCH_SUCCESS = () => "Changed protected branches";
|
||||
export const UPDATE_DEFAULT_BRANCH_SUCCESS = () => "Updated default branch";
|
||||
// Git Branch Protection end
|
||||
|
||||
export const NAV_DESCRIPTION = () =>
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ export const FEATURE_FLAG = {
|
|||
release_app_sidebar_enabled: "release_app_sidebar_enabled",
|
||||
release_git_branch_protection_enabled:
|
||||
"release_git_branch_protection_enabled",
|
||||
license_git_branch_protection_enabled:
|
||||
"license_git_branch_protection_enabled",
|
||||
license_widget_rtl_support_enabled: "license_widget_rtl_support_enabled",
|
||||
} as const;
|
||||
|
||||
|
|
@ -62,7 +60,6 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
|
|||
ab_show_templates_instead_of_blank_canvas_enabled: false,
|
||||
release_app_sidebar_enabled: false,
|
||||
release_git_branch_protection_enabled: false,
|
||||
license_git_branch_protection_enabled: false,
|
||||
license_widget_rtl_support_enabled: false,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ export function EditorHeader() {
|
|||
);
|
||||
const isPreviewingApp =
|
||||
isPreviewMode || isAppSettingsPaneWithNavigationTabOpen;
|
||||
const isProtectedMode = useSelector(protectedModeSelector);
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) {
|
|||
const selectedTheme = useSelector(getSelectedAppTheme);
|
||||
const params = useParams<{ applicationId: string; pageId: string }>();
|
||||
const shouldHaveTopMargin =
|
||||
!(isPreviewMode || isProtectedMode) ||
|
||||
(!isPreviewMode && !isProtectedMode) ||
|
||||
!isAppSettingsPaneWithNavigationTabOpen ||
|
||||
pages.length > 1;
|
||||
const isAppThemeChanging = useSelector(getAppThemeIsChanging);
|
||||
|
|
@ -185,7 +185,7 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) {
|
|||
}
|
||||
|
||||
const isPreviewingNavigation =
|
||||
isPreviewMode || isProtectedMode || isAppSettingsPaneWithNavigationTabOpen;
|
||||
isPreviewMode || isAppSettingsPaneWithNavigationTabOpen;
|
||||
|
||||
/**
|
||||
* calculating exact height to not allow scroll at this component,
|
||||
|
|
@ -231,6 +231,7 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) {
|
|||
shouldHaveTopMargin &&
|
||||
!showCanvasTopSection &&
|
||||
!isPreviewingNavigation &&
|
||||
!isProtectedMode &&
|
||||
!showAnonymousDataPopup,
|
||||
"mt-24": shouldShowSnapShotBanner,
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ const getQuickActionButtons = ({
|
|||
{
|
||||
className: "t--bottom-bar-commit",
|
||||
disabled: isProtectedMode,
|
||||
count: isProtectedMode ? undefined : changesToCommit,
|
||||
count: changesToCommit,
|
||||
icon: "plus",
|
||||
loading: isFetchingGitStatus,
|
||||
onClick: commit,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
BRANCH_PROTECTION_RULE_1,
|
||||
BRANCH_PROTECTION_RULE_2,
|
||||
BRANCH_PROTECTION_RULE_3,
|
||||
GIT_CONNECT_SUCCESS_MESSAGE,
|
||||
GIT_CONNECT_SUCCESS_TITLE,
|
||||
OPEN_GIT_SETTINGS,
|
||||
START_USING_GIT,
|
||||
|
|
@ -21,10 +20,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import styled from "styled-components";
|
||||
import { getCurrentAppGitMetaData } from "@appsmith/selectors/applicationSelectors";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import {
|
||||
getDefaultGitBranchName,
|
||||
getIsGitProtectedFeatureEnabled,
|
||||
} from "selectors/gitSyncSelectors";
|
||||
import { getDefaultGitBranchName } from "selectors/gitSyncSelectors";
|
||||
|
||||
const Container = styled.div``;
|
||||
|
||||
|
|
@ -57,18 +53,6 @@ const FeatureIcon = styled(Icon)`
|
|||
margin-right: 4px;
|
||||
`;
|
||||
|
||||
const BranchTag = styled(Tag)`
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
const DefaultBranchMessage = styled(Text)`
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
const ProtectionRulesTitle = styled(Text)`
|
||||
margin-bottom: 8px;
|
||||
`;
|
||||
|
||||
const features = [
|
||||
createMessage(BRANCH_PROTECTION_RULE_1),
|
||||
createMessage(BRANCH_PROTECTION_RULE_2),
|
||||
|
|
@ -78,9 +62,6 @@ const features = [
|
|||
function ConnectionSuccess() {
|
||||
const gitMetadata = useSelector(getCurrentAppGitMetaData);
|
||||
const defaultBranchName = useSelector(getDefaultGitBranchName);
|
||||
const isGitProtectedFeatureEnabled = useSelector(
|
||||
getIsGitProtectedFeatureEnabled,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -110,55 +91,42 @@ function ConnectionSuccess() {
|
|||
});
|
||||
};
|
||||
|
||||
const preBranchProtectionContent = () => {
|
||||
return (
|
||||
<Text renderAs="p">{createMessage(GIT_CONNECT_SUCCESS_MESSAGE)}</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const postBranchProtectionContent = () => {
|
||||
return (
|
||||
<>
|
||||
<DefaultBranchMessage renderAs="p">
|
||||
Right now,{" "}
|
||||
<BranchTag isClosable={false}>{defaultBranchName}</BranchTag> is set
|
||||
as the default branch and it is protected.
|
||||
</DefaultBranchMessage>
|
||||
<ProtectionRulesTitle renderAs="p">
|
||||
{createMessage(BRANCH_PROTECTION_RULES_AS_FOLLOWS)}
|
||||
</ProtectionRulesTitle>
|
||||
<FeatureList>
|
||||
{features.map((feature) => (
|
||||
<FeatureItem key={feature}>
|
||||
<FeatureIcon
|
||||
color="var(--ads-v2-color-blue-600)"
|
||||
name="oval-check"
|
||||
size="md"
|
||||
/>
|
||||
<Text>{feature}</Text>
|
||||
</FeatureItem>
|
||||
))}
|
||||
</FeatureList>
|
||||
<Text>{createMessage(BRANCH_PROTECTION_CHANGE_RULE)}</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const preBranchProtectionActions = () => {
|
||||
return (
|
||||
<Button
|
||||
data-testid="t--start-using-git-button"
|
||||
onClick={handleStartGit}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(START_USING_GIT)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const postBranchProtectionActions = () => {
|
||||
return (
|
||||
<>
|
||||
return (
|
||||
<>
|
||||
<ModalBody>
|
||||
<Container>
|
||||
<TitleContainer>
|
||||
<StyledIcon color="#059669" name="oval-check" size="lg" />
|
||||
<TitleText kind="heading-s" renderAs="h3">
|
||||
{createMessage(GIT_CONNECT_SUCCESS_TITLE)}
|
||||
</TitleText>
|
||||
</TitleContainer>
|
||||
<Text renderAs="p" style={{ marginBottom: 16 }}>
|
||||
Right now,{" "}
|
||||
<Tag isClosable={false} style={{ display: "inline-flex" }}>
|
||||
{defaultBranchName}
|
||||
</Tag>{" "}
|
||||
is set as the default branch and it is protected.
|
||||
</Text>
|
||||
<Text renderAs="p" style={{ marginBottom: 8 }}>
|
||||
{createMessage(BRANCH_PROTECTION_RULES_AS_FOLLOWS)}
|
||||
</Text>
|
||||
<FeatureList>
|
||||
{features.map((feature) => (
|
||||
<FeatureItem key={feature}>
|
||||
<FeatureIcon
|
||||
color="var(--ads-v2-color-blue-600)"
|
||||
name="oval-check"
|
||||
size="md"
|
||||
/>
|
||||
<Text>{feature}</Text>
|
||||
</FeatureItem>
|
||||
))}
|
||||
</FeatureList>
|
||||
<Text>{createMessage(BRANCH_PROTECTION_CHANGE_RULE)}</Text>
|
||||
</Container>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
data-testid="t--start-using-git-button"
|
||||
kind="secondary"
|
||||
|
|
@ -170,29 +138,6 @@ function ConnectionSuccess() {
|
|||
<Button onClick={handleOpenSettings} size="md">
|
||||
{createMessage(OPEN_GIT_SETTINGS)}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalBody>
|
||||
<Container>
|
||||
<TitleContainer>
|
||||
<StyledIcon color="#059669" name="oval-check" size="lg" />
|
||||
<TitleText kind="heading-s" renderAs="h3">
|
||||
{createMessage(GIT_CONNECT_SUCCESS_TITLE)}
|
||||
</TitleText>
|
||||
</TitleContainer>
|
||||
{isGitProtectedFeatureEnabled
|
||||
? postBranchProtectionContent()
|
||||
: preBranchProtectionContent()}
|
||||
</Container>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{isGitProtectedFeatureEnabled
|
||||
? postBranchProtectionActions()
|
||||
: preBranchProtectionActions()}
|
||||
</ModalFooter>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,14 +6,12 @@ import {
|
|||
createMessage,
|
||||
} from "@appsmith/constants/messages";
|
||||
import { updateGitDefaultBranch } from "actions/gitSyncActions";
|
||||
import { isCEMode } from "@appsmith/utils";
|
||||
import { Button, Link, Option, Select, Text } from "design-system";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { getGitBranches } from "selectors/gitSyncSelectors";
|
||||
import styled from "styled-components";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
||||
import { useAppsmithEnterpriseLink } from "./hooks";
|
||||
|
||||
const Container = styled.div`
|
||||
padding-top: 16px;
|
||||
|
|
@ -43,10 +41,9 @@ const StyledSelect = styled(Select)`
|
|||
`;
|
||||
|
||||
function GitDefaultBranch() {
|
||||
const isCE = isCEMode();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const isGitProtectedFeatureLicensed = useFeatureFlag(
|
||||
FEATURE_FLAG.license_git_branch_protection_enabled,
|
||||
);
|
||||
const unfilteredBranches = useSelector(getGitBranches);
|
||||
const [selectedValue, setSelectedValue] = useState<string | undefined>();
|
||||
|
||||
|
|
@ -55,10 +52,6 @@ function GitDefaultBranch() {
|
|||
return defaultBranch?.branchName;
|
||||
}, [unfilteredBranches]);
|
||||
|
||||
const enterprisePricingLink = useAppsmithEnterpriseLink(
|
||||
"git_branch_protection",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultBranch = unfilteredBranches.find((b) => b.default);
|
||||
setSelectedValue(defaultBranch?.branchName);
|
||||
|
|
@ -75,7 +68,7 @@ function GitDefaultBranch() {
|
|||
};
|
||||
|
||||
const updateIsDisabled =
|
||||
!selectedValue || selectedValue === currentDefaultBranch;
|
||||
isCE && (!selectedValue || selectedValue === currentDefaultBranch);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
|
|
@ -86,24 +79,21 @@ function GitDefaultBranch() {
|
|||
<SectionDesc kind="body-m" renderAs="p">
|
||||
{createMessage(DEFAULT_BRANCH_DESC)}
|
||||
</SectionDesc>
|
||||
{!isGitProtectedFeatureLicensed && (
|
||||
<SectionDesc kind="body-m" renderAs="p">
|
||||
To change your default branch, try{" "}
|
||||
<Link
|
||||
kind="primary"
|
||||
style={{ display: "inline-flex" }}
|
||||
target="_blank"
|
||||
to={enterprisePricingLink}
|
||||
>
|
||||
{createMessage(APPSMITH_ENTERPRISE)}
|
||||
</Link>
|
||||
</SectionDesc>
|
||||
)}
|
||||
<SectionDesc kind="body-m" renderAs="p">
|
||||
To change your default branch, try{" "}
|
||||
<Link
|
||||
kind="primary"
|
||||
style={{ display: "inline-flex" }}
|
||||
target="_blank"
|
||||
to="https://www.appsmith.com/enterprise?lead_source=git%20feat%20branch%20config"
|
||||
>
|
||||
{createMessage(APPSMITH_ENTERPRISE)}
|
||||
</Link>
|
||||
</SectionDesc>
|
||||
</HeadContainer>
|
||||
<BodyContainer>
|
||||
<StyledSelect
|
||||
data-testid="t--git-default-branch-select"
|
||||
isDisabled={!isGitProtectedFeatureLicensed}
|
||||
isDisabled={isCE}
|
||||
onChange={(v) => setSelectedValue(v)}
|
||||
value={selectedValue}
|
||||
>
|
||||
|
|
@ -114,7 +104,6 @@ function GitDefaultBranch() {
|
|||
))}
|
||||
</StyledSelect>
|
||||
<Button
|
||||
data-testid="t--git-default-branch-update-btn"
|
||||
isDisabled={updateIsDisabled}
|
||||
kind="secondary"
|
||||
onClick={handleUpdate}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
UPDATE,
|
||||
createMessage,
|
||||
} from "@appsmith/constants/messages";
|
||||
import { isCEMode } from "@appsmith/utils";
|
||||
import { updateGitProtectedBranchesInit } from "actions/gitSyncActions";
|
||||
import { Button, Link, Option, Select, Text } from "design-system";
|
||||
import { xor } from "lodash";
|
||||
|
|
@ -17,9 +18,6 @@ import {
|
|||
getProtectedBranchesSelector,
|
||||
} from "selectors/gitSyncSelectors";
|
||||
import styled from "styled-components";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
||||
import { useAppsmithEnterpriseLink } from "./hooks";
|
||||
|
||||
const Container = styled.div`
|
||||
padding-top: 16px;
|
||||
|
|
@ -48,29 +46,19 @@ const StyledSelect = styled(Select)`
|
|||
margin-right: 12px;
|
||||
`;
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
function GitProtectedBranches() {
|
||||
const isCE = isCEMode();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const unfilteredBranches = useSelector(getGitBranches);
|
||||
const branches = unfilteredBranches.filter(
|
||||
(b) => !b.branchName.includes("origin/"),
|
||||
);
|
||||
const isGitProtectedFeatureLicensed = useFeatureFlag(
|
||||
FEATURE_FLAG.license_git_branch_protection_enabled,
|
||||
);
|
||||
const defaultBranch = useSelector(getDefaultGitBranchName);
|
||||
const protectedBranches = useSelector(getProtectedBranchesSelector);
|
||||
const isUpdateLoading = useSelector(getIsUpdateProtectedBranchesLoading);
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>();
|
||||
|
||||
const enterprisePricingLink = useAppsmithEnterpriseLink(
|
||||
"git_branch_protection",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedValues(protectedBranches);
|
||||
}, []);
|
||||
|
|
@ -98,22 +86,20 @@ function GitProtectedBranches() {
|
|||
<SectionDesc kind="body-m" renderAs="p">
|
||||
{createMessage(BRANCH_PROTECTION_DESC)}
|
||||
</SectionDesc>
|
||||
{!isGitProtectedFeatureLicensed && (
|
||||
<SectionDesc kind="body-m" renderAs="p">
|
||||
To protect multiple branches, try{" "}
|
||||
<StyledLink
|
||||
kind="primary"
|
||||
target="_blank"
|
||||
to={enterprisePricingLink}
|
||||
>
|
||||
{createMessage(APPSMITH_ENTERPRISE)}
|
||||
</StyledLink>
|
||||
</SectionDesc>
|
||||
)}
|
||||
<SectionDesc kind="body-m" renderAs="p">
|
||||
To protect multiple branches, try{" "}
|
||||
<Link
|
||||
kind="primary"
|
||||
style={{ display: "inline-flex" }}
|
||||
target="_blank"
|
||||
to="https://www.appsmith.com/enterprise?lead_source=git%20feat%20branch%20config"
|
||||
>
|
||||
{createMessage(APPSMITH_ENTERPRISE)}
|
||||
</Link>
|
||||
</SectionDesc>
|
||||
</HeadContainer>
|
||||
<BodyContainer>
|
||||
<StyledSelect
|
||||
data-testid="t--git-protected-branches-select"
|
||||
isMultiSelect
|
||||
maxTagTextLength={8}
|
||||
onChange={(v) => setSelectedValues(v)}
|
||||
|
|
@ -121,9 +107,7 @@ function GitProtectedBranches() {
|
|||
>
|
||||
{branches.map((b) => (
|
||||
<Option
|
||||
disabled={
|
||||
!isGitProtectedFeatureLicensed && b.branchName !== defaultBranch
|
||||
}
|
||||
disabled={isCE && b.branchName !== defaultBranch}
|
||||
key={b.branchName}
|
||||
value={b.branchName}
|
||||
>
|
||||
|
|
@ -132,7 +116,6 @@ function GitProtectedBranches() {
|
|||
))}
|
||||
</StyledSelect>
|
||||
<Button
|
||||
data-testid="t--git-protected-branches-update-btn"
|
||||
isDisabled={updateIsDisabled}
|
||||
isLoading={isUpdateLoading}
|
||||
kind="secondary"
|
||||
|
|
|
|||
|
|
@ -34,9 +34,12 @@ import type {
|
|||
import {
|
||||
fetchGitRemoteStatusInit,
|
||||
fetchGitProtectedBranchesInit,
|
||||
updateGitProtectedBranchesInit,
|
||||
fetchGitProtectedBranchesSuccess,
|
||||
fetchGitProtectedBranchesError,
|
||||
fetchGitRemoteStatusSuccess,
|
||||
clearCommitSuccessfulState,
|
||||
updateGitProtectedBranchesError,
|
||||
updateGitProtectedBranchesSuccess,
|
||||
updateGitProtectedBranchesInit,
|
||||
} from "actions/gitSyncActions";
|
||||
import {
|
||||
commitToRepoSuccess,
|
||||
|
|
@ -87,7 +90,6 @@ import {
|
|||
ERROR_GIT_AUTH_FAIL,
|
||||
ERROR_GIT_INVALID_REMOTE,
|
||||
GIT_USER_UPDATED_SUCCESSFULLY,
|
||||
PROTECT_BRANCH_SUCCESS,
|
||||
} from "@appsmith/constants/messages";
|
||||
import type { GitApplicationMetadata } from "@appsmith/api/ApplicationApi";
|
||||
|
||||
|
|
@ -253,7 +255,6 @@ function* connectToGitSaga(action: ConnectToGitReduxAction) {
|
|||
|
||||
/* commit effect START */
|
||||
yield put(commitToRepoSuccess());
|
||||
yield put(clearCommitSuccessfulState());
|
||||
const curApplication: ApplicationPayload = yield select(
|
||||
getCurrentApplication,
|
||||
);
|
||||
|
|
@ -1080,24 +1081,14 @@ function* fetchGitProtectedBranchesSaga() {
|
|||
);
|
||||
if (isValidResponse) {
|
||||
const protectedBranches: string[] = response?.data;
|
||||
yield put({
|
||||
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_SUCCESS,
|
||||
payload: { protectedBranches },
|
||||
});
|
||||
yield put(fetchGitProtectedBranchesSuccess(protectedBranches));
|
||||
} else {
|
||||
yield put({
|
||||
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_ERROR,
|
||||
payload: {
|
||||
error: response?.responseMeta?.error?.message,
|
||||
show: true,
|
||||
},
|
||||
});
|
||||
yield put(
|
||||
fetchGitProtectedBranchesError(response?.responseMeta?.error?.message),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_ERROR,
|
||||
payload: { error, show: true },
|
||||
});
|
||||
yield put(fetchGitProtectedBranchesError(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1112,40 +1103,16 @@ function* updateGitProtectedBranchesSaga({
|
|||
}
|
||||
const { protectedBranches } = payload;
|
||||
const applicationId: string = yield select(getCurrentApplicationId);
|
||||
let response: ApiResponse<string[]>;
|
||||
try {
|
||||
response = yield call(
|
||||
yield call(
|
||||
GitSyncAPI.updateProtectedBranches,
|
||||
applicationId,
|
||||
protectedBranches,
|
||||
);
|
||||
const isValidResponse: boolean = yield validateResponse(
|
||||
response,
|
||||
false,
|
||||
getLogToSentryFromResponse(response),
|
||||
);
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_SUCCESS,
|
||||
});
|
||||
yield put(fetchGitProtectedBranchesInit());
|
||||
toast.show(createMessage(PROTECT_BRANCH_SUCCESS), {
|
||||
kind: "success",
|
||||
});
|
||||
} else {
|
||||
yield put({
|
||||
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_ERROR,
|
||||
payload: {
|
||||
error: response?.responseMeta?.error?.message,
|
||||
show: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
yield put(updateGitProtectedBranchesSuccess());
|
||||
yield put(fetchGitProtectedBranchesInit());
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_ERROR,
|
||||
payload: { error, show: true },
|
||||
});
|
||||
yield put(updateGitProtectedBranchesError(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ import org.eclipse.jgit.util.StringUtils;
|
|||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.observability.micrometer.Micrometer;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
|
@ -151,7 +150,6 @@ public class GitServiceCEImpl implements GitServiceCE {
|
|||
private final RedisUtils redisUtils;
|
||||
private final ObservationRegistry observationRegistry;
|
||||
private final GitPrivateRepoHelper gitPrivateRepoHelper;
|
||||
private final TransactionalOperator transactionalOperator;
|
||||
|
||||
private static final Duration RETRY_DELAY = Duration.ofSeconds(1);
|
||||
private static final Integer MAX_RETRIES = 20;
|
||||
|
|
@ -3330,8 +3328,7 @@ public class GitServiceCEImpl implements GitServiceCE {
|
|||
// user want to protect multiple branches, not allowed
|
||||
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
|
||||
}
|
||||
})
|
||||
.as(transactionalOperator::transactional);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
|
|
@ -159,7 +158,7 @@ public class GitServiceCETest {
|
|||
@Autowired
|
||||
ApplicationPageService applicationPageService;
|
||||
|
||||
@SpyBean
|
||||
@Autowired
|
||||
ApplicationService applicationService;
|
||||
|
||||
@Autowired
|
||||
|
|
@ -4354,7 +4353,7 @@ public class GitServiceCETest {
|
|||
.assertNext(applicationList -> {
|
||||
for (Application application : applicationList) {
|
||||
GitApplicationMetadata metadata = application.getGitApplicationMetadata();
|
||||
assertThat(metadata.getIsProtectedBranch()).isNotEqualTo(TRUE);
|
||||
assertThat(metadata.getIsProtectedBranch()).isNotEqualTo(true);
|
||||
if (application.getId().equals(defaultAppId)) {
|
||||
// the default app should have the empty protected branch list
|
||||
assertThat(metadata.getBranchProtectionRules()).isEmpty();
|
||||
|
|
@ -4375,30 +4374,4 @@ public class GitServiceCETest {
|
|||
.expectErrorMessage(AppsmithError.ACTION_IS_NOT_AUTHORIZED.getMessage("Protect branch"))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void updateProtectedBranches_WhenOneOperationFails_ChangesReverted() {
|
||||
List<String> branchList = List.of("master", "develop", "feature");
|
||||
// create three app with master as the default branch
|
||||
String defaultAppId = createBranchedApplication(branchList);
|
||||
|
||||
Mockito.when(applicationService.updateProtectedBranches(Mockito.any(), Mockito.any()))
|
||||
.thenReturn(Mono.error(new AppsmithException(AppsmithError.GENERIC_BAD_REQUEST, "Test error")));
|
||||
|
||||
Mono<List<String>> updateProtectedBranchesMono = gitService.updateProtectedBranches(defaultAppId, List.of());
|
||||
|
||||
StepVerifier.create(updateProtectedBranchesMono)
|
||||
.expectErrorMessage(AppsmithError.GENERIC_BAD_REQUEST.getMessage("Test error"))
|
||||
.verify();
|
||||
|
||||
StepVerifier.create(applicationService.findById(defaultAppId))
|
||||
.assertNext(application -> {
|
||||
GitApplicationMetadata metadata = application.getGitApplicationMetadata();
|
||||
assertThat(metadata.getIsProtectedBranch()).isNotEqualTo(TRUE);
|
||||
// the default app should have the empty protected branch list
|
||||
assertThat(metadata.getBranchProtectionRules()).isNullOrEmpty();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user