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:
parent
0238a8adc8
commit
64ef0c33dd
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => ({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 }));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -22,3 +22,5 @@ export enum CREDENTIAL_MODE {
|
|||
MANUALLY = "MANUALLY",
|
||||
IMPORT_JSON = "IMPORT_JSON",
|
||||
}
|
||||
|
||||
export const REMOTE_BRANCH_PREFIX = "origin/";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user