PromucFlow_constructor/app/client/src/git/components/ProtectedBranches/ProtectedBranchesView.tsx
Rudraprasad Das f6d7ce626f
chore: git mod - test fixes (#38357)
## 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 -->
2025-01-07 12:30:42 +01:00

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;