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(
|
this.agHelper.AssertContains(
|
||||||
Cypress.env("MESSAGES").DISCARDING_AND_PULLING_CHANGES(),
|
Cypress.env("MESSAGES").DISCARDING_AND_PULLING_CHANGES(),
|
||||||
);
|
);
|
||||||
|
this.agHelper.AssertContains("Discarded changes successfully");
|
||||||
this.assertHelper.AssertNetworkStatus("@discardChanges");
|
this.assertHelper.AssertNetworkStatus("@discardChanges");
|
||||||
this.assertHelper.AssertNetworkStatus("@gitStatus");
|
this.assertHelper.AssertNetworkStatus("@gitStatus");
|
||||||
this.agHelper.AssertContains("Discarded changes successfully");
|
|
||||||
this.agHelper.AssertElementExist(this._bottomBarCommit, 0, 30000);
|
this.agHelper.AssertElementExist(this._bottomBarCommit, 0, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,8 +196,11 @@ export const fetchGitRemoteStatusSuccess = (payload: GitRemoteStatusData) => ({
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const discardChanges = () => ({
|
export const discardChanges = (
|
||||||
|
payload: { successToastMessage?: string } | undefined | null = {},
|
||||||
|
) => ({
|
||||||
type: ReduxActionTypes.GIT_DISCARD_CHANGES,
|
type: ReduxActionTypes.GIT_DISCARD_CHANGES,
|
||||||
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const discardChangesSuccess = (payload: any) => ({
|
export const discardChangesSuccess = (payload: any) => ({
|
||||||
|
|
|
||||||
|
|
@ -842,6 +842,7 @@ export const COMMITTING_AND_PUSHING_CHANGES = () =>
|
||||||
export const DISCARDING_AND_PULLING_CHANGES = () =>
|
export const DISCARDING_AND_PULLING_CHANGES = () =>
|
||||||
"Discarding and pulling changes...";
|
"Discarding and pulling changes...";
|
||||||
export const DISCARD_SUCCESS = () => "Discarded changes successfully.";
|
export const DISCARD_SUCCESS = () => "Discarded changes successfully.";
|
||||||
|
export const DISCARD_AND_PULL_SUCCESS = () => "Pulled from remote successfully";
|
||||||
|
|
||||||
export const IS_MERGING = () => "Merging changes...";
|
export const IS_MERGING = () => "Merging changes...";
|
||||||
|
|
||||||
|
|
@ -1087,7 +1088,8 @@ export const NOW_PROTECT_BRANCH = () =>
|
||||||
"You can now protect your default branch.";
|
"You can now protect your default branch.";
|
||||||
export const APPSMITH_ENTERPRISE = () => "Appsmith Enterprise";
|
export const APPSMITH_ENTERPRISE = () => "Appsmith Enterprise";
|
||||||
export const PROTECT_BRANCH_SUCCESS = () => "Changed protected branches";
|
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 = () =>
|
export const CONTACT_ADMIN_FOR_GIT = () =>
|
||||||
"Please contact your workspace admin to connect your app to a git repo";
|
"Please contact your workspace admin to connect your app to a git repo";
|
||||||
// Git Branch Protection end
|
// Git Branch Protection end
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ import {
|
||||||
createMessage,
|
createMessage,
|
||||||
DELETING_APPLICATION,
|
DELETING_APPLICATION,
|
||||||
DELETING_MULTIPLE_APPLICATION,
|
DELETING_MULTIPLE_APPLICATION,
|
||||||
DISCARD_SUCCESS,
|
|
||||||
ERROR_IMPORTING_APPLICATION_TO_WORKSPACE,
|
ERROR_IMPORTING_APPLICATION_TO_WORKSPACE,
|
||||||
} from "@appsmith/constants/messages";
|
} from "@appsmith/constants/messages";
|
||||||
import { APP_MODE } from "entities/App";
|
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({
|
yield put({
|
||||||
type: ReduxActionTypes.SET_APP_VERSION_ON_WORKER,
|
type: ReduxActionTypes.SET_APP_VERSION_ON_WORKER,
|
||||||
payload: response.data.application?.evaluationVersion,
|
payload: response.data.application?.evaluationVersion,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
CONNECTING_TO_REPO_DISABLED,
|
CONNECTING_TO_REPO_DISABLED,
|
||||||
CONTACT_ADMIN_FOR_GIT,
|
CONTACT_ADMIN_FOR_GIT,
|
||||||
createMessage,
|
createMessage,
|
||||||
|
DISCARD_AND_PULL_SUCCESS,
|
||||||
DURING_ONBOARDING_TOUR,
|
DURING_ONBOARDING_TOUR,
|
||||||
GIT_SETTINGS,
|
GIT_SETTINGS,
|
||||||
MERGE,
|
MERGE,
|
||||||
|
|
@ -136,8 +137,11 @@ const getPullBtnStatus = (
|
||||||
) => {
|
) => {
|
||||||
const { behindCount, isClean } = gitStatus || {};
|
const { behindCount, isClean } = gitStatus || {};
|
||||||
let message = createMessage(NO_COMMITS_TO_PULL);
|
let message = createMessage(NO_COMMITS_TO_PULL);
|
||||||
let disabled = isProtected ? false : behindCount === 0;
|
let disabled = behindCount === 0;
|
||||||
if (!isClean) {
|
if (isProtected) {
|
||||||
|
disabled = false;
|
||||||
|
message = createMessage(PULL_CHANGES);
|
||||||
|
} else if (!isClean) {
|
||||||
disabled = true;
|
disabled = true;
|
||||||
message = createMessage(CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES);
|
message = createMessage(CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES);
|
||||||
} else if (pullFailed) {
|
} else if (pullFailed) {
|
||||||
|
|
@ -189,7 +193,7 @@ const getQuickActionButtons = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
className: "t--bottom-bar-pull",
|
className: "t--bottom-bar-pull",
|
||||||
count: isProtectedMode ? undefined : gitStatus?.behindCount,
|
count: gitStatus?.behindCount,
|
||||||
icon: "down-arrow-2",
|
icon: "down-arrow-2",
|
||||||
onClick: () => !pullDisabled && pull(),
|
onClick: () => !pullDisabled && pull(),
|
||||||
tooltipText: pullTooltipMessage,
|
tooltipText: pullTooltipMessage,
|
||||||
|
|
@ -360,7 +364,11 @@ export default function QuickGitActions() {
|
||||||
source: "BOTTOM_BAR_GIT_PULL_BUTTON",
|
source: "BOTTOM_BAR_GIT_PULL_BUTTON",
|
||||||
});
|
});
|
||||||
if (isProtectedMode) {
|
if (isProtectedMode) {
|
||||||
dispatch(discardChanges());
|
dispatch(
|
||||||
|
discardChanges({
|
||||||
|
successToastMessage: createMessage(DISCARD_AND_PULL_SUCCESS),
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
dispatch(gitPullInit({ triggeredFromBottomBar: true }));
|
dispatch(gitPullInit({ triggeredFromBottomBar: true }));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,8 @@ function GitDefaultBranch() {
|
||||||
<BodyContainer>
|
<BodyContainer>
|
||||||
<StyledSelect
|
<StyledSelect
|
||||||
data-testid="t--git-default-branch-select"
|
data-testid="t--git-default-branch-select"
|
||||||
|
dropdownMatchSelectWidth
|
||||||
|
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||||
isDisabled={!isGitProtectedFeatureLicensed}
|
isDisabled={!isGitProtectedFeatureLicensed}
|
||||||
onChange={(v) => setSelectedValue(v)}
|
onChange={(v) => setSelectedValue(v)}
|
||||||
value={selectedValue}
|
value={selectedValue}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from "@appsmith/constants/messages";
|
} from "@appsmith/constants/messages";
|
||||||
import { updateGitProtectedBranchesInit } from "actions/gitSyncActions";
|
import { updateGitProtectedBranchesInit } from "actions/gitSyncActions";
|
||||||
import { Button, Link, Option, Select, Text } from "design-system";
|
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 React, { useEffect, useMemo, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
|
|
@ -20,6 +20,7 @@ import styled from "styled-components";
|
||||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
||||||
import { useAppsmithEnterpriseLink } from "./hooks";
|
import { useAppsmithEnterpriseLink } from "./hooks";
|
||||||
|
import { REMOTE_BRANCH_PREFIX } from "../../constants";
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
|
@ -56,13 +57,33 @@ function GitProtectedBranches() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const unfilteredBranches = useSelector(getGitBranches);
|
const unfilteredBranches = useSelector(getGitBranches);
|
||||||
const branches = unfilteredBranches.filter(
|
const defaultBranch = useSelector(getDefaultGitBranchName);
|
||||||
(b) => !b.branchName.includes("origin/"),
|
|
||||||
);
|
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(
|
const isGitProtectedFeatureLicensed = useFeatureFlag(
|
||||||
FEATURE_FLAG.license_git_branch_protection_enabled,
|
FEATURE_FLAG.license_git_branch_protection_enabled,
|
||||||
);
|
);
|
||||||
const defaultBranch = useSelector(getDefaultGitBranchName);
|
|
||||||
const protectedBranches = useSelector(getProtectedBranchesSelector);
|
const protectedBranches = useSelector(getProtectedBranchesSelector);
|
||||||
const isUpdateLoading = useSelector(getIsUpdateProtectedBranchesLoading);
|
const isUpdateLoading = useSelector(getIsUpdateProtectedBranchesLoading);
|
||||||
const [selectedValues, setSelectedValues] = useState<string[]>();
|
const [selectedValues, setSelectedValues] = useState<string[]>();
|
||||||
|
|
@ -114,20 +135,22 @@ function GitProtectedBranches() {
|
||||||
<BodyContainer>
|
<BodyContainer>
|
||||||
<StyledSelect
|
<StyledSelect
|
||||||
data-testid="t--git-protected-branches-select"
|
data-testid="t--git-protected-branches-select"
|
||||||
|
dropdownMatchSelectWidth
|
||||||
|
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||||
isMultiSelect
|
isMultiSelect
|
||||||
maxTagTextLength={8}
|
maxTagTextLength={8}
|
||||||
onChange={(v) => setSelectedValues(v)}
|
onChange={(v) => setSelectedValues(v)}
|
||||||
value={selectedValues}
|
value={selectedValues}
|
||||||
>
|
>
|
||||||
{branches.map((b) => (
|
{branchNames.map((branchName) => (
|
||||||
<Option
|
<Option
|
||||||
disabled={
|
disabled={
|
||||||
!isGitProtectedFeatureLicensed && b.branchName !== defaultBranch
|
!isGitProtectedFeatureLicensed && branchName !== defaultBranch
|
||||||
}
|
}
|
||||||
key={b.branchName}
|
key={branchName}
|
||||||
value={b.branchName}
|
value={branchName}
|
||||||
>
|
>
|
||||||
{b.branchName}
|
{branchName}
|
||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</StyledSelect>
|
</StyledSelect>
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,5 @@ export enum CREDENTIAL_MODE {
|
||||||
MANUALLY = "MANUALLY",
|
MANUALLY = "MANUALLY",
|
||||||
IMPORT_JSON = "IMPORT_JSON",
|
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 { 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 = () => {
|
export const useIsGitAdmin = () => {
|
||||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
const workspace = useSelector(getCurrentAppWorkspace);
|
||||||
const tenantPermissions = useSelector(getTenantPermissions);
|
return hasCreateNewAppPermission(workspace.userPermissions);
|
||||||
const canCreateWorkspace = getHasCreateWorkspacePermission(
|
|
||||||
isFeatureEnabled,
|
|
||||||
tenantPermissions,
|
|
||||||
);
|
|
||||||
return canCreateWorkspace;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import {
|
import {
|
||||||
actionChannel,
|
actionChannel,
|
||||||
call,
|
call,
|
||||||
|
delay,
|
||||||
fork,
|
fork,
|
||||||
put,
|
put,
|
||||||
select,
|
select,
|
||||||
|
|
@ -84,6 +85,7 @@ import {
|
||||||
import {
|
import {
|
||||||
createMessage,
|
createMessage,
|
||||||
DELETE_BRANCH_SUCCESS,
|
DELETE_BRANCH_SUCCESS,
|
||||||
|
DISCARD_SUCCESS,
|
||||||
ERROR_GIT_AUTH_FAIL,
|
ERROR_GIT_AUTH_FAIL,
|
||||||
ERROR_GIT_INVALID_REMOTE,
|
ERROR_GIT_INVALID_REMOTE,
|
||||||
GIT_USER_UPDATED_SUCCESSFULLY,
|
GIT_USER_UPDATED_SUCCESSFULLY,
|
||||||
|
|
@ -1024,11 +1026,13 @@ export function* deleteBranch({ payload }: ReduxAction<any>) {
|
||||||
yield put(fetchBranchesInit({ pruneBranches: true }));
|
yield put(fetchBranchesInit({ pruneBranches: true }));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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>;
|
let response: ApiResponse<GitDiscardResponse>;
|
||||||
try {
|
try {
|
||||||
const appId: string = yield select(getCurrentApplicationId);
|
const appId: string = yield select(getCurrentApplicationId);
|
||||||
|
|
@ -1040,10 +1044,15 @@ function* discardChanges() {
|
||||||
);
|
);
|
||||||
if (isValidResponse) {
|
if (isValidResponse) {
|
||||||
yield put(discardChangesSuccess(response.data));
|
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 =
|
const pageId: string =
|
||||||
response.data?.pages?.find((page: any) => page.isDefault)?.id || "";
|
response.data?.pages?.find((page: any) => page.isDefault)?.id || "";
|
||||||
localStorage.setItem("GIT_DISCARD_CHANGES", "success");
|
|
||||||
const branch = response.data.gitApplicationMetadata.branchName;
|
const branch = response.data.gitApplicationMetadata.branchName;
|
||||||
window.open(builderURL({ pageId, branch }), "_self");
|
window.open(builderURL({ pageId, branch }), "_self");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1053,11 +1062,9 @@ function* discardChanges() {
|
||||||
show: true,
|
show: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
localStorage.setItem("GIT_DISCARD_CHANGES", "failure");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(discardChangesFailure({ error, show: true }));
|
yield put(discardChangesFailure({ error, show: true }));
|
||||||
localStorage.setItem("GIT_DISCARD_CHANGES", "failure");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user