fix: [code-1085]: fix multiple bugs from bugbash (#786)

This commit is contained in:
Calvin Lee 2023-11-11 03:04:58 +00:00 committed by Harness
parent c537e188ac
commit f6c2826cd2
13 changed files with 110 additions and 57 deletions

View File

@ -71,6 +71,16 @@
width: 100% !important; width: 100% !important;
} }
.codeClose {
cursor: pointer !important;
padding: 2px !important;
&:hover {
background: var(--grey-100) !important;
box-shadow: var(--elevation-2) !important;
padding: 2px !important;
}
}
.targetContainer { .targetContainer {
:global(.bp3-form-group) { :global(.bp3-form-group) {
margin-bottom: unset !important; margin-bottom: unset !important;

View File

@ -21,6 +21,7 @@ export declare const bypassContainer: string
export declare const checkboxLabel: string export declare const checkboxLabel: string
export declare const checkboxText: string export declare const checkboxText: string
export declare const checkContainer: string export declare const checkContainer: string
export declare const codeClose: string
export declare const dividerContainer: string export declare const dividerContainer: string
export declare const generalContainer: string export declare const generalContainer: string
export declare const greyButton: string export declare const greyButton: string

View File

@ -15,6 +15,7 @@
*/ */
import React, { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
import cx from 'classnames' import cx from 'classnames'
import * as yup from 'yup'
import { import {
Button, Button,
ButtonVariation, ButtonVariation,
@ -36,7 +37,7 @@ import { useHistory } from 'react-router-dom'
import { useGet, useMutate } from 'restful-react' import { useGet, useMutate } from 'restful-react'
import { BranchTargetType, SettingsTab, branchTargetOptions } from 'utils/GitUtils' import { BranchTargetType, SettingsTab, branchTargetOptions } from 'utils/GitUtils'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { getErrorMessage, rulesFormInitialPayload } from 'utils/Utils' import { REGEX_VALID_REPO_NAME, getErrorMessage, rulesFormInitialPayload } from 'utils/Utils'
import type { import type {
TypesRepository, TypesRepository,
OpenapiRule, OpenapiRule,
@ -192,6 +193,10 @@ const BranchProtectionForm = (props: {
formName="branchProtectionRulesNewEditForm" formName="branchProtectionRulesNewEditForm"
initialValues={initialValues} initialValues={initialValues}
enableReinitialize enableReinitialize
validationSchema={yup.object().shape({
name: yup.string().trim().required().matches(REGEX_VALID_REPO_NAME, getString('validation.nameLogic')),
minReviewers: yup.number().typeError(getString('enterANumber'))
})}
onSubmit={async formData => { onSubmit={async formData => {
const stratArray = [ const stratArray = [
formData.squashMerge && 'squash', formData.squashMerge && 'squash',
@ -243,6 +248,9 @@ const BranchProtectionForm = (props: {
} }
} }
} }
if (!formData.limitMergeStrategies) {
delete (payload?.definition as ProtectionBranch)?.pullreq?.merge?.strategies_allowed
}
if (!formData.requireMinReviewers) { if (!formData.requireMinReviewers) {
delete (payload?.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_count delete (payload?.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_count
} }
@ -393,6 +401,7 @@ const BranchProtectionForm = (props: {
) )
formik.setFieldValue('targetList', filteredData) formik.setFieldValue('targetList', filteredData)
}} }}
className={css.codeClose}
/> />
</Container> </Container>
) )
@ -408,6 +417,8 @@ const BranchProtectionForm = (props: {
items={filteredUserOptions} items={filteredUserOptions}
onQueryChange={setSearchTerm} onQueryChange={setSearchTerm}
className={css.widthContainer} className={css.widthContainer}
value={{ label: '', value: '' }}
placeholder={getString('selectUsers')}
onChange={item => { onChange={item => {
const id = item.value?.toString().split(' ')[0] const id = item.value?.toString().split(' ')[0]
const displayName = item.label const displayName = item.label
@ -432,7 +443,10 @@ const BranchProtectionForm = (props: {
<Container padding={{ top: 'large' }}> <Container padding={{ top: 'large' }}>
<Layout.Horizontal spacing="small"> <Layout.Horizontal spacing="small">
<Button <Button
type="submit" onClick={() => {
formik.submitForm()
}}
type="button"
text={editMode ? getString('branchProtection.saveRule') : getString('branchProtection.createRule')} text={editMode ? getString('branchProtection.saveRule') : getString('branchProtection.createRule')}
variation={ButtonVariation.PRIMARY} variation={ButtonVariation.PRIMARY}
disabled={false} disabled={false}

View File

@ -27,6 +27,7 @@ const BypassList = (props: {
const filteredData = bypassList.filter(item => !(item[0] === owner[0] && item[1] === owner[1])) const filteredData = bypassList.filter(item => !(item[0] === owner[0] && item[1] === owner[1]))
setFieldValue('bypassList', filteredData) setFieldValue('bypassList', filteredData)
}} }}
className={css.codeClose}
/> />
</Layout.Horizontal> </Layout.Horizontal>
) )

View File

@ -83,6 +83,11 @@ const ProtectionRulesForm = (props: {
className={css.checkboxLabel} className={css.checkboxLabel}
label={getString('branchProtection.requireMinReviewersTitle')} label={getString('branchProtection.requireMinReviewersTitle')}
name={'requireMinReviewers'} name={'requireMinReviewers'}
onChange={e => {
if ((e.target as HTMLInputElement).checked) {
setFieldValue('minReviewers', 1)
}
}}
/> />
<Text padding={{ left: 'xlarge' }} className={css.checkboxText}> <Text padding={{ left: 'xlarge' }} className={css.checkboxText}>
{getString('branchProtection.requireMinReviewersContent')} {getString('branchProtection.requireMinReviewersContent')}
@ -141,6 +146,8 @@ const ProtectionRulesForm = (props: {
<FormInput.Select <FormInput.Select
onQueryChange={setSearchStatusTerm} onQueryChange={setSearchStatusTerm}
items={filteredStatusOptions} items={filteredStatusOptions}
value={{ label: '', value: '' }}
placeholder={getString('selectStatuses')}
onChange={item => { onChange={item => {
statusChecks?.push(item.value as string) statusChecks?.push(item.value as string)
const uniqueArr = Array.from(new Set(statusChecks)) const uniqueArr = Array.from(new Set(statusChecks))
@ -162,6 +169,7 @@ const ProtectionRulesForm = (props: {
</Text> </Text>
<FlexExpander /> <FlexExpander />
<Icon <Icon
className={css.codeClose}
name="code-close" name="code-close"
onClick={() => { onClick={() => {
const filteredData = statusChecks.filter(item => !(item === status)) const filteredData = statusChecks.filter(item => !(item === status))

View File

@ -26,7 +26,8 @@ import {
ButtonVariation, ButtonVariation,
Toggle, Toggle,
useToaster, useToaster,
FlexExpander FlexExpander,
StringSubstitute
} from '@harnessio/uicore' } from '@harnessio/uicore'
import cx from 'classnames' import cx from 'classnames'
@ -196,15 +197,19 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
<Text <Text
padding={{ top: 'medium', bottom: 'medium' }} padding={{ top: 'medium', bottom: 'medium' }}
font={{ variation: FontVariation.BODY2_SEMI }}> font={{ variation: FontVariation.BODY2_SEMI }}>
{checked ? getString('disableWebhookContent') : getString('enableWebhookContent')} <StringSubstitute
<strong>{row.original?.uid}</strong> str={checked ? getString('disableWebhookContent') : getString('enableWebhookContent')}
vars={{
name: <strong>{row.original?.uid}</strong>
}}
/>
</Text> </Text>
<Layout.Horizontal> <Layout.Horizontal>
<Button <Button
variation={ButtonVariation.PRIMARY} variation={ButtonVariation.PRIMARY}
text={getString('confirm')} text={getString('confirm')}
onClick={() => { onClick={() => {
const data = { enabled: !checked } const data = { state: checked ? 'disabled' : 'active' }
mutate(data) mutate(data)
.then(() => { .then(() => {
showSuccess(getString('branchProtection.ruleUpdated')) showSuccess(getString('branchProtection.ruleUpdated'))

View File

@ -1,5 +1,5 @@
.dialog { .dialog {
max-width: 500px; max-width: 700px;
} }
.warningIcon { .warningIcon {
@ -15,6 +15,6 @@
.ruleContainer { .ruleContainer {
border-radius: 4px !important; border-radius: 4px !important;
background: var(--primary-bg) !important; background: var(--primary-bg) !important;
width: 380px !important; width: fit-content !important;
height: 36px !important; height: 36px !important;
} }

View File

@ -229,6 +229,7 @@ export interface StringsMap {
enableWebhookContent: string enableWebhookContent: string
enableWebhookTitle: string enableWebhookTitle: string
enabled: string enabled: string
enterANumber: string
enterAddress: string enterAddress: string
enterBitbucketPlaceholder: string enterBitbucketPlaceholder: string
enterBranchPlaceholder: string enterBranchPlaceholder: string
@ -708,7 +709,9 @@ export interface StringsMap {
selectRange: string selectRange: string
selectSpace: string selectSpace: string
selectSpaceText: string selectSpaceText: string
selectStatuses: string
selectToViewMore: string selectToViewMore: string
selectUsers: string
setAsAdmin: string setAsAdmin: string
setting: string setting: string
settings: string settings: string
@ -822,7 +825,6 @@ export interface StringsMap {
viewRaw: string viewRaw: string
viewRepo: string viewRepo: string
viewed: string viewed: string
waitForApproval: string
webhook: string webhook: string
webhookAllEventsSelected: string webhookAllEventsSelected: string
webhookBranchCreated: string webhookBranchCreated: string

View File

@ -368,7 +368,7 @@ webhookPRCommentCreated: PR comment created
nameYourWebhook: Name your webhook nameYourWebhook: Name your webhook
submitReview: Submit Review submitReview: Submit Review
approve: Approve approve: Approve
requestChanges: Request changes requestChanges: Changes Requested
repoEmptyMarkdown: | repoEmptyMarkdown: |
## This repository is empty ## This repository is empty
@ -439,8 +439,8 @@ reviewers: Reviewers
refresh: Refresh refresh: Refresh
enableWebhookTitle: Enable the Webhook enableWebhookTitle: Enable the Webhook
disableWebhookTitle: Disable the Webhook disableWebhookTitle: Disable the Webhook
enableWebhookContent: Please confirm that you wanted to turn on enableWebhookContent: Please confirm that you wanted to turn on {name}
disableWebhookContent: Please confirm that you wanted to turn off disableWebhookContent: Please confirm that you wanted to turn off {name}
ascending: Ascending ascending: Ascending
descending: Descending descending: Descending
active: Active active: Active
@ -919,5 +919,7 @@ codeOwner:
waitToApprove: '{count} pending {count|1:approval,approvals} ' waitToApprove: '{count} pending {count|1:approval,approvals} '
approvalCompleted: '{count}/{total} approvals completed' approvalCompleted: '{count}/{total} approvals completed'
approved: Approved approved: Approved
waitForApproval: Wait for Approval
comingSoon: Coming soon... comingSoon: Coming soon...
enterANumber: Enter a number
selectUsers: Select Users
selectStatuses: Select Statuses

View File

@ -15,6 +15,7 @@
*/ */
.main { .main {
// --layout-spacing: var(--layout-medium) !important;
border-radius: 4px; border-radius: 4px;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
background: var(--white); background: var(--white);

View File

@ -101,7 +101,7 @@ export function CodeOwnersOverview({
return codeOwners?.evaluation_entries?.length ? ( return codeOwners?.evaluation_entries?.length ? (
<Container <Container
className={css.main} className={css.main}
margin={{ bottom: pullRequestMetadata.description ? undefined : 'large' }} margin={{ top: 'medium', bottom: pullRequestMetadata.description ? undefined : 'large' }}
style={{ '--border-color': Utils.getRealCSSColor(borderColor) } as React.CSSProperties}> style={{ '--border-color': Utils.getRealCSSColor(borderColor) } as React.CSSProperties}>
<Match expr={isExpanded}> <Match expr={isExpanded}>
<Truthy> <Truthy>
@ -269,7 +269,7 @@ const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({ data }) => {
return ( return (
<Text flex className={cx(css.approvalText, css.waitingContainer)} color={Color.ORANGE_700}> <Text flex className={cx(css.approvalText, css.waitingContainer)} color={Color.ORANGE_700}>
<Container className={css.circle}></Container> <Container className={css.circle}></Container>
{getString('waitForApproval')} {getString('pending')}
</Text> </Text>
) )
} }

View File

@ -48,7 +48,6 @@ import { useAppContext } from 'AppContext'
import { Images } from 'images' import { Images } from 'images'
import { import {
extractInfoFromRuleViolationArr, extractInfoFromRuleViolationArr,
FieldCheck,
getErrorMessage, getErrorMessage,
MergeCheckStatus, MergeCheckStatus,
permissionProps, permissionProps,
@ -62,7 +61,7 @@ import ReviewSplitButton from 'components/Changes/ReviewSplitButton/ReviewSplitB
import RuleViolationAlertModal from 'components/RuleViolationAlertModal/RuleViolationAlertModal' import RuleViolationAlertModal from 'components/RuleViolationAlertModal/RuleViolationAlertModal'
import css from './PullRequestActionsBox.module.scss' import css from './PullRequestActionsBox.module.scss'
const POLLING_INTERVAL = 20000 const POLLING_INTERVAL = 60000
export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
repoMetadata, repoMetadata,
pullRequestMetadata, pullRequestMetadata,
@ -100,11 +99,11 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
() => pullRequestMetadata.merge_check_status === MergeCheckStatus.UNCHECKED && !isClosed, () => pullRequestMetadata.merge_check_status === MergeCheckStatus.UNCHECKED && !isClosed,
[pullRequestMetadata, isClosed] [pullRequestMetadata, isClosed]
) )
useEffect(() => { useEffect(() => {
if (ruleViolationArr && !isDraft && ruleViolationArr.data.rule_violations) { if (ruleViolationArr && !isDraft && ruleViolationArr.data.rule_violations) {
const { checkIfBypassAllowed, violationArr, uniqueViolations } = extractInfoFromRuleViolationArr( const { checkIfBypassAllowed, violationArr, uniqueViolations } = extractInfoFromRuleViolationArr(
ruleViolationArr.data.rule_violations, ruleViolationArr.data.rule_violations
fieldsToCheck
) )
setNotBypassable(checkIfBypassAllowed) setNotBypassable(checkIfBypassAllowed)
setFinalRulesArr(violationArr) setFinalRulesArr(violationArr)
@ -112,25 +111,35 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ruleViolationArr]) }, [ruleViolationArr])
useEffect(() => { const dryMerge = () => {
const dryMerge = () => { if (!isClosed && pullRequestMetadata.state !== PullRequestState.MERGED) {
if (!isClosed) { mergePR({ bypass_rules: true, dry_run: true, source_sha: pullRequestMetadata?.source_sha })
mergePR({ bypass_rules: true, dry_run: true, method: 'merge', source_sha: pullRequestMetadata?.source_sha }) .then(res => {
.then(res => { if (res?.rule_violations?.length > 0) {
if (res?.rule_violations?.length > 0) { setRuleViolation(true)
setRuleViolation(true) setRuleViolationArr({ data: { rule_violations: res?.rule_violations } })
setRuleViolationArr({ data: { rule_violations: res?.rule_violations } }) setAllowedStrats(res.allowed_methods)
} else { } else {
setRuleViolation(false) setRuleViolation(false)
} setAllowedStrats(res.allowed_methods)
}) }
.catch(err => { })
.catch(err => {
if (err.status === 422) {
setRuleViolation(true) setRuleViolation(true)
setRuleViolationArr(err) setRuleViolationArr(err)
}) setAllowedStrats(err.allowed_methods)
} } else {
showError(getErrorMessage(err))
}
})
} }
dryMerge() }
useEffect(() => {
dryMerge() // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
// dryMerge()
const intervalId = setInterval(async () => { const intervalId = setInterval(async () => {
dryMerge() dryMerge()
}, POLLING_INTERVAL) // Poll every 20 seconds }, POLLING_INTERVAL) // Poll every 20 seconds
@ -165,6 +174,12 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
desc: getString('pr.mergeOptions.closeDesc') desc: getString('pr.mergeOptions.closeDesc')
} }
] ]
const [allowedStrats, setAllowedStrats] = useState<string[]>([
mergeOptions[0].method,
mergeOptions[1].method,
mergeOptions[2].method,
mergeOptions[3].method
])
const draftOptions: PRDraftOption[] = [ const draftOptions: PRDraftOption[] = [
{ {
method: 'open', method: 'open',
@ -178,25 +193,21 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
} }
] ]
const fieldsToCheck: FieldCheck = {
'pullreq.approvals.require_minimum_count': getString('branchProtection.requireMinReviewersTitle'),
'pullreq.approvals.require_code_owners': getString('branchProtection.reqReviewFromCodeOwnerTitle'),
'pullreq.approvals.require_latest_commit': getString('branchProtection.reqNewChangesTitle'),
'pullreq.comments.require_resolve_all': getString('branchProtection.reqCommentResolutionTitle'),
'pullreq.status_checks.all_must_succeed': getString('branchProtection.reqStatusChecksTitle'),
'pullreq.status_checks.require_uids': getString('branchProtection.reqStatusChecksTitle'),
'pullreq.merge.strategies_allowed': getString('branchProtection.limitMergeStrategies'),
'pullreq.merge.delete_branch': getString('branchProtection.autoDeleteTitle'),
'lifecycle.create_forbidden': getString('branchProtection.blockBranchCreation'),
'lifecycle.delete_forbidden': getString('branchProtection.blockBranchDeletion'),
'lifecycle.update_forbidden': getString('branchProtection.requirePr')
}
const [mergeOption, setMergeOption, resetMergeOption] = useUserPreference<PRMergeOption>( const [mergeOption, setMergeOption, resetMergeOption] = useUserPreference<PRMergeOption>(
UserPreference.PULL_REQUEST_MERGE_STRATEGY, UserPreference.PULL_REQUEST_MERGE_STRATEGY,
mergeOptions[0], mergeOptions[0],
option => option.method !== 'close' option => option.method !== 'close'
) )
useEffect(() => {
if (allowedStrats) {
const matchingMethods = mergeOptions.filter(option => allowedStrats.includes(option.method))
if (matchingMethods.length > 0) {
setMergeOption(matchingMethods[0])
}
} else {
setMergeOption(mergeOptions[3])
} // eslint-disable-next-line react-hooks/exhaustive-deps
}, [allowedStrats])
const [draftOption, setDraftOption] = useState<PRDraftOption>(draftOptions[0]) const [draftOption, setDraftOption] = useState<PRDraftOption>(draftOptions[0])
const permPushResult = hooks?.usePermissionTranslate?.( const permPushResult = hooks?.usePermissionTranslate?.(
{ {
@ -216,7 +227,6 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
if (pullRequestMetadata.state === PullRequestFilterOption.MERGED) { if (pullRequestMetadata.state === PullRequestFilterOption.MERGED) {
return <MergeInfo pullRequestMetadata={pullRequestMetadata} /> return <MergeInfo pullRequestMetadata={pullRequestMetadata} />
} }
return ( return (
<Container <Container
className={cx(css.main, { className={cx(css.main, {
@ -276,7 +286,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
: ruleViolation : ruleViolation
? 'branchProtection.prFailedText' ? 'branchProtection.prFailedText'
: 'pr.branchHasNoConflicts', : 'pr.branchHasNoConflicts',
ruleViolation ? { ruleCount: length } : {} ruleViolation ? { ruleCount: length } : { ruleCount: 0 }
)} )}
{ruleViolation && mergeable && !isDraft ? ( {ruleViolation && mergeable && !isDraft ? (
<Button <Button
@ -439,11 +449,12 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
} }
}}> }}>
{mergeOptions.map(option => { {mergeOptions.map(option => {
const mergeCheck = allowedStrats !== undefined && allowedStrats.includes(option.method)
return ( return (
<Menu.Item <Menu.Item
key={option.method} key={option.method}
className={css.menuItem} className={css.menuItem}
disabled={option.disabled} disabled={option.method !== 'close' ? !mergeCheck : option.disabled}
text={ text={
<> <>
<BIcon icon={mergeOption.method === option.method ? 'tick' : 'blank'} /> <BIcon icon={mergeOption.method === option.method ? 'tick' : 'blank'} />

View File

@ -76,15 +76,13 @@ export interface PageBrowserProps {
page: string page: string
} }
export const extractInfoFromRuleViolationArr = (ruleViolationArr: TypesRuleViolations[], fieldsToCheck: FieldCheck) => { export const extractInfoFromRuleViolationArr = (ruleViolationArr: TypesRuleViolations[]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const tempArray: any[] = ruleViolationArr?.flatMap( const tempArray: any[] = ruleViolationArr?.flatMap(
(item: { violations?: TypesViolation[] | null }) => item?.violations?.map(violation => violation.code) ?? [] (item: { violations?: TypesViolation[] | null }) => item?.violations?.map(violation => violation.message) ?? []
) )
const uniqueViolations = new Set(tempArray) const uniqueViolations = new Set(tempArray)
const violationArr = [...uniqueViolations] const violationArr = [...uniqueViolations].map(violation => ({ violation: violation }))
.filter(violation => violation in fieldsToCheck)
.map(violation => ({ violation: fieldsToCheck[violation] }))
const checkIfBypassAllowed = ruleViolationArr.some(ruleViolation => ruleViolation.bypassed === false) const checkIfBypassAllowed = ruleViolationArr.some(ruleViolation => ruleViolation.bypassed === false)