fix: git branch protection bug fixes (#28749)

## Description
Fixes a couple of issues with branch protection
- #28727 Toast message issues
- #28726 Pull count on pull icon
- #28748 Display remote branches in list
- #28697 Pulling protected branch shows discard toast
- Added branch name to default branch toast
- #28698 Select not scrolling bug

#### PR fixes following issue(s)
Mentioned above

#### Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video
>
>
#### Type of change
- Bug fix (non-breaking change which fixes an issue)
>
>
>
## Testing
>
#### How Has This Been Tested?
> 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
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [ ] 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


#### QA activity:
- [ ] [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
This commit is contained in:
Rudraprasad Das 2023-11-09 16:58:04 +05:30 committed by Nayan
parent 0238a8adc8
commit 64ef0c33dd
10 changed files with 74 additions and 41 deletions

View File

@ -474,9 +474,9 @@ export class GitSync {
this.agHelper.AssertContains(
Cypress.env("MESSAGES").DISCARDING_AND_PULLING_CHANGES(),
);
this.agHelper.AssertContains("Discarded changes successfully");
this.assertHelper.AssertNetworkStatus("@discardChanges");
this.assertHelper.AssertNetworkStatus("@gitStatus");
this.agHelper.AssertContains("Discarded changes successfully");
this.agHelper.AssertElementExist(this._bottomBarCommit, 0, 30000);
}

View File

@ -196,8 +196,11 @@ export const fetchGitRemoteStatusSuccess = (payload: GitRemoteStatusData) => ({
payload,
});
export const discardChanges = () => ({
export const discardChanges = (
payload: { successToastMessage?: string } | undefined | null = {},
) => ({
type: ReduxActionTypes.GIT_DISCARD_CHANGES,
payload,
});
export const discardChangesSuccess = (payload: any) => ({

View File

@ -842,6 +842,7 @@ export const COMMITTING_AND_PUSHING_CHANGES = () =>
export const DISCARDING_AND_PULLING_CHANGES = () =>
"Discarding and pulling changes...";
export const DISCARD_SUCCESS = () => "Discarded changes successfully.";
export const DISCARD_AND_PULL_SUCCESS = () => "Pulled from remote successfully";
export const IS_MERGING = () => "Merging changes...";
@ -1087,7 +1088,8 @@ 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";
export const UPDATE_DEFAULT_BRANCH_SUCCESS = (branchName: string) =>
`Updated default branch ${!!branchName ? `to ${branchName}` : ""}`;
export const CONTACT_ADMIN_FOR_GIT = () =>
"Please contact your workspace admin to connect your app to a git repo";
// Git Branch Protection end

View File

@ -65,7 +65,6 @@ import {
createMessage,
DELETING_APPLICATION,
DELETING_MULTIPLE_APPLICATION,
DISCARD_SUCCESS,
ERROR_IMPORTING_APPLICATION_TO_WORKSPACE,
} from "@appsmith/constants/messages";
import { APP_MODE } from "entities/App";
@ -300,12 +299,6 @@ export function* fetchAppAndPagesSaga(
},
});
if (localStorage.getItem("GIT_DISCARD_CHANGES") === "success") {
toast.show(createMessage(DISCARD_SUCCESS), {
kind: "success",
});
localStorage.setItem("GIT_DISCARD_CHANGES", "");
}
yield put({
type: ReduxActionTypes.SET_APP_VERSION_ON_WORKER,
payload: response.data.application?.evaluationVersion,

View File

@ -13,6 +13,7 @@ import {
CONNECTING_TO_REPO_DISABLED,
CONTACT_ADMIN_FOR_GIT,
createMessage,
DISCARD_AND_PULL_SUCCESS,
DURING_ONBOARDING_TOUR,
GIT_SETTINGS,
MERGE,
@ -136,8 +137,11 @@ const getPullBtnStatus = (
) => {
const { behindCount, isClean } = gitStatus || {};
let message = createMessage(NO_COMMITS_TO_PULL);
let disabled = isProtected ? false : behindCount === 0;
if (!isClean) {
let disabled = behindCount === 0;
if (isProtected) {
disabled = false;
message = createMessage(PULL_CHANGES);
} else if (!isClean) {
disabled = true;
message = createMessage(CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES);
} else if (pullFailed) {
@ -189,7 +193,7 @@ const getQuickActionButtons = ({
},
{
className: "t--bottom-bar-pull",
count: isProtectedMode ? undefined : gitStatus?.behindCount,
count: gitStatus?.behindCount,
icon: "down-arrow-2",
onClick: () => !pullDisabled && pull(),
tooltipText: pullTooltipMessage,
@ -360,7 +364,11 @@ export default function QuickGitActions() {
source: "BOTTOM_BAR_GIT_PULL_BUTTON",
});
if (isProtectedMode) {
dispatch(discardChanges());
dispatch(
discardChanges({
successToastMessage: createMessage(DISCARD_AND_PULL_SUCCESS),
}),
);
} else {
dispatch(gitPullInit({ triggeredFromBottomBar: true }));
}

View File

@ -103,6 +103,8 @@ function GitDefaultBranch() {
<BodyContainer>
<StyledSelect
data-testid="t--git-default-branch-select"
dropdownMatchSelectWidth
getPopupContainer={(triggerNode) => triggerNode.parentNode}
isDisabled={!isGitProtectedFeatureLicensed}
onChange={(v) => setSelectedValue(v)}
value={selectedValue}

View File

@ -7,7 +7,7 @@ import {
} from "@appsmith/constants/messages";
import { updateGitProtectedBranchesInit } from "actions/gitSyncActions";
import { Button, Link, Option, Select, Text } from "design-system";
import { xor } from "lodash";
import { union, xor } from "lodash";
import React, { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
@ -20,6 +20,7 @@ import styled from "styled-components";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { useAppsmithEnterpriseLink } from "./hooks";
import { REMOTE_BRANCH_PREFIX } from "../../constants";
const Container = styled.div`
padding-top: 16px;
@ -56,13 +57,33 @@ function GitProtectedBranches() {
const dispatch = useDispatch();
const unfilteredBranches = useSelector(getGitBranches);
const branches = unfilteredBranches.filter(
(b) => !b.branchName.includes("origin/"),
);
const defaultBranch = useSelector(getDefaultGitBranchName);
const branchNames = useMemo(() => {
const remoteBranchNames = [];
const localBranchNames = [];
for (const unfilteredBranch of unfilteredBranches) {
if (unfilteredBranch.branchName === defaultBranch) {
continue;
}
if (unfilteredBranch.branchName.includes(REMOTE_BRANCH_PREFIX)) {
remoteBranchNames.push(
unfilteredBranch.branchName.replace(REMOTE_BRANCH_PREFIX, ""),
);
} else {
localBranchNames.push(unfilteredBranch.branchName);
}
}
const branchNames = union(localBranchNames, remoteBranchNames);
if (defaultBranch) {
branchNames.unshift(defaultBranch);
}
return branchNames;
}, [unfilteredBranches, defaultBranch]);
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[]>();
@ -114,20 +135,22 @@ function GitProtectedBranches() {
<BodyContainer>
<StyledSelect
data-testid="t--git-protected-branches-select"
dropdownMatchSelectWidth
getPopupContainer={(triggerNode) => triggerNode.parentNode}
isMultiSelect
maxTagTextLength={8}
onChange={(v) => setSelectedValues(v)}
value={selectedValues}
>
{branches.map((b) => (
{branchNames.map((branchName) => (
<Option
disabled={
!isGitProtectedFeatureLicensed && b.branchName !== defaultBranch
!isGitProtectedFeatureLicensed && branchName !== defaultBranch
}
key={b.branchName}
value={b.branchName}
key={branchName}
value={branchName}
>
{b.branchName}
{branchName}
</Option>
))}
</StyledSelect>

View File

@ -22,3 +22,5 @@ export enum CREDENTIAL_MODE {
MANUALLY = "MANUALLY",
IMPORT_JSON = "IMPORT_JSON",
}
export const REMOTE_BRANCH_PREFIX = "origin/";

View File

@ -1,15 +1,8 @@
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { getTenantPermissions } from "@appsmith/selectors/tenantSelectors";
import { getHasCreateWorkspacePermission } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
import { useSelector } from "react-redux";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { getCurrentAppWorkspace } from "@appsmith/selectors/workspaceSelectors";
import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers";
export const useIsGitAdmin = () => {
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const tenantPermissions = useSelector(getTenantPermissions);
const canCreateWorkspace = getHasCreateWorkspacePermission(
isFeatureEnabled,
tenantPermissions,
);
return canCreateWorkspace;
const workspace = useSelector(getCurrentAppWorkspace);
return hasCreateNewAppPermission(workspace.userPermissions);
};

View File

@ -10,6 +10,7 @@ import {
import {
actionChannel,
call,
delay,
fork,
put,
select,
@ -84,6 +85,7 @@ import {
import {
createMessage,
DELETE_BRANCH_SUCCESS,
DISCARD_SUCCESS,
ERROR_GIT_AUTH_FAIL,
ERROR_GIT_INVALID_REMOTE,
GIT_USER_UPDATED_SUCCESSFULLY,
@ -1024,11 +1026,13 @@ export function* deleteBranch({ payload }: ReduxAction<any>) {
yield put(fetchBranchesInit({ pruneBranches: true }));
}
} catch (error) {
yield put(deleteBranchError(error));
yield put(deleteBranchError({ error, show: true }));
}
}
function* discardChanges() {
function* discardChanges({
payload,
}: ReduxAction<{ successToastMessage: string } | null | undefined>) {
let response: ApiResponse<GitDiscardResponse>;
try {
const appId: string = yield select(getCurrentApplicationId);
@ -1040,10 +1044,15 @@ function* discardChanges() {
);
if (isValidResponse) {
yield put(discardChangesSuccess(response.data));
// const applicationId: string = response.data.id;
const successToastMessage =
payload?.successToastMessage ?? createMessage(DISCARD_SUCCESS);
toast.show(successToastMessage, {
kind: "success",
});
// adding delay to show toast animation before reloading
yield delay(500);
const pageId: string =
response.data?.pages?.find((page: any) => page.isDefault)?.id || "";
localStorage.setItem("GIT_DISCARD_CHANGES", "success");
const branch = response.data.gitApplicationMetadata.branchName;
window.open(builderURL({ pageId, branch }), "_self");
} else {
@ -1053,11 +1062,9 @@ function* discardChanges() {
show: true,
}),
);
localStorage.setItem("GIT_DISCARD_CHANGES", "failure");
}
} catch (error) {
yield put(discardChangesFailure({ error, show: true }));
localStorage.setItem("GIT_DISCARD_CHANGES", "failure");
}
}