## Description - Locator fixes for cy tests - Missing saga for discard and merge - New component for hot keys Fixes https://github.com/appsmithorg/appsmith/issues/37821 Fixes https://github.com/appsmithorg/appsmith/issues/37822 Fixes https://github.com/appsmithorg/appsmith/issues/37824 ## 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/12649057943> > Commit: 94c57d0a2398fca8e6cbb4be573586aa98dffbe1 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12649057943&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Git` > Spec: > <hr>Tue, 07 Jan 2025 10:42:55 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 ## Release Notes - **New Features** - Added repository limit error modal to handle scenarios when repository limits are reached. - Introduced Git hot keys for quick access to Git operations. - Enhanced Git synchronization functionality with new merge and discard change capabilities. - Integrated feature flag handling in test suites for Git-related functionalities. - **Improvements** - Standardized test identifiers across Git-related components. - Refined Git modal interactions and state management. - Updated locator references for more consistent testing. - Improved user feedback with success notifications for branch deletions. - **Bug Fixes** - Resolved issues with branch switching and URL handling. - Improved error handling in Git synchronization processes. - **Testing** - Updated Cypress test suites with new feature flag interceptors. - Enhanced test coverage for Git operations and modal interactions. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
208 lines
5.8 KiB
TypeScript
208 lines
5.8 KiB
TypeScript
import {
|
|
APPSMITH_ENTERPRISE,
|
|
BRANCH_PROTECTION,
|
|
BRANCH_PROTECTION_DESC,
|
|
LEARN_MORE,
|
|
UPDATE,
|
|
createMessage,
|
|
} from "ee/constants/messages";
|
|
import { Button, Link, Option, Select, Text } from "@appsmith/ads";
|
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
import styled from "styled-components";
|
|
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
|
import { DOCS_BRANCH_PROTECTION_URL } from "constants/ThirdPartyConstants";
|
|
import { GIT_REMOTE_BRANCH_PREFIX } from "git/constants/misc";
|
|
import { useAppsmithEnterpriseUrl } from "git/hooks/useAppsmithEnterpriseUrl";
|
|
import type { FetchBranchesResponseData } from "git/requests/fetchBranchesRequest.types";
|
|
import xor from "lodash/xor";
|
|
import noop from "lodash/noop";
|
|
import type { FetchProtectedBranchesResponseData } from "git/requests/fetchProtectedBranchesRequest.types";
|
|
|
|
const Container = styled.div`
|
|
padding-top: 16px;
|
|
padding-bottom: 16px;
|
|
`;
|
|
|
|
const HeadContainer = styled.div`
|
|
margin-bottom: 16px;
|
|
`;
|
|
|
|
const BodyContainer = styled.div`
|
|
display: flex;
|
|
`;
|
|
|
|
const SectionTitle = styled(Text)`
|
|
font-weight: 600;
|
|
margin-bottom: 4px;
|
|
`;
|
|
|
|
const SectionDesc = styled(Text)`
|
|
margin-bottom: 4px;
|
|
`;
|
|
|
|
const StyledSelect = styled(Select)`
|
|
width: 300px;
|
|
margin-right: 12px;
|
|
`;
|
|
|
|
const StyledLink = styled(Link)`
|
|
display: inline-flex;
|
|
`;
|
|
|
|
interface ProtectedBranchesViewProps {
|
|
branches: FetchBranchesResponseData | null;
|
|
defaultBranch: string | null;
|
|
isProtectedBranchesLicensed: boolean;
|
|
isUpdateProtectedBranchesLoading: boolean;
|
|
protectedBranches: FetchProtectedBranchesResponseData | null;
|
|
updateProtectedBranches?: (branches: string[]) => void;
|
|
}
|
|
|
|
function ProtectedBranchesView({
|
|
branches = null,
|
|
defaultBranch = null,
|
|
isProtectedBranchesLicensed = false,
|
|
isUpdateProtectedBranchesLoading = false,
|
|
protectedBranches = null,
|
|
updateProtectedBranches = noop,
|
|
}: ProtectedBranchesViewProps) {
|
|
const [selectedValues, setSelectedValues] = useState<string[]>([]);
|
|
|
|
const filteredBranches = useMemo(() => {
|
|
const returnVal: string[] = [];
|
|
|
|
for (const branch of branches ?? []) {
|
|
if (branch.branchName === defaultBranch) {
|
|
returnVal.unshift(branch.branchName);
|
|
} else if (branch.branchName.includes(GIT_REMOTE_BRANCH_PREFIX)) {
|
|
const localBranchName = branch.branchName.replace(
|
|
GIT_REMOTE_BRANCH_PREFIX,
|
|
"",
|
|
);
|
|
|
|
if (!returnVal.includes(localBranchName)) {
|
|
returnVal.push(
|
|
branch.branchName.replace(GIT_REMOTE_BRANCH_PREFIX, ""),
|
|
);
|
|
}
|
|
} else {
|
|
returnVal.push(branch.branchName);
|
|
}
|
|
}
|
|
|
|
return returnVal;
|
|
}, [branches, defaultBranch]);
|
|
|
|
const isUpdateDisabled = useMemo(() => {
|
|
const areDifferent = xor(protectedBranches, selectedValues).length > 0;
|
|
|
|
return !areDifferent;
|
|
}, [protectedBranches, selectedValues]);
|
|
|
|
const enterprisePricingUrl = useAppsmithEnterpriseUrl(
|
|
"git_branch_protection",
|
|
);
|
|
|
|
useEffect(
|
|
function setValueOnInit() {
|
|
setSelectedValues(protectedBranches ?? []);
|
|
},
|
|
[protectedBranches],
|
|
);
|
|
|
|
const sendAnalyticsEvent = useCallback(() => {
|
|
const eventData = {
|
|
branches_added: [] as string[],
|
|
branches_removed: [] as string[],
|
|
protected_branches: selectedValues,
|
|
};
|
|
|
|
for (const val of selectedValues) {
|
|
if (!protectedBranches?.includes(val)) {
|
|
eventData.branches_added.push(val);
|
|
}
|
|
}
|
|
|
|
for (const val of protectedBranches ?? []) {
|
|
if (!selectedValues.includes(val)) {
|
|
eventData.branches_removed.push(val);
|
|
}
|
|
}
|
|
|
|
AnalyticsUtil.logEvent("GS_PROTECTED_BRANCHES_UPDATE", eventData);
|
|
}, [protectedBranches, selectedValues]);
|
|
|
|
const handleUpdate = useCallback(() => {
|
|
sendAnalyticsEvent();
|
|
updateProtectedBranches(selectedValues ?? []);
|
|
}, [selectedValues, sendAnalyticsEvent, updateProtectedBranches]);
|
|
|
|
const handleGetPopupContainer = useCallback(
|
|
(triggerNode) => triggerNode.parentNode,
|
|
[],
|
|
);
|
|
|
|
return (
|
|
<Container>
|
|
<HeadContainer>
|
|
<SectionTitle kind="heading-s" renderAs="h3">
|
|
{createMessage(BRANCH_PROTECTION)}
|
|
</SectionTitle>
|
|
<SectionDesc kind="body-m" renderAs="p">
|
|
{createMessage(BRANCH_PROTECTION_DESC)}{" "}
|
|
<StyledLink target="_blank" to={DOCS_BRANCH_PROTECTION_URL}>
|
|
{createMessage(LEARN_MORE)}
|
|
</StyledLink>
|
|
</SectionDesc>
|
|
{!isProtectedBranchesLicensed && (
|
|
<SectionDesc kind="body-m" renderAs="p">
|
|
To protect multiple branches, try{" "}
|
|
<StyledLink
|
|
kind="primary"
|
|
target="_blank"
|
|
to={enterprisePricingUrl}
|
|
>
|
|
{createMessage(APPSMITH_ENTERPRISE)}
|
|
</StyledLink>
|
|
</SectionDesc>
|
|
)}
|
|
</HeadContainer>
|
|
<BodyContainer>
|
|
<StyledSelect
|
|
data-testid="t--git-branch-protection-select"
|
|
dropdownMatchSelectWidth
|
|
getPopupContainer={handleGetPopupContainer}
|
|
isMultiSelect
|
|
maxTagTextLength={8}
|
|
onChange={setSelectedValues}
|
|
value={selectedValues}
|
|
>
|
|
{filteredBranches.map((branchName) => (
|
|
<Option
|
|
disabled={
|
|
!isProtectedBranchesLicensed && branchName !== defaultBranch
|
|
}
|
|
key={branchName}
|
|
value={branchName}
|
|
>
|
|
{branchName}
|
|
</Option>
|
|
))}
|
|
</StyledSelect>
|
|
<Button
|
|
data-testid="t--git-branch-protection-update-btn"
|
|
isDisabled={isUpdateDisabled}
|
|
isLoading={isUpdateProtectedBranchesLoading}
|
|
kind="secondary"
|
|
onClick={handleUpdate}
|
|
size="md"
|
|
>
|
|
{createMessage(UPDATE)}
|
|
</Button>
|
|
</BodyContainer>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
export default ProtectedBranchesView;
|