mirror of https://github.com/harness/drone.git
feat: [CODE-3324] add default reviewers support (#3572)
* fix: [CODE-3324] resolve comments * fix: [CODE-3324] resolve comments * fix: [CODE-3324] min reviewer req condition * fix: [CODE-3324] approval check * fix: [CODE-3324] latest rule check * fix: [CODE-3324] null check again * fix: [CODE-3324] null check * fix: [CODE-3324] update on latest changes tag * fix: [CODE-3324] pr page fix - only default rule present * fix: [CODE-3324] inital state of default reviewers * fix: [CODE-3324] swagger indentation * feat: [CODE-3324] add default reviewers supportmain
parent
1cfdf10e08
commit
8e925410fb
web/src
components/BranchProtection
framework/strings
i18n
pages/PullRequest
CodeOwners
Conversation
PullRequestOverviewPanel
DefaultReviewers
services/code
utils
|
@ -81,6 +81,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.codeCloseBtn {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
.targetContainer {
|
.targetContainer {
|
||||||
:global(.bp3-form-group) {
|
:global(.bp3-form-group) {
|
||||||
margin-bottom: unset !important;
|
margin-bottom: unset !important;
|
||||||
|
@ -187,3 +191,14 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
max-width: calc(100% - 100px) !important;
|
max-width: calc(100% - 100px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reviewerBlock {
|
||||||
|
background-color: var(--primary-1) !important;
|
||||||
|
padding: 3px 10px !important;
|
||||||
|
gap: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.defaultReviewerContainer {
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ 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 codeClose: string
|
||||||
|
export declare const codeCloseBtn: string
|
||||||
|
export declare const defaultReviewerContainer: 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
|
||||||
|
@ -35,6 +37,7 @@ export declare const minText: string
|
||||||
export declare const noData: string
|
export declare const noData: string
|
||||||
export declare const paddingTop: string
|
export declare const paddingTop: string
|
||||||
export declare const popover: string
|
export declare const popover: string
|
||||||
|
export declare const reviewerBlock: string
|
||||||
export declare const row: string
|
export declare const row: string
|
||||||
export declare const statusWidthContainer: string
|
export declare const statusWidthContainer: string
|
||||||
export declare const table: string
|
export declare const table: string
|
||||||
|
|
|
@ -35,7 +35,14 @@ import { Menu, PopoverPosition } from '@blueprintjs/core'
|
||||||
import { Icon } from '@harnessio/icons'
|
import { Icon } from '@harnessio/icons'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
import { useGet, useMutate } from 'restful-react'
|
import { useGet, useMutate } from 'restful-react'
|
||||||
import { BranchTargetType, MergeStrategy, SettingTypeMode, SettingsTab, branchTargetOptions } from 'utils/GitUtils'
|
import {
|
||||||
|
BranchTargetType,
|
||||||
|
MergeStrategy,
|
||||||
|
PrincipalUserType,
|
||||||
|
SettingTypeMode,
|
||||||
|
SettingsTab,
|
||||||
|
branchTargetOptions
|
||||||
|
} from 'utils/GitUtils'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import {
|
import {
|
||||||
LabelsPageScope,
|
LabelsPageScope,
|
||||||
|
@ -136,10 +143,19 @@ const BranchProtectionForm = (props: {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const transformUserArray = transformDataToArray(rule?.users || [])
|
|
||||||
const usersArrayCurr = transformUserArray?.map(user => `${user.id} ${user.display_name}`)
|
const usersMap = rule?.users
|
||||||
|
|
||||||
|
const bypassListUsers = rule?.definition?.bypass?.user_ids?.map(id => usersMap?.[id])
|
||||||
|
const transformBypassListArray = transformDataToArray(bypassListUsers || [])
|
||||||
|
const usersArrayCurr = transformBypassListArray?.map(user => `${user.id} ${user.display_name}`)
|
||||||
const [userArrayState, setUserArrayState] = useState<string[]>(usersArrayCurr)
|
const [userArrayState, setUserArrayState] = useState<string[]>(usersArrayCurr)
|
||||||
|
|
||||||
|
const defaultReviewersUsers = rule?.definition?.pullreq?.reviewers?.default_reviewer_ids?.map(id => usersMap?.[id])
|
||||||
|
const transformDefaultReviewersArray = transformDataToArray(defaultReviewersUsers || [])
|
||||||
|
const reviewerArrayCurr = transformDefaultReviewersArray?.map(user => `${user.id} ${user.display_name}`)
|
||||||
|
const [defaultReviewersState, setDefaultReviewersState] = useState<string[]>(reviewerArrayCurr)
|
||||||
|
|
||||||
const getUpdateChecksPath = () =>
|
const getUpdateChecksPath = () =>
|
||||||
currentRule?.scope === 0 && repoMetadata
|
currentRule?.scope === 0 && repoMetadata
|
||||||
? `/repos/${repoMetadata?.path}/+/checks/recent`
|
? `/repos/${repoMetadata?.path}/+/checks/recent`
|
||||||
|
@ -173,6 +189,20 @@ const BranchProtectionForm = (props: {
|
||||||
[principals]
|
[principals]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const userPrincipalOptions: SelectOption[] = useMemo(
|
||||||
|
() =>
|
||||||
|
principals?.reduce<SelectOption[]>((acc, principal) => {
|
||||||
|
if (principal?.type === PrincipalUserType.USER) {
|
||||||
|
acc.push({
|
||||||
|
value: `${principal.id?.toString() as string} ${principal.uid}`,
|
||||||
|
label: `${principal?.display_name} (${principal.email})`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, []) || [],
|
||||||
|
[principals]
|
||||||
|
)
|
||||||
|
|
||||||
const handleSubmit = async (operation: Promise<OpenapiRule>, successMessage: string, resetForm: () => void) => {
|
const handleSubmit = async (operation: Promise<OpenapiRule>, successMessage: string, resetForm: () => void) => {
|
||||||
try {
|
try {
|
||||||
await operation
|
await operation
|
||||||
|
@ -204,7 +234,10 @@ const BranchProtectionForm = (props: {
|
||||||
const initialValues = useMemo((): RulesFormPayload => {
|
const initialValues = useMemo((): RulesFormPayload => {
|
||||||
if (editMode && rule) {
|
if (editMode && rule) {
|
||||||
const minReviewerCheck =
|
const minReviewerCheck =
|
||||||
((rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_count as number) > 0 ? true : false
|
((rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_count as number) > 0
|
||||||
|
const minDefaultReviewerCheck =
|
||||||
|
((rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_default_reviewer_count as number) >
|
||||||
|
0
|
||||||
const isMergePresent = (rule.definition as ProtectionBranch)?.pullreq?.merge?.strategies_allowed?.includes(
|
const isMergePresent = (rule.definition as ProtectionBranch)?.pullreq?.merge?.strategies_allowed?.includes(
|
||||||
MergeStrategy.MERGE
|
MergeStrategy.MERGE
|
||||||
)
|
)
|
||||||
|
@ -224,12 +257,19 @@ const BranchProtectionForm = (props: {
|
||||||
const includeArr = includeList?.map((arr: string) => ['include', arr])
|
const includeArr = includeList?.map((arr: string) => ['include', arr])
|
||||||
const excludeArr = excludeList?.map((arr: string) => ['exclude', arr])
|
const excludeArr = excludeList?.map((arr: string) => ['exclude', arr])
|
||||||
const finalArray = [...includeArr, ...excludeArr]
|
const finalArray = [...includeArr, ...excludeArr]
|
||||||
const usersArray = transformDataToArray(rule.users)
|
const usersArray = transformDataToArray(bypassListUsers || [])
|
||||||
|
|
||||||
const bypassList =
|
const bypassList =
|
||||||
userArrayState.length > 0
|
userArrayState.length > 0
|
||||||
? userArrayState
|
? userArrayState
|
||||||
: usersArray?.map(user => `${user.id} ${user.display_name} (${user.email})`)
|
: usersArray?.map(user => `${user.id} ${user.display_name} (${user.email})`)
|
||||||
|
|
||||||
|
const reviewersArray = transformDataToArray(defaultReviewersUsers || [])
|
||||||
|
const defaultReviewersList =
|
||||||
|
defaultReviewersState.length > 0
|
||||||
|
? defaultReviewersState
|
||||||
|
: reviewersArray?.map(user => `${user.id} ${user.display_name} (${user.email})`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: rule?.identifier,
|
name: rule?.identifier,
|
||||||
desc: rule.description,
|
desc: rule.description,
|
||||||
|
@ -239,10 +279,16 @@ const BranchProtectionForm = (props: {
|
||||||
targetList: finalArray,
|
targetList: finalArray,
|
||||||
allRepoOwners: (rule.definition as ProtectionBranch)?.bypass?.repo_owners,
|
allRepoOwners: (rule.definition as ProtectionBranch)?.bypass?.repo_owners,
|
||||||
bypassList: bypassList,
|
bypassList: bypassList,
|
||||||
|
defaultReviewersEnabled: (rule.definition as any)?.pullreq?.reviewers?.default_reviewer_ids?.length > 0,
|
||||||
|
defaultReviewersList: defaultReviewersList,
|
||||||
requireMinReviewers: minReviewerCheck,
|
requireMinReviewers: minReviewerCheck,
|
||||||
|
requireMinDefaultReviewers: minDefaultReviewerCheck,
|
||||||
minReviewers: minReviewerCheck
|
minReviewers: minReviewerCheck
|
||||||
? (rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_count
|
? (rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_count
|
||||||
: '',
|
: '',
|
||||||
|
minDefaultReviewers: minDefaultReviewerCheck
|
||||||
|
? (rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_default_reviewer_count
|
||||||
|
: '',
|
||||||
autoAddCodeOwner: (rule.definition as ProtectionBranch)?.pullreq?.reviewers?.request_code_owners,
|
autoAddCodeOwner: (rule.definition as ProtectionBranch)?.pullreq?.reviewers?.request_code_owners,
|
||||||
requireCodeOwner: (rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_code_owners,
|
requireCodeOwner: (rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_code_owners,
|
||||||
requireNewChanges: (rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_latest_commit,
|
requireNewChanges: (rule.definition as ProtectionBranch)?.pullreq?.approvals?.require_latest_commit,
|
||||||
|
@ -269,7 +315,8 @@ const BranchProtectionForm = (props: {
|
||||||
(rule.definition as ProtectionBranch)?.lifecycle?.update_forbidden &&
|
(rule.definition as ProtectionBranch)?.lifecycle?.update_forbidden &&
|
||||||
!(rule.definition as ProtectionBranch)?.pullreq?.merge?.block,
|
!(rule.definition as ProtectionBranch)?.pullreq?.merge?.block,
|
||||||
targetSet: false,
|
targetSet: false,
|
||||||
bypassSet: false
|
bypassSet: false,
|
||||||
|
defaultReviewersSet: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +327,14 @@ const BranchProtectionForm = (props: {
|
||||||
getEditPermissionRequestFromScope(space, currentRule?.scope ?? 0, repoMetadata),
|
getEditPermissionRequestFromScope(space, currentRule?.scope ?? 0, repoMetadata),
|
||||||
[space, currentRule?.scope, repoMetadata]
|
[space, currentRule?.scope, repoMetadata]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultReviewerProps = {
|
||||||
|
setSearchTerm,
|
||||||
|
userPrincipalOptions,
|
||||||
|
settingSectionMode,
|
||||||
|
setDefaultReviewersState
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik<RulesFormPayload>
|
<Formik<RulesFormPayload>
|
||||||
formName="branchProtectionRulesNewEditForm"
|
formName="branchProtectionRulesNewEditForm"
|
||||||
|
@ -287,7 +342,34 @@ const BranchProtectionForm = (props: {
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
validationSchema={yup.object().shape({
|
validationSchema={yup.object().shape({
|
||||||
name: yup.string().trim().required().matches(REGEX_VALID_REPO_NAME, getString('validation.nameLogic')),
|
name: yup.string().trim().required().matches(REGEX_VALID_REPO_NAME, getString('validation.nameLogic')),
|
||||||
minReviewers: yup.number().typeError(getString('enterANumber'))
|
minReviewers: yup.number().typeError(getString('enterANumber')),
|
||||||
|
minDefaultReviewers: yup.number().typeError(getString('enterANumber')),
|
||||||
|
defaultReviewersList: yup
|
||||||
|
.array()
|
||||||
|
.of(yup.string())
|
||||||
|
.test(
|
||||||
|
'min-reviewers', // Name of the test
|
||||||
|
getString('branchProtection.atLeastMinReviewer', { count: 1 }),
|
||||||
|
function (defaultReviewersList) {
|
||||||
|
const { minDefaultReviewers, requireMinDefaultReviewers, defaultReviewersEnabled } = this.parent
|
||||||
|
const minReviewers = Number(minDefaultReviewers) || 0
|
||||||
|
if (defaultReviewersEnabled && requireMinDefaultReviewers) {
|
||||||
|
const isValid = defaultReviewersList && defaultReviewersList.length >= minReviewers
|
||||||
|
|
||||||
|
return (
|
||||||
|
isValid ||
|
||||||
|
this.createError({
|
||||||
|
message:
|
||||||
|
minReviewers > 1
|
||||||
|
? getString('branchProtection.atLeastMinReviewers', { count: minReviewers })
|
||||||
|
: getString('branchProtection.atLeastMinReviewer', { count: minReviewers })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
})}
|
})}
|
||||||
onSubmit={async (formData, { resetForm }) => {
|
onSubmit={async (formData, { resetForm }) => {
|
||||||
const stratArray = [
|
const stratArray = [
|
||||||
|
@ -302,6 +384,7 @@ const BranchProtectionForm = (props: {
|
||||||
formData?.targetList?.filter(([type]) => type === 'exclude').map(([, value]) => value) ?? []
|
formData?.targetList?.filter(([type]) => type === 'exclude').map(([, value]) => value) ?? []
|
||||||
|
|
||||||
const bypassList = formData?.bypassList?.map(item => parseInt(item.split(' ')[0]))
|
const bypassList = formData?.bypassList?.map(item => parseInt(item.split(' ')[0]))
|
||||||
|
const defaultReviewersList = formData?.defaultReviewersList?.map(item => parseInt(item.split(' ')[0]))
|
||||||
const payload: OpenapiRule = {
|
const payload: OpenapiRule = {
|
||||||
identifier: formData.name,
|
identifier: formData.name,
|
||||||
type: 'branch',
|
type: 'branch',
|
||||||
|
@ -321,11 +404,13 @@ const BranchProtectionForm = (props: {
|
||||||
approvals: {
|
approvals: {
|
||||||
require_code_owners: formData.requireCodeOwner,
|
require_code_owners: formData.requireCodeOwner,
|
||||||
require_minimum_count: parseInt(formData.minReviewers as string),
|
require_minimum_count: parseInt(formData.minReviewers as string),
|
||||||
|
require_minimum_default_reviewer_count: parseInt(formData.minDefaultReviewers as string),
|
||||||
require_latest_commit: formData.requireNewChanges,
|
require_latest_commit: formData.requireNewChanges,
|
||||||
require_no_change_request: formData.reqResOfChanges
|
require_no_change_request: formData.reqResOfChanges
|
||||||
},
|
},
|
||||||
reviewers: {
|
reviewers: {
|
||||||
request_code_owners: formData.autoAddCodeOwner
|
request_code_owners: formData.autoAddCodeOwner,
|
||||||
|
default_reviewer_ids: defaultReviewersList
|
||||||
},
|
},
|
||||||
comments: {
|
comments: {
|
||||||
require_resolve_all: formData.requireCommentResolution
|
require_resolve_all: formData.requireCommentResolution
|
||||||
|
@ -356,6 +441,9 @@ const BranchProtectionForm = (props: {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
if (!formData.requireMinDefaultReviewers) {
|
||||||
|
delete (payload?.definition as ProtectionBranch)?.pullreq?.approvals?.require_minimum_default_reviewer_count
|
||||||
|
}
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
handleSubmit(updateRule(payload), getString('branchProtection.ruleUpdated'), resetForm)
|
handleSubmit(updateRule(payload), getString('branchProtection.ruleUpdated'), resetForm)
|
||||||
} else {
|
} else {
|
||||||
|
@ -549,6 +637,7 @@ const BranchProtectionForm = (props: {
|
||||||
statusChecks={statusChecks}
|
statusChecks={statusChecks}
|
||||||
limitMergeStrats={limitMergeStrats}
|
limitMergeStrats={limitMergeStrats}
|
||||||
setSearchStatusTerm={setSearchStatusTerm}
|
setSearchStatusTerm={setSearchStatusTerm}
|
||||||
|
defaultReviewerProps={defaultReviewerProps}
|
||||||
/>
|
/>
|
||||||
<Container padding={{ top: 'large' }}>
|
<Container padding={{ top: 'large' }}>
|
||||||
<Layout.Horizontal spacing="small">
|
<Layout.Horizontal spacing="small">
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import { Icon } from '@harnessio/icons'
|
||||||
|
import { Container, FlexExpander, Layout, Text } from '@harnessio/uicore'
|
||||||
|
import { Classes, Popover, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
|
||||||
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
|
import css from '../BranchProtectionForm.module.scss'
|
||||||
|
|
||||||
|
const DefaultReviewersList = (props: {
|
||||||
|
defaultReviewersList?: string[] // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
setFieldValue: (field: string, value: any, shouldValidate?: boolean | undefined) => void
|
||||||
|
}) => {
|
||||||
|
const { defaultReviewersList, setFieldValue } = props
|
||||||
|
const defaultReviewerContent = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal className={cx(css.widthContainer, css.defaultReviewerContainer)} padding={{ bottom: 'large' }}>
|
||||||
|
{defaultReviewersList?.map((owner: string, idx: number) => {
|
||||||
|
const str = owner.slice(owner.indexOf(' ') + 1)
|
||||||
|
const name = str.split(' (')[0]
|
||||||
|
const email = str.split(' (')[1].replace(')', '')
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
key={`${name}-${idx}`}
|
||||||
|
interactionKind={PopoverInteractionKind.HOVER}
|
||||||
|
position={PopoverPosition.TOP_LEFT}
|
||||||
|
popoverClassName={Classes.DARK}
|
||||||
|
content={
|
||||||
|
<Container padding="medium">
|
||||||
|
<Text font={{ variation: FontVariation.FORM_HELP }} color={Color.WHITE}>
|
||||||
|
{email}
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
}>
|
||||||
|
<Layout.Horizontal key={`${name}-${idx}`} flex={{ align: 'center-center' }} className={css.reviewerBlock}>
|
||||||
|
<Text padding={{ top: 'tiny' }} lineClamp={1}>
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
<FlexExpander />
|
||||||
|
<Icon
|
||||||
|
name="code-close"
|
||||||
|
onClick={() => {
|
||||||
|
const filteredData = defaultReviewersList.filter(item => !(item === owner))
|
||||||
|
setFieldValue('defaultReviewersList', filteredData)
|
||||||
|
}}
|
||||||
|
className={css.codeCloseBtn}
|
||||||
|
/>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}, [defaultReviewersList, setFieldValue])
|
||||||
|
return <>{defaultReviewerContent}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DefaultReviewersList
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import React from 'react'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import { Container, FormInput, SelectOption, Text } from '@harnessio/uicore'
|
||||||
|
import { Color } from '@harnessio/design-system'
|
||||||
|
import type { FormikProps } from 'formik'
|
||||||
|
import { Render } from 'react-jsx-match'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import type { RulesFormPayload } from 'utils/Utils'
|
||||||
|
import { SettingTypeMode } from 'utils/GitUtils'
|
||||||
|
import DefaultReviewersList from './DefaultReviewersList'
|
||||||
|
import css from '../BranchProtectionForm.module.scss'
|
||||||
|
|
||||||
|
const DefaultReviewersSection = (props: {
|
||||||
|
formik: FormikProps<RulesFormPayload>
|
||||||
|
defaultReviewerProps: {
|
||||||
|
setSearchTerm: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
userPrincipalOptions: SelectOption[]
|
||||||
|
settingSectionMode: SettingTypeMode
|
||||||
|
setDefaultReviewersState: React.Dispatch<React.SetStateAction<string[]>>
|
||||||
|
}
|
||||||
|
}) => {
|
||||||
|
const { formik, defaultReviewerProps } = props
|
||||||
|
const { settingSectionMode, userPrincipalOptions, setSearchTerm, setDefaultReviewersState } = defaultReviewerProps
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const setFieldValue = formik.setFieldValue
|
||||||
|
|
||||||
|
const defaultReviewersList =
|
||||||
|
settingSectionMode === SettingTypeMode.EDIT || formik.values.defaultReviewersSet
|
||||||
|
? formik.values.defaultReviewersList
|
||||||
|
: []
|
||||||
|
const minDefaultReviewers = formik.values.requireMinDefaultReviewers
|
||||||
|
const defaultReviewersEnabled = formik.values.defaultReviewersEnabled
|
||||||
|
const filteredPrincipalOptions = userPrincipalOptions.filter(
|
||||||
|
(item: SelectOption) => !defaultReviewersList?.includes(item.value as string)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormInput.CheckBox
|
||||||
|
className={css.checkboxLabel}
|
||||||
|
label={getString('branchProtection.enableDefaultReviewersTitle')}
|
||||||
|
name={'defaultReviewersEnabled'}
|
||||||
|
onChange={e => {
|
||||||
|
if (!(e.target as HTMLInputElement).checked) {
|
||||||
|
setFieldValue('requireMinDefaultReviewers', false)
|
||||||
|
formik.setFieldValue('defaultReviewersList', [])
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text padding={{ left: 'xlarge' }} className={css.checkboxText}>
|
||||||
|
{getString('branchProtection.enableDefaultReviewersText')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Render when={defaultReviewersEnabled}>
|
||||||
|
<Container padding={{ top: 'xlarge', left: 'xlarge' }}>
|
||||||
|
<FormInput.Select
|
||||||
|
items={filteredPrincipalOptions}
|
||||||
|
onQueryChange={setSearchTerm}
|
||||||
|
className={css.widthContainer}
|
||||||
|
value={{ label: '', value: '' }}
|
||||||
|
placeholder={getString('selectReviewers')}
|
||||||
|
onChange={item => {
|
||||||
|
const id = item.value?.toString().split(' ')[0]
|
||||||
|
const displayName = item.label
|
||||||
|
const defaultReviewerEntry = `${id} ${displayName}`
|
||||||
|
defaultReviewersList?.push(defaultReviewerEntry)
|
||||||
|
const uniqueArr = Array.from(new Set(defaultReviewersList))
|
||||||
|
formik.setFieldValue('defaultReviewersList', uniqueArr)
|
||||||
|
formik.setFieldValue('defaultReviewersSet', true)
|
||||||
|
setDefaultReviewersState([...uniqueArr])
|
||||||
|
}}
|
||||||
|
name={'defaultReviewerSelect'}></FormInput.Select>
|
||||||
|
{formik.errors.defaultReviewersList && (
|
||||||
|
<Text color={Color.RED_350} padding={{ bottom: 'medium' }}>
|
||||||
|
{formik.errors.defaultReviewersList}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<DefaultReviewersList defaultReviewersList={defaultReviewersList} setFieldValue={formik.setFieldValue} />
|
||||||
|
|
||||||
|
<FormInput.CheckBox
|
||||||
|
className={css.checkboxLabel}
|
||||||
|
label={getString('branchProtection.requireMinDefaultReviewersTitle')}
|
||||||
|
name={'requireMinDefaultReviewers'}
|
||||||
|
onChange={e => {
|
||||||
|
if ((e.target as HTMLInputElement).checked) {
|
||||||
|
setFieldValue('minDefaultReviewers', 1)
|
||||||
|
setFieldValue('defaultReviewersEnabled', true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text padding={{ left: 'xlarge' }} className={css.checkboxText}>
|
||||||
|
{getString('branchProtection.requireMinDefaultReviewersContent')}
|
||||||
|
</Text>
|
||||||
|
{minDefaultReviewers && (
|
||||||
|
<Container padding={{ left: 'xlarge', top: 'medium' }}>
|
||||||
|
<FormInput.Text
|
||||||
|
className={cx(css.widthContainer, css.minText)}
|
||||||
|
name={'minDefaultReviewers'}
|
||||||
|
placeholder={getString('branchProtection.minNumberPlaceholder')}
|
||||||
|
label={getString('branchProtection.minNumber')}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</Render>
|
||||||
|
<hr className={css.dividerContainer} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DefaultReviewersSection
|
|
@ -22,6 +22,8 @@ import type { FormikProps } from 'formik'
|
||||||
import { Classes, Popover, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
|
import { Classes, Popover, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import type { RulesFormPayload } from 'utils/Utils'
|
import type { RulesFormPayload } from 'utils/Utils'
|
||||||
|
import type { SettingTypeMode } from 'utils/GitUtils'
|
||||||
|
import DefaultReviewersSection from './DefaultReviewersSection'
|
||||||
import css from '../BranchProtectionForm.module.scss'
|
import css from '../BranchProtectionForm.module.scss'
|
||||||
|
|
||||||
const ProtectionRulesForm = (props: {
|
const ProtectionRulesForm = (props: {
|
||||||
|
@ -29,9 +31,15 @@ const ProtectionRulesForm = (props: {
|
||||||
minReviewers: boolean
|
minReviewers: boolean
|
||||||
statusOptions: SelectOption[]
|
statusOptions: SelectOption[]
|
||||||
statusChecks: string[]
|
statusChecks: string[]
|
||||||
limitMergeStrats: boolean // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
limitMergeStrats: boolean
|
||||||
setSearchStatusTerm: React.Dispatch<React.SetStateAction<string>>
|
setSearchStatusTerm: React.Dispatch<React.SetStateAction<string>>
|
||||||
formik: FormikProps<RulesFormPayload>
|
formik: FormikProps<RulesFormPayload>
|
||||||
|
defaultReviewerProps: {
|
||||||
|
setSearchTerm: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
userPrincipalOptions: SelectOption[]
|
||||||
|
settingSectionMode: SettingTypeMode
|
||||||
|
setDefaultReviewersState: React.Dispatch<React.SetStateAction<string[]>>
|
||||||
|
}
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
statusChecks,
|
statusChecks,
|
||||||
|
@ -40,7 +48,8 @@ const ProtectionRulesForm = (props: {
|
||||||
requireStatusChecks,
|
requireStatusChecks,
|
||||||
statusOptions,
|
statusOptions,
|
||||||
limitMergeStrats,
|
limitMergeStrats,
|
||||||
formik
|
formik,
|
||||||
|
defaultReviewerProps
|
||||||
} = props
|
} = props
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const setFieldValue = formik.setFieldValue
|
const setFieldValue = formik.setFieldValue
|
||||||
|
@ -143,6 +152,7 @@ const ProtectionRulesForm = (props: {
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<hr className={css.dividerContainer} />
|
<hr className={css.dividerContainer} />
|
||||||
|
<DefaultReviewersSection formik={formik} defaultReviewerProps={defaultReviewerProps} />
|
||||||
<FormInput.CheckBox
|
<FormInput.CheckBox
|
||||||
className={css.checkboxLabel}
|
className={css.checkboxLabel}
|
||||||
label={getString('branchProtection.requireMinReviewersTitle')}
|
label={getString('branchProtection.requireMinReviewersTitle')}
|
||||||
|
|
|
@ -219,6 +219,18 @@ const BranchProtectionListing = (props: {
|
||||||
requiredRule: {
|
requiredRule: {
|
||||||
[RuleFields.AUTO_ADD_CODE_OWNERS]: true
|
[RuleFields.AUTO_ADD_CODE_OWNERS]: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
requireMinDefaultReviewersTitle: {
|
||||||
|
title: getString('branchProtection.requireMinDefaultReviewersTitle'),
|
||||||
|
requiredRule: {
|
||||||
|
[RuleFields.APPROVALS_REQUIRE_MINIMUM_DEFAULT_REVIEWERS]: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultReviewersAdded: {
|
||||||
|
title: getString('branchProtection.enableDefaultReviewersTitle'),
|
||||||
|
requiredRule: {
|
||||||
|
[RuleFields.DEFAULT_REVIEWERS_ADDED]: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,8 @@ export interface StringsMap {
|
||||||
'branchProtection.addCodeownersToReviewText': string
|
'branchProtection.addCodeownersToReviewText': string
|
||||||
'branchProtection.addCodeownersToReviewTitle': string
|
'branchProtection.addCodeownersToReviewTitle': string
|
||||||
'branchProtection.allRepoOwners': string
|
'branchProtection.allRepoOwners': string
|
||||||
|
'branchProtection.atLeastMinReviewer': string
|
||||||
|
'branchProtection.atLeastMinReviewers': string
|
||||||
'branchProtection.autoDeleteText': string
|
'branchProtection.autoDeleteText': string
|
||||||
'branchProtection.autoDeleteTitle': string
|
'branchProtection.autoDeleteTitle': string
|
||||||
'branchProtection.blockBranchCreation': string
|
'branchProtection.blockBranchCreation': string
|
||||||
|
@ -102,6 +104,8 @@ export interface StringsMap {
|
||||||
'branchProtection.disableTheRule': string
|
'branchProtection.disableTheRule': string
|
||||||
'branchProtection.edit': string
|
'branchProtection.edit': string
|
||||||
'branchProtection.editRule': string
|
'branchProtection.editRule': string
|
||||||
|
'branchProtection.enableDefaultReviewersText': string
|
||||||
|
'branchProtection.enableDefaultReviewersTitle': string
|
||||||
'branchProtection.enableTheRule': string
|
'branchProtection.enableTheRule': string
|
||||||
'branchProtection.limitMergeStrategies': string
|
'branchProtection.limitMergeStrategies': string
|
||||||
'branchProtection.limitMergeStrategiesText': string
|
'branchProtection.limitMergeStrategiesText': string
|
||||||
|
@ -124,6 +128,8 @@ export interface StringsMap {
|
||||||
'branchProtection.reqReviewFromCodeOwnerTitle': string
|
'branchProtection.reqReviewFromCodeOwnerTitle': string
|
||||||
'branchProtection.reqStatusChecksText': string
|
'branchProtection.reqStatusChecksText': string
|
||||||
'branchProtection.reqStatusChecksTitle': string
|
'branchProtection.reqStatusChecksTitle': string
|
||||||
|
'branchProtection.requireMinDefaultReviewersContent': string
|
||||||
|
'branchProtection.requireMinDefaultReviewersTitle': string
|
||||||
'branchProtection.requireMinReviewersContent': string
|
'branchProtection.requireMinReviewersContent': string
|
||||||
'branchProtection.requireMinReviewersTitle': string
|
'branchProtection.requireMinReviewersTitle': string
|
||||||
'branchProtection.requirePr': string
|
'branchProtection.requirePr': string
|
||||||
|
@ -170,24 +176,31 @@ export interface StringsMap {
|
||||||
'changesSection.changesApproved': string
|
'changesSection.changesApproved': string
|
||||||
'changesSection.changesApprovedByXReviewers': string
|
'changesSection.changesApprovedByXReviewers': string
|
||||||
'changesSection.changesWereAppByCodeOwner': string
|
'changesSection.changesWereAppByCodeOwner': string
|
||||||
|
'changesSection.changesWereAppByDefaultReviewers': string
|
||||||
'changesSection.changesWereAppByLatestReqRev': string
|
'changesSection.changesWereAppByLatestReqRev': string
|
||||||
'changesSection.codeOwnerReqChanges': string
|
'changesSection.codeOwnerReqChanges': string
|
||||||
'changesSection.codeOwnerReqChangesToPr': string
|
'changesSection.codeOwnerReqChangesToPr': string
|
||||||
|
'changesSection.defaultReviewersChangesToPr': string
|
||||||
|
'changesSection.defaultReviewersStatus': string
|
||||||
'changesSection.latestChangesApprovedByXReviewers': string
|
'changesSection.latestChangesApprovedByXReviewers': string
|
||||||
'changesSection.latestChangesPendingReqRev': string
|
'changesSection.latestChangesPendingReqRev': string
|
||||||
'changesSection.latestChangesWereAppByCodeOwner': string
|
'changesSection.latestChangesWereAppByCodeOwner': string
|
||||||
|
'changesSection.latestChangesWereAppByDefaultReviewers': string
|
||||||
'changesSection.latestChangesWereApprovedByReq': string
|
'changesSection.latestChangesWereApprovedByReq': string
|
||||||
'changesSection.noCodeOwnerReviewsReq': string
|
'changesSection.noCodeOwnerReviewsReq': string
|
||||||
'changesSection.noReviewsReq': string
|
'changesSection.noReviewsReq': string
|
||||||
'changesSection.pendingAppFromCodeOwners': string
|
'changesSection.pendingAppFromCodeOwners': string
|
||||||
'changesSection.pendingLatestApprovalCodeOwners': string
|
'changesSection.pendingLatestApprovalCodeOwners': string
|
||||||
|
'changesSection.pendingLatestApprovalDefaultReviewers': string
|
||||||
'changesSection.prMergeBlockedMessage': string
|
'changesSection.prMergeBlockedMessage': string
|
||||||
'changesSection.prMergeBlockedTitle': string
|
'changesSection.prMergeBlockedTitle': string
|
||||||
'changesSection.pullReqWithoutAnyReviews': string
|
'changesSection.pullReqWithoutAnyReviews': string
|
||||||
'changesSection.reqChangeFromCodeOwners': string
|
'changesSection.reqChangeFromCodeOwners': string
|
||||||
'changesSection.someChangesWereAppByCodeOwner': string
|
'changesSection.someChangesWereAppByCodeOwner': string
|
||||||
'changesSection.waitingOnCodeOwner': string
|
'changesSection.waitingOnCodeOwner': string
|
||||||
|
'changesSection.waitingOnDefaultReviewers': string
|
||||||
'changesSection.waitingOnLatestCodeOwner': string
|
'changesSection.waitingOnLatestCodeOwner': string
|
||||||
|
'changesSection.waitingOnLatestDefaultReviewers': string
|
||||||
'changesSection.waitingOnReviewers': string
|
'changesSection.waitingOnReviewers': string
|
||||||
'changesSection.xApprovalsArePending': string
|
'changesSection.xApprovalsArePending': string
|
||||||
characterLimit: string
|
characterLimit: string
|
||||||
|
@ -309,6 +322,7 @@ export interface StringsMap {
|
||||||
dangerDeleteProject: string
|
dangerDeleteProject: string
|
||||||
defaultBranch: string
|
defaultBranch: string
|
||||||
defaultBranchTitle: string
|
defaultBranchTitle: string
|
||||||
|
defaultReviewers: string
|
||||||
delete: string
|
delete: string
|
||||||
deleteBranch: string
|
deleteBranch: string
|
||||||
deleteBranchConfirm: string
|
deleteBranchConfirm: string
|
||||||
|
@ -673,6 +687,7 @@ export interface StringsMap {
|
||||||
ok: string
|
ok: string
|
||||||
on: string
|
on: string
|
||||||
onDate: string
|
onDate: string
|
||||||
|
onLatestChanges: string
|
||||||
oneMustBeSelected: string
|
oneMustBeSelected: string
|
||||||
open: string
|
open: string
|
||||||
optional: string
|
optional: string
|
||||||
|
@ -1036,6 +1051,7 @@ export interface StringsMap {
|
||||||
selectMergeStrat: string
|
selectMergeStrat: string
|
||||||
selectRange: string
|
selectRange: string
|
||||||
selectRepositoryPlaceholder: string
|
selectRepositoryPlaceholder: string
|
||||||
|
selectReviewers: string
|
||||||
selectSpace: string
|
selectSpace: string
|
||||||
selectSpaceText: string
|
selectSpaceText: string
|
||||||
selectStatuses: string
|
selectStatuses: string
|
||||||
|
|
|
@ -1079,6 +1079,12 @@ branchProtection:
|
||||||
commitDirectlyBlockText: Some rules don't allow you to commit directly
|
commitDirectlyBlockText: Some rules don't allow you to commit directly
|
||||||
commitDirectlyAlertText: Some rules will be bypassed to commit directly
|
commitDirectlyAlertText: Some rules will be bypassed to commit directly
|
||||||
commitDirectlyAlertBtn: Bypass rules and commit directly
|
commitDirectlyAlertBtn: Bypass rules and commit directly
|
||||||
|
enableDefaultReviewersTitle: Enable default reviewers
|
||||||
|
enableDefaultReviewersText: Require approval from default reviewer for each pull request
|
||||||
|
requireMinDefaultReviewersTitle: Require a minimum number of default reviewers
|
||||||
|
requireMinDefaultReviewersContent: Require approval on pull requests from a minimum number of default reviewers
|
||||||
|
atLeastMinReviewers: 'Select at least {{count}} default reviewers'
|
||||||
|
atLeastMinReviewer: 'Select at least {{count}} default reviewer'
|
||||||
codeOwner:
|
codeOwner:
|
||||||
title: Code Owner
|
title: Code Owner
|
||||||
changesRequested: '{count} {count|1:change,changes} requested'
|
changesRequested: '{count} {count|1:change,changes} requested'
|
||||||
|
@ -1089,6 +1095,7 @@ approved: Approved
|
||||||
comingSoon: Coming soon...
|
comingSoon: Coming soon...
|
||||||
enterANumber: Enter a number
|
enterANumber: Enter a number
|
||||||
selectUsers: Select Users
|
selectUsers: Select Users
|
||||||
|
selectReviewers: Select Reviewers
|
||||||
selectUsersAndServiceAcc: Select Users and Service Accounts
|
selectUsersAndServiceAcc: Select Users and Service Accounts
|
||||||
selectStatuses: Select Statuses
|
selectStatuses: Select Statuses
|
||||||
featureRoadmap: Feature Roadmap
|
featureRoadmap: Feature Roadmap
|
||||||
|
@ -1150,8 +1157,11 @@ changesSection:
|
||||||
pendingAppFromCodeOwners: Approvals pending from code owners
|
pendingAppFromCodeOwners: Approvals pending from code owners
|
||||||
pendingLatestApprovalCodeOwners: Latest changes are pending approval from code owners
|
pendingLatestApprovalCodeOwners: Latest changes are pending approval from code owners
|
||||||
waitingOnCodeOwner: Changes are pending approval from code owners
|
waitingOnCodeOwner: Changes are pending approval from code owners
|
||||||
|
pendingLatestApprovalDefaultReviewers: Latest changes are pending approval from default reviewers
|
||||||
|
waitingOnDefaultReviewers: Changes are pending approval from default reviewers
|
||||||
waitingOnReviewers: Changes are pending approval from required reviewers
|
waitingOnReviewers: Changes are pending approval from required reviewers
|
||||||
waitingOnLatestCodeOwner: Waiting on code owner reviews of latest changes
|
waitingOnLatestCodeOwner: Waiting on code owner reviews of latest changes
|
||||||
|
waitingOnLatestDefaultReviewers: Waiting on default reviewer's reviews of latest changes
|
||||||
approvalPending: Approvals pending
|
approvalPending: Approvals pending
|
||||||
changesApproved: Changes approved
|
changesApproved: Changes approved
|
||||||
noReviewsReq: No reviews required
|
noReviewsReq: No reviews required
|
||||||
|
@ -1161,13 +1171,17 @@ changesSection:
|
||||||
changesApprovedByXReviewers: Changes were approved by {length} {length|1:reviewer,reviewers}
|
changesApprovedByXReviewers: Changes were approved by {length} {length|1:reviewer,reviewers}
|
||||||
latestChangesApprovedByXReviewers: Latest changes were approved by {length} {length|1:reviewer,reviewers}
|
latestChangesApprovedByXReviewers: Latest changes were approved by {length} {length|1:reviewer,reviewers}
|
||||||
latestChangesWereAppByCodeOwner: Latest changes were approved by code owners
|
latestChangesWereAppByCodeOwner: Latest changes were approved by code owners
|
||||||
|
latestChangesWereAppByDefaultReviewers: Latest changes were approved by default reviewers
|
||||||
latestChangesPendingReqRev: Latest changes are pending approval from required reviewers
|
latestChangesPendingReqRev: Latest changes are pending approval from required reviewers
|
||||||
changesWereAppByCodeOwner: Changes were approved by code owners
|
changesWereAppByCodeOwner: Changes were approved by code owners
|
||||||
|
changesWereAppByDefaultReviewers: Changes were approved by default reviewers
|
||||||
|
defaultReviewersStatus: Default reviewers were added to the PR
|
||||||
changesWereAppByLatestReqRev: Changes were approved by required reviewers
|
changesWereAppByLatestReqRev: Changes were approved by required reviewers
|
||||||
latestChangesWereApprovedByReq: Latest changes were approved by required reviewers
|
latestChangesWereApprovedByReq: Latest changes were approved by required reviewers
|
||||||
someChangesWereAppByCodeOwner: Some changes were approved by code owners
|
someChangesWereAppByCodeOwner: Some changes were approved by code owners
|
||||||
xApprovalsArePending: '{approved}/{min} approvals are pending'
|
xApprovalsArePending: '{approved}/{min} approvals are pending'
|
||||||
codeOwnerReqChangesToPr: Code owners requested changes to the pull request
|
codeOwnerReqChangesToPr: Code owners requested changes to the pull request
|
||||||
|
defaultReviewersChangesToPr: Default reviewers requested changes to the pull request
|
||||||
checkSection:
|
checkSection:
|
||||||
someReqChecksFailed: Some required checks have failed
|
someReqChecksFailed: Some required checks have failed
|
||||||
someReqChecksPending: Some required checks are pending
|
someReqChecksPending: Some required checks are pending
|
||||||
|
@ -1203,6 +1217,7 @@ checklist: Check list
|
||||||
code: Code
|
code: Code
|
||||||
approvedBy: Approved By
|
approvedBy: Approved By
|
||||||
ownersHeading: OWNERS
|
ownersHeading: OWNERS
|
||||||
|
defaultReviewers: DEFAULT REVIEWERS
|
||||||
changesRequestedBy: Changes Requested By
|
changesRequestedBy: Changes Requested By
|
||||||
changesRequested: changesRequested
|
changesRequested: changesRequested
|
||||||
owners: Owners
|
owners: Owners
|
||||||
|
@ -1322,3 +1337,4 @@ rebase: Rebase
|
||||||
rebaseBranch: Rebase branch
|
rebaseBranch: Rebase branch
|
||||||
updateWithRebase: Update with rebase
|
updateWithRebase: Update with rebase
|
||||||
updatedBranchMessageRebase: Updated branch with rebase
|
updatedBranchMessageRebase: Updated branch with rebase
|
||||||
|
onLatestChanges: on latest changes
|
||||||
|
|
|
@ -104,7 +104,7 @@ export function CodeOwnersOverview({
|
||||||
return codeOwners?.evaluation_entries?.length ? (
|
return codeOwners?.evaluation_entries?.length ? (
|
||||||
<Container
|
<Container
|
||||||
className={cx(css.main, { [css.codeOwner]: !standalone })}
|
className={cx(css.main, { [css.codeOwner]: !standalone })}
|
||||||
margin={{ top: 'medium', bottom: pullReqMetadata.description ? undefined : 'large' }}
|
margin={{ top: 'medium', bottom: pullReqMetadata?.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>
|
||||||
|
@ -165,7 +165,7 @@ export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'CODE',
|
id: 'CODE',
|
||||||
width: '45%',
|
width: '40%',
|
||||||
sort: true,
|
sort: true,
|
||||||
Header: getString('code'),
|
Header: getString('code'),
|
||||||
accessor: 'CODE',
|
accessor: 'CODE',
|
||||||
|
@ -183,7 +183,7 @@ export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Owners',
|
id: 'Owners',
|
||||||
width: '13%',
|
width: '18%',
|
||||||
sort: true,
|
sort: true,
|
||||||
Header: getString('ownersHeading'),
|
Header: getString('ownersHeading'),
|
||||||
accessor: 'OWNERS',
|
accessor: 'OWNERS',
|
||||||
|
|
|
@ -27,7 +27,8 @@ import type {
|
||||||
TypesPullReqReviewer,
|
TypesPullReqReviewer,
|
||||||
RepoRepositoryOutput,
|
RepoRepositoryOutput,
|
||||||
TypesRuleViolations,
|
TypesRuleViolations,
|
||||||
TypesBranchExtended
|
TypesBranchExtended,
|
||||||
|
TypesDefaultReviewerApprovalsResponse
|
||||||
} from 'services/code'
|
} from 'services/code'
|
||||||
import {
|
import {
|
||||||
PRMergeOption,
|
PRMergeOption,
|
||||||
|
@ -103,6 +104,7 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
|
||||||
const [reqCodeOwnerApproval, setReqCodeOwnerApproval] = useState(false)
|
const [reqCodeOwnerApproval, setReqCodeOwnerApproval] = useState(false)
|
||||||
const [minApproval, setMinApproval] = useState(0)
|
const [minApproval, setMinApproval] = useState(0)
|
||||||
const [reqCodeOwnerLatestApproval, setReqCodeOwnerLatestApproval] = useState(false)
|
const [reqCodeOwnerLatestApproval, setReqCodeOwnerLatestApproval] = useState(false)
|
||||||
|
const [defaultReviewersInfoSet, setDefaultReviewersInfoSet] = useState<TypesDefaultReviewerApprovalsResponse[]>([])
|
||||||
const [minReqLatestApproval, setMinReqLatestApproval] = useState(0)
|
const [minReqLatestApproval, setMinReqLatestApproval] = useState(0)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [resolvedCommentArr, setResolvedCommentArr] = useState<any>()
|
const [resolvedCommentArr, setResolvedCommentArr] = useState<any>()
|
||||||
|
@ -234,7 +236,8 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
|
||||||
setMinApproval,
|
setMinApproval,
|
||||||
setReqCodeOwnerLatestApproval,
|
setReqCodeOwnerLatestApproval,
|
||||||
setMinReqLatestApproval,
|
setMinReqLatestApproval,
|
||||||
setPRStateLoading
|
setPRStateLoading,
|
||||||
|
setDefaultReviewersInfoSet
|
||||||
) // eslint-disable-next-line react-hooks/exhaustive-deps
|
) // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [unchecked, pullReqMetadata?.source_sha, activities])
|
}, [unchecked, pullReqMetadata?.source_sha, activities])
|
||||||
|
|
||||||
|
@ -295,6 +298,7 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
|
||||||
reqCodeOwnerLatestApproval={reqCodeOwnerLatestApproval}
|
reqCodeOwnerLatestApproval={reqCodeOwnerLatestApproval}
|
||||||
refetchCodeOwners={refetchCodeOwners}
|
refetchCodeOwners={refetchCodeOwners}
|
||||||
mergeBlockedRule={mergeBlockedRule}
|
mergeBlockedRule={mergeBlockedRule}
|
||||||
|
defaultReviewersInfoSet={defaultReviewersInfoSet}
|
||||||
/>
|
/>
|
||||||
</Render>
|
</Render>
|
||||||
),
|
),
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { Render } from 'react-jsx-match'
|
||||||
import { isEmpty } from 'lodash-es'
|
import { isEmpty } from 'lodash-es'
|
||||||
import type { IconName } from '@blueprintjs/core'
|
import type { IconName } from '@blueprintjs/core'
|
||||||
import { Icon } from '@harnessio/icons'
|
import { Icon } from '@harnessio/icons'
|
||||||
import { CodeOwnerReqDecision, findChangeReqDecisions } from 'utils/Utils'
|
import { CodeOwnerReqDecision, findChangeReqDecisions, getUnifiedDefaultReviewersState } from 'utils/Utils'
|
||||||
import { CodeOwnerSection } from 'pages/PullRequest/CodeOwners/CodeOwnersOverview'
|
import { CodeOwnerSection } from 'pages/PullRequest/CodeOwners/CodeOwnersOverview'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import type {
|
import type {
|
||||||
|
@ -38,10 +38,12 @@ import type {
|
||||||
TypesCodeOwnerEvaluationEntry,
|
TypesCodeOwnerEvaluationEntry,
|
||||||
TypesPullReq,
|
TypesPullReq,
|
||||||
TypesPullReqReviewer,
|
TypesPullReqReviewer,
|
||||||
RepoRepositoryOutput
|
RepoRepositoryOutput,
|
||||||
|
TypesDefaultReviewerApprovalsResponse
|
||||||
} from 'services/code'
|
} from 'services/code'
|
||||||
import { capitalizeFirstLetter } from 'pages/PullRequest/Checks/ChecksUtils'
|
import { capitalizeFirstLetter } from 'pages/PullRequest/Checks/ChecksUtils'
|
||||||
import { findWaitingDecisions } from 'pages/PullRequest/PullRequestUtils'
|
import { defaultReviewerResponseWithDecision, findWaitingDecisions } from 'pages/PullRequest/PullRequestUtils'
|
||||||
|
import { DefaultReviewersPanel } from 'pages/PullRequest/DefaultReviewers/DefaultReviewersPanel'
|
||||||
import greyCircle from '../../../../../icons/greyCircle.svg?url'
|
import greyCircle from '../../../../../icons/greyCircle.svg?url'
|
||||||
import emptyStatus from '../../../../../icons/emptyStatus.svg?url'
|
import emptyStatus from '../../../../../icons/emptyStatus.svg?url'
|
||||||
import Success from '../../../../../icons/code-success.svg?url'
|
import Success from '../../../../../icons/code-success.svg?url'
|
||||||
|
@ -58,6 +60,7 @@ interface ChangesSectionProps {
|
||||||
reqCodeOwnerApproval: boolean
|
reqCodeOwnerApproval: boolean
|
||||||
minApproval: number
|
minApproval: number
|
||||||
reviewers: TypesPullReqReviewer[] | null
|
reviewers: TypesPullReqReviewer[] | null
|
||||||
|
defaultReviewersInfoSet: TypesDefaultReviewerApprovalsResponse[]
|
||||||
minReqLatestApproval: number
|
minReqLatestApproval: number
|
||||||
reqCodeOwnerLatestApproval: boolean
|
reqCodeOwnerLatestApproval: boolean
|
||||||
mergeBlockedRule: boolean
|
mergeBlockedRule: boolean
|
||||||
|
@ -69,6 +72,7 @@ interface ChangesSectionProps {
|
||||||
const ChangesSection = (props: ChangesSectionProps) => {
|
const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
const {
|
const {
|
||||||
reviewers: currReviewers,
|
reviewers: currReviewers,
|
||||||
|
defaultReviewersInfoSet,
|
||||||
minApproval,
|
minApproval,
|
||||||
reqCodeOwnerApproval,
|
reqCodeOwnerApproval,
|
||||||
repoMetadata,
|
repoMetadata,
|
||||||
|
@ -139,6 +143,18 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
changeReqEvaluations[0].reviewer?.display_name || changeReqEvaluations[0].reviewer?.uid || ''
|
changeReqEvaluations[0].reviewer?.display_name || changeReqEvaluations[0].reviewer?.uid || ''
|
||||||
)
|
)
|
||||||
: 'Reviewer'
|
: 'Reviewer'
|
||||||
|
const updatedDefaultApprovalRes = reviewers
|
||||||
|
? defaultReviewerResponseWithDecision(defaultReviewersInfoSet, reviewers)
|
||||||
|
: defaultReviewersInfoSet
|
||||||
|
|
||||||
|
const {
|
||||||
|
defReviewerApprovalRequiredByRule,
|
||||||
|
defReviewerLatestApprovalRequiredByRule,
|
||||||
|
defReviewerApprovedLatestChanges,
|
||||||
|
defReviewerApprovedChanges,
|
||||||
|
changesRequestedByDefReviewersArr
|
||||||
|
} = getUnifiedDefaultReviewersState(updatedDefaultApprovalRes)
|
||||||
|
|
||||||
const extractInfoForCodeOwnerContent = () => {
|
const extractInfoForCodeOwnerContent = () => {
|
||||||
let statusMessage = ''
|
let statusMessage = ''
|
||||||
let statusColor = 'grey' // Default color for no rules required
|
let statusColor = 'grey' // Default color for no rules required
|
||||||
|
@ -151,6 +167,8 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
minApproval > 0 ||
|
minApproval > 0 ||
|
||||||
reqCodeOwnerLatestApproval ||
|
reqCodeOwnerLatestApproval ||
|
||||||
minReqLatestApproval > 0 ||
|
minReqLatestApproval > 0 ||
|
||||||
|
defReviewerApprovalRequiredByRule ||
|
||||||
|
defReviewerLatestApprovalRequiredByRule ||
|
||||||
mergeBlockedRule
|
mergeBlockedRule
|
||||||
) {
|
) {
|
||||||
if (mergeBlockedRule) {
|
if (mergeBlockedRule) {
|
||||||
|
@ -183,6 +201,14 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
statusMessage = getString('changesSection.latestChangesPendingReqRev')
|
statusMessage = getString('changesSection.latestChangesPendingReqRev')
|
||||||
statusColor = Color.ORANGE_500
|
statusColor = Color.ORANGE_500
|
||||||
statusIcon = 'execution-waiting'
|
statusIcon = 'execution-waiting'
|
||||||
|
} else if (defReviewerLatestApprovalRequiredByRule && !defReviewerApprovedLatestChanges) {
|
||||||
|
title = getString('changesSection.approvalPending')
|
||||||
|
statusMessage = stringSubstitute(getString('changesSection.pendingLatestApprovalDefaultReviewers'), {
|
||||||
|
count: approvedEvaluations?.length || '0',
|
||||||
|
total: minApproval
|
||||||
|
}) as string
|
||||||
|
statusColor = Color.ORANGE_500
|
||||||
|
statusIcon = 'execution-waiting'
|
||||||
} else if (approvedEvaluations && approvedEvaluations?.length < minApproval && minApproval > 0) {
|
} else if (approvedEvaluations && approvedEvaluations?.length < minApproval && minApproval > 0) {
|
||||||
title = getString('changesSection.approvalPending')
|
title = getString('changesSection.approvalPending')
|
||||||
statusMessage = stringSubstitute(getString('changesSection.waitingOnReviewers'), {
|
statusMessage = stringSubstitute(getString('changesSection.waitingOnReviewers'), {
|
||||||
|
@ -190,6 +216,15 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
total: minApproval
|
total: minApproval
|
||||||
}) as string
|
}) as string
|
||||||
|
|
||||||
|
statusColor = Color.ORANGE_500
|
||||||
|
statusIcon = 'execution-waiting'
|
||||||
|
} else if (defReviewerApprovalRequiredByRule && !defReviewerApprovedChanges) {
|
||||||
|
title = getString('changesSection.approvalPending')
|
||||||
|
statusMessage = stringSubstitute(getString('changesSection.waitingOnDefaultReviewers'), {
|
||||||
|
count: approvedEvaluations?.length || '0',
|
||||||
|
total: minApproval
|
||||||
|
}) as string
|
||||||
|
|
||||||
statusColor = Color.ORANGE_500
|
statusColor = Color.ORANGE_500
|
||||||
statusIcon = 'execution-waiting'
|
statusIcon = 'execution-waiting'
|
||||||
} else if (reqCodeOwnerLatestApproval && latestCodeOwnerApprovalArr?.length > 0) {
|
} else if (reqCodeOwnerLatestApproval && latestCodeOwnerApprovalArr?.length > 0) {
|
||||||
|
@ -384,6 +419,84 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderDefaultReviewersStatus = () => {
|
||||||
|
if (defReviewerLatestApprovalRequiredByRule && !defReviewerApprovedLatestChanges) {
|
||||||
|
return (
|
||||||
|
// Waiting on default reviewers reviews of latest changes
|
||||||
|
<Layout.Horizontal>
|
||||||
|
<Container padding={{ left: 'large' }}>
|
||||||
|
<img alt="emptyStatus" width={16} height={16} src={emptyStatus} />
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<Text padding={{ left: 'medium' }} className={css.sectionSubheader}>
|
||||||
|
{getString('changesSection.waitingOnLatestDefaultReviewers')}
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (defReviewerApprovalRequiredByRule && !defReviewerApprovedChanges) {
|
||||||
|
//Changes are pending approval from default reviewers
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal>
|
||||||
|
<Container padding={{ left: 'large' }}>
|
||||||
|
<img alt="emptyStatus" width={16} height={16} src={emptyStatus} />
|
||||||
|
</Container>
|
||||||
|
<Text padding={{ left: 'medium' }} className={css.sectionSubheader}>
|
||||||
|
{getString('changesSection.waitingOnDefaultReviewers')}
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defReviewerLatestApprovalRequiredByRule && defReviewerApprovedLatestChanges) {
|
||||||
|
// Latest changes were approved by default reviewers
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
icon={'tick-circle'}
|
||||||
|
iconProps={{
|
||||||
|
size: 16,
|
||||||
|
color: Color.GREEN_700,
|
||||||
|
padding: { right: 'medium' }
|
||||||
|
}}
|
||||||
|
padding={{ left: 'large' }}
|
||||||
|
className={css.sectionSubheader}>
|
||||||
|
{getString('changesSection.latestChangesWereAppByDefaultReviewers')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defReviewerApprovalRequiredByRule && defReviewerApprovedChanges) {
|
||||||
|
//Changes were approved by default reviewers
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
icon={'tick-circle'}
|
||||||
|
iconProps={{
|
||||||
|
size: 16,
|
||||||
|
color: Color.GREEN_700,
|
||||||
|
padding: { right: 'medium' }
|
||||||
|
}}
|
||||||
|
padding={{ left: 'large' }}
|
||||||
|
className={css.sectionSubheader}>
|
||||||
|
{getString('changesSection.changesWereAppByDefaultReviewers')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
icon={'tick-circle'}
|
||||||
|
iconProps={{
|
||||||
|
size: 16,
|
||||||
|
color: Color.GREEN_700,
|
||||||
|
padding: { right: 'medium' }
|
||||||
|
}}
|
||||||
|
padding={{ left: 'large' }}
|
||||||
|
className={css.sectionSubheader}>
|
||||||
|
{getString('changesSection.defaultReviewersStatus')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
const viewBtn =
|
const viewBtn =
|
||||||
!mergeBlockedRule &&
|
!mergeBlockedRule &&
|
||||||
(minApproval > minReqLatestApproval ||
|
(minApproval > minReqLatestApproval ||
|
||||||
|
@ -573,6 +686,56 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
{!isEmpty(defaultReviewersInfoSet) &&
|
||||||
|
(defReviewerApprovalRequiredByRule || defReviewerLatestApprovalRequiredByRule) && (
|
||||||
|
<Container className={css.borderContainer} padding={{ left: 'xlarge', right: 'small' }}>
|
||||||
|
<Layout.Horizontal className={css.paddingContainer} flex={{ justifyContent: 'space-between' }}>
|
||||||
|
{changesRequestedByDefReviewersArr && changesRequestedByDefReviewersArr?.length > 0 ? (
|
||||||
|
<Text
|
||||||
|
className={cx(
|
||||||
|
css.sectionSubheader,
|
||||||
|
defReviewerApprovalRequiredByRule || defReviewerLatestApprovalRequiredByRule
|
||||||
|
? css.redIcon
|
||||||
|
: css.greyIcon
|
||||||
|
)}
|
||||||
|
icon={'error-transparent-no-outline'}
|
||||||
|
iconProps={{
|
||||||
|
size: 17,
|
||||||
|
color:
|
||||||
|
defReviewerApprovalRequiredByRule || defReviewerLatestApprovalRequiredByRule
|
||||||
|
? Color.RED_600
|
||||||
|
: '',
|
||||||
|
padding: { right: 'medium' }
|
||||||
|
}}
|
||||||
|
padding={{ left: 'large' }}>
|
||||||
|
{getString('changesSection.defaultReviewersChangesToPr')}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
renderDefaultReviewersStatus()
|
||||||
|
)}
|
||||||
|
{(defReviewerApprovalRequiredByRule || defReviewerLatestApprovalRequiredByRule) && (
|
||||||
|
<Container className={css.changeContainerPadding}>
|
||||||
|
<Container className={css.requiredContainer}>
|
||||||
|
<Text className={css.requiredText}>{getString('required')}</Text>
|
||||||
|
</Container>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
{!isEmpty(defaultReviewersInfoSet) && (
|
||||||
|
<Container
|
||||||
|
className={css.codeOwnerContainer}
|
||||||
|
padding={{ top: 'small', bottom: 'small', left: 'xxxlarge', right: 'small' }}>
|
||||||
|
<DefaultReviewersPanel
|
||||||
|
defaultRevApprovalResponse={updatedDefaultApprovalRes.filter(
|
||||||
|
res => res.minimum_required_count || res.minimum_required_count_latest
|
||||||
|
)} //to only consider response with min default reviewers required (>0)
|
||||||
|
pullReqMetadata={pullReqMetadata}
|
||||||
|
repoMetadata={repoMetadata}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
{!isEmpty(codeOwners) && !isEmpty(codeOwners.evaluation_entries) && (
|
{!isEmpty(codeOwners) && !isEmpty(codeOwners.evaluation_entries) && (
|
||||||
<Container className={css.borderContainer} padding={{ left: 'xlarge', right: 'small' }}>
|
<Container className={css.borderContainer} padding={{ left: 'xlarge', right: 'small' }}>
|
||||||
<Layout.Horizontal className={css.paddingContainer} flex={{ justifyContent: 'space-between' }}>
|
<Layout.Horizontal className={css.paddingContainer} flex={{ justifyContent: 'space-between' }}>
|
||||||
|
@ -604,7 +767,6 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
</Container>
|
|
||||||
{codeOwners && !isEmpty(codeOwners?.evaluation_entries) && (
|
{codeOwners && !isEmpty(codeOwners?.evaluation_entries) && (
|
||||||
<Container
|
<Container
|
||||||
className={css.codeOwnerContainer}
|
className={css.codeOwnerContainer}
|
||||||
|
@ -617,6 +779,7 @@ const ChangesSection = (props: ChangesSectionProps) => {
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
</Container>
|
||||||
</Render>
|
</Render>
|
||||||
</Render>
|
</Render>
|
||||||
)
|
)
|
||||||
|
|
|
@ -50,29 +50,27 @@ interface ReviewerAddActivityPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatListWithAndFragment = (names: string[]): React.ReactNode => {
|
const formatListWithAndFragment = (names: string[]): React.ReactNode => {
|
||||||
const uniqueNames = [...new Set(names)] // Ensure uniqueness
|
switch (names.length) {
|
||||||
|
|
||||||
switch (uniqueNames.length) {
|
|
||||||
case 0:
|
case 0:
|
||||||
return null
|
return null
|
||||||
case 1:
|
case 1:
|
||||||
return <strong>{uniqueNames[0]}</strong>
|
return <strong>{names[0]}</strong>
|
||||||
case 2:
|
case 2:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<strong>{uniqueNames[0]}</strong> and <strong>{uniqueNames[1]}</strong>
|
<strong>{names[0]}</strong> and <strong>{names[1]}</strong>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{uniqueNames.slice(0, -1).map((name, index) => (
|
{names.slice(0, -1).map((name, index) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<strong>{name}</strong>
|
<strong>{name}</strong>
|
||||||
{index < uniqueNames.length - 2 ? ', ' : ''}
|
{index < names.length - 2 ? ', ' : ''}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}{' '}
|
))}{' '}
|
||||||
and <strong>{uniqueNames[uniqueNames.length - 1]}</strong>
|
and <strong>{names[names.length - 1]}</strong>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -86,10 +84,18 @@ export const SystemComment: React.FC<SystemCommentProps> = ({ pullReqMetadata, c
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const displayNameList = useMemo(() => {
|
const displayNameList = useMemo(() => {
|
||||||
const checkList = payload?.metadata?.mentions?.ids ?? []
|
const checkList = payload?.metadata?.mentions?.ids ?? []
|
||||||
|
const uniqueList = [...new Set(checkList)]
|
||||||
const mentionsMap = payload?.mentions ?? {}
|
const mentionsMap = payload?.mentions ?? {}
|
||||||
return [...new Set(checkList.map(id => mentionsMap[id]?.display_name ?? ''))]
|
return uniqueList.map(id => mentionsMap[id]?.display_name ?? '')
|
||||||
}, [payload?.metadata?.mentions?.ids, payload?.mentions])
|
}, [payload?.metadata?.mentions?.ids, payload?.mentions])
|
||||||
|
|
||||||
|
const principalNameList = useMemo(() => {
|
||||||
|
const checkList = (payload?.payload as any)?.principal_ids ?? []
|
||||||
|
const uniqueList = [...new Set(checkList)]
|
||||||
|
const mentionsMap = payload?.mentions ?? {}
|
||||||
|
return uniqueList.map(id => mentionsMap[id as number]?.display_name ?? '')
|
||||||
|
}, [(payload?.payload as any)?.principal_ids, payload?.mentions])
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CommentType.MERGE: {
|
case CommentType.MERGE: {
|
||||||
return (
|
return (
|
||||||
|
@ -461,6 +467,7 @@ export const SystemComment: React.FC<SystemCommentProps> = ({ pullReqMetadata, c
|
||||||
|
|
||||||
case CommentType.REVIEWER_ADD: {
|
case CommentType.REVIEWER_ADD: {
|
||||||
const activityMentions = formatListWithAndFragment(displayNameList)
|
const activityMentions = formatListWithAndFragment(displayNameList)
|
||||||
|
const principalMentions = formatListWithAndFragment(principalNameList)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.mergedBox}>
|
<Container className={css.mergedBox}>
|
||||||
|
@ -499,7 +506,7 @@ export const SystemComment: React.FC<SystemCommentProps> = ({ pullReqMetadata, c
|
||||||
str={getString('prReview.codeowners')}
|
str={getString('prReview.codeowners')}
|
||||||
vars={{
|
vars={{
|
||||||
author: <strong>{payload?.author?.display_name}</strong>,
|
author: <strong>{payload?.author?.display_name}</strong>,
|
||||||
codeowners: activityMentions
|
codeowners: principalMentions
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Case>
|
</Case>
|
||||||
|
@ -508,7 +515,7 @@ export const SystemComment: React.FC<SystemCommentProps> = ({ pullReqMetadata, c
|
||||||
str={getString('prReview.defaultReviewers')}
|
str={getString('prReview.defaultReviewers')}
|
||||||
vars={{
|
vars={{
|
||||||
author: <strong>{payload?.author?.display_name}</strong>,
|
author: <strong>{payload?.author?.display_name}</strong>,
|
||||||
reviewers: activityMentions
|
reviewers: principalMentions
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Case>
|
</Case>
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { Render } from 'react-jsx-match'
|
||||||
|
import { Container, Text, TableV2, Layout, Avatar } from '@harnessio/uicore'
|
||||||
|
import { Color } from '@harnessio/design-system'
|
||||||
|
import type { CellProps, Column } from 'react-table'
|
||||||
|
import type { GitInfoProps } from 'utils/GitUtils'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import type { TypesDefaultReviewerApprovalsResponseWithRevDecision } from 'utils/Utils'
|
||||||
|
import { PullReqReviewDecision } from '../PullRequestUtils'
|
||||||
|
import css from '../CodeOwners/CodeOwnersOverview.module.scss'
|
||||||
|
|
||||||
|
interface DefaultReviewersPanelProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> {
|
||||||
|
defaultRevApprovalResponse: TypesDefaultReviewerApprovalsResponseWithRevDecision[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultReviewersPanel: React.FC<DefaultReviewersPanelProps> = ({
|
||||||
|
defaultRevApprovalResponse,
|
||||||
|
pullReqMetadata
|
||||||
|
}) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'REQUIRED',
|
||||||
|
width: '40%',
|
||||||
|
sort: true,
|
||||||
|
Header: getString('required'),
|
||||||
|
accessor: 'REQUIRED',
|
||||||
|
Cell: ({ row }: CellProps<TypesDefaultReviewerApprovalsResponseWithRevDecision>) => {
|
||||||
|
if (row.original?.minimum_required_count && row.original?.minimum_required_count > 0)
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
lineClamp={1}
|
||||||
|
padding={{ left: 'small', right: 'small' }}
|
||||||
|
color={Color.BLACK}
|
||||||
|
flex={{ justifyContent: 'space-between' }}>
|
||||||
|
{row.original.current_count} / {row.original.minimum_required_count}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
else if (row.original?.minimum_required_count_latest && row.original?.minimum_required_count_latest > 0)
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal>
|
||||||
|
<Text
|
||||||
|
lineClamp={1}
|
||||||
|
padding={{ left: 'small', right: 'small' }}
|
||||||
|
color={Color.BLACK}
|
||||||
|
flex={{ justifyContent: 'space-between' }}>
|
||||||
|
{row.original.current_count} / {row.original.minimum_required_count_latest}
|
||||||
|
</Text>
|
||||||
|
<Text color={Color.GREY_350}>({getString('onLatestChanges')})</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
else <Text>{row.original.current_count}</Text>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'DefaultReviewers',
|
||||||
|
width: '18%',
|
||||||
|
sort: true,
|
||||||
|
Header: getString('defaultReviewers'),
|
||||||
|
accessor: 'DefaultReviewers',
|
||||||
|
Cell: ({ row }: CellProps<TypesDefaultReviewerApprovalsResponseWithRevDecision>) => {
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal
|
||||||
|
key={`keyContainer-${row.original.rule_info?.identifier}`}
|
||||||
|
className={css.ownerContainer}
|
||||||
|
spacing="tiny">
|
||||||
|
{row.original.principals?.map((principal, idx) => {
|
||||||
|
if (idx < 2) {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
key={`text-${principal?.display_name}-${idx}-avatar`}
|
||||||
|
hoverCard={true}
|
||||||
|
email={principal?.email || ' '}
|
||||||
|
size="small"
|
||||||
|
name={principal?.display_name || ''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (idx === 2 && row.original.principals?.length && row.original.principals?.length > 2) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={`text-${principal?.display_name}-${idx}-top`}
|
||||||
|
padding={{ top: 'xsmall' }}
|
||||||
|
tooltipProps={{ isDark: true }}
|
||||||
|
tooltip={
|
||||||
|
<Container width={215} padding={'small'}>
|
||||||
|
<Layout.Horizontal key={`tooltip-${idx}`} className={css.ownerTooltip}>
|
||||||
|
{row.original.principals?.map((entry, entryidx) => (
|
||||||
|
<Text
|
||||||
|
key={`text-${entry?.display_name}-${entryidx}`}
|
||||||
|
lineClamp={1}
|
||||||
|
color={Color.GREY_0}
|
||||||
|
padding={{ right: 'small' }}>
|
||||||
|
{row.original.principals?.length === entryidx + 1
|
||||||
|
? `${entry?.display_name}`
|
||||||
|
: `${entry?.display_name}, `}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
}
|
||||||
|
flex={{ alignItems: 'center' }}>{`+${row.original.principals?.length - 2}`}</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changesRequested',
|
||||||
|
Header: getString('changesRequestedBy'),
|
||||||
|
width: '24%',
|
||||||
|
sort: true,
|
||||||
|
accessor: 'ChangesRequested',
|
||||||
|
Cell: ({ row }: CellProps<TypesDefaultReviewerApprovalsResponseWithRevDecision>) => {
|
||||||
|
const changeReqEvaluations = row?.original?.principals?.filter(
|
||||||
|
principal => principal?.review_decision === PullReqReviewDecision.CHANGEREQ
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal className={css.ownerContainer} spacing="tiny">
|
||||||
|
{changeReqEvaluations?.map((principal, idx: number) => {
|
||||||
|
if (idx < 2) {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
key={`approved-${principal?.display_name}-avatar`}
|
||||||
|
hoverCard={true}
|
||||||
|
email={principal?.email || ' '}
|
||||||
|
size="small"
|
||||||
|
name={principal?.display_name || ''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (idx === 2 && changeReqEvaluations.length && changeReqEvaluations.length > 2) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={`approved-${principal?.display_name}-text`}
|
||||||
|
padding={{ top: 'xsmall' }}
|
||||||
|
tooltipProps={{ isDark: true }}
|
||||||
|
tooltip={
|
||||||
|
<Container width={215} padding={'small'}>
|
||||||
|
<Layout.Horizontal className={css.ownerTooltip}>
|
||||||
|
{changeReqEvaluations?.map(evalPrincipal => (
|
||||||
|
<Text
|
||||||
|
key={`approved-${evalPrincipal?.display_name}`}
|
||||||
|
lineClamp={1}
|
||||||
|
color={Color.GREY_0}
|
||||||
|
padding={{ right: 'small' }}>{`${evalPrincipal?.display_name}, `}</Text>
|
||||||
|
))}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
}
|
||||||
|
flex={{ alignItems: 'center' }}>{`+${changeReqEvaluations.length - 2}`}</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'approvedBy',
|
||||||
|
Header: getString('approvedBy'),
|
||||||
|
sort: true,
|
||||||
|
width: '15%',
|
||||||
|
accessor: 'APPROVED BY',
|
||||||
|
Cell: ({ row }: CellProps<TypesDefaultReviewerApprovalsResponseWithRevDecision>) => {
|
||||||
|
const approvedEvaluations = row?.original?.principals?.filter(
|
||||||
|
principal =>
|
||||||
|
principal.review_decision === PullReqReviewDecision.APPROVED &&
|
||||||
|
(row.original.minimum_required_count_latest
|
||||||
|
? principal.review_sha === pullReqMetadata?.source_sha
|
||||||
|
: true)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal className={css.ownerContainer} spacing="tiny">
|
||||||
|
{approvedEvaluations?.map((principal, idx: number) => {
|
||||||
|
if (idx < 2) {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
key={`approved-${principal?.display_name}-avatar`}
|
||||||
|
hoverCard={true}
|
||||||
|
email={principal?.email || ' '}
|
||||||
|
size="small"
|
||||||
|
name={principal?.display_name || ''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (idx === 2 && approvedEvaluations.length && approvedEvaluations.length > 2) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={`approved-${principal?.display_name}-text`}
|
||||||
|
padding={{ top: 'xsmall' }}
|
||||||
|
tooltipProps={{ isDark: true }}
|
||||||
|
tooltip={
|
||||||
|
<Container width={215} padding={'small'}>
|
||||||
|
<Layout.Horizontal className={css.ownerTooltip}>
|
||||||
|
{approvedEvaluations?.map(appPrincipalObj => (
|
||||||
|
<Text
|
||||||
|
key={`approved-${appPrincipalObj?.display_name}`}
|
||||||
|
lineClamp={1}
|
||||||
|
color={Color.GREY_0}
|
||||||
|
padding={{ right: 'small' }}>{`${appPrincipalObj?.display_name}, `}</Text>
|
||||||
|
))}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
}
|
||||||
|
flex={{ alignItems: 'center' }}>{`+${approvedEvaluations.length - 2}`}</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as unknown as Column<TypesDefaultReviewerApprovalsResponseWithRevDecision>[], // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Render when={defaultRevApprovalResponse?.length}>
|
||||||
|
<Container>
|
||||||
|
<Layout.Vertical spacing="small">
|
||||||
|
<TableV2
|
||||||
|
className={css.codeOwnerTable}
|
||||||
|
sortable
|
||||||
|
columns={columns}
|
||||||
|
data={defaultRevApprovalResponse as TypesDefaultReviewerApprovalsResponseWithRevDecision[]}
|
||||||
|
getRowClassName={() => css.row}
|
||||||
|
/>
|
||||||
|
</Layout.Vertical>
|
||||||
|
</Container>
|
||||||
|
</Render>
|
||||||
|
)
|
||||||
|
}
|
|
@ -23,9 +23,12 @@ import type {
|
||||||
EnumMergeMethod,
|
EnumMergeMethod,
|
||||||
EnumPullReqReviewDecision,
|
EnumPullReqReviewDecision,
|
||||||
TypesCodeOwnerEvaluationEntry,
|
TypesCodeOwnerEvaluationEntry,
|
||||||
|
TypesDefaultReviewerApprovalsResponse,
|
||||||
TypesOwnerEvaluation,
|
TypesOwnerEvaluation,
|
||||||
|
TypesPrincipalInfo,
|
||||||
TypesPullReq,
|
TypesPullReq,
|
||||||
TypesPullReqActivity
|
TypesPullReqActivity,
|
||||||
|
TypesPullReqReviewer
|
||||||
} from 'services/code'
|
} from 'services/code'
|
||||||
|
|
||||||
export interface PRMergeOption extends SelectOption {
|
export interface PRMergeOption extends SelectOption {
|
||||||
|
@ -95,7 +98,7 @@ export const findWaitingDecisions = (
|
||||||
const hasApprovedDecision = entry?.owner_evaluations?.some(
|
const hasApprovedDecision = entry?.owner_evaluations?.some(
|
||||||
evaluation =>
|
evaluation =>
|
||||||
evaluation.review_decision === PullReqReviewDecision.APPROVED &&
|
evaluation.review_decision === PullReqReviewDecision.APPROVED &&
|
||||||
(reqCodeOwnerLatestApproval ? evaluation.review_sha === pullReqMetadata.source_sha : true)
|
(reqCodeOwnerLatestApproval ? evaluation.review_sha === pullReqMetadata?.source_sha : true)
|
||||||
)
|
)
|
||||||
return !hasApprovedDecision
|
return !hasApprovedDecision
|
||||||
})
|
})
|
||||||
|
@ -176,3 +179,41 @@ export const getMergeOptions = (getString: UseStringsReturn['getString'], mergea
|
||||||
value: 'close'
|
value: 'close'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const updateReviewDecisionPrincipal = (reviewers: TypesPullReqReviewer[], principals: TypesPrincipalInfo[]) => {
|
||||||
|
const reviewDecisionMap: {
|
||||||
|
[x: number]: { sha: string; review_decision: EnumPullReqReviewDecision } | null
|
||||||
|
} = reviewers?.reduce((acc, rev) => {
|
||||||
|
if (rev.reviewer?.id) {
|
||||||
|
acc[rev.reviewer.id] = {
|
||||||
|
sha: rev.sha ?? '',
|
||||||
|
review_decision: rev.review_decision ?? 'pending'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as { [x: number]: { sha: string; review_decision: EnumPullReqReviewDecision } | null })
|
||||||
|
|
||||||
|
return principals?.map(principal => {
|
||||||
|
if (principal?.id) {
|
||||||
|
return {
|
||||||
|
...principal,
|
||||||
|
review_decision: reviewDecisionMap[principal.id] ? reviewDecisionMap[principal.id]?.review_decision : 'pending',
|
||||||
|
review_sha: reviewDecisionMap[principal.id]?.sha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return principal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultReviewerResponseWithDecision = (
|
||||||
|
defaultRevApprovalResponse: TypesDefaultReviewerApprovalsResponse[],
|
||||||
|
reviewers: TypesPullReqReviewer[]
|
||||||
|
) => {
|
||||||
|
return defaultRevApprovalResponse?.map(res => {
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
principals:
|
||||||
|
reviewers && res.principals ? updateReviewDecisionPrincipal(reviewers, res.principals) : res.principals
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ export type EnumPullReqCommentStatus = 'active' | 'resolved'
|
||||||
|
|
||||||
export type EnumPullReqReviewDecision = 'approved' | 'changereq' | 'pending' | 'reviewed'
|
export type EnumPullReqReviewDecision = 'approved' | 'changereq' | 'pending' | 'reviewed'
|
||||||
|
|
||||||
export type EnumPullReqReviewerType = 'assigned' | 'requested' | 'self_assigned'
|
export type EnumPullReqReviewerType = 'assigned' | 'code_owners' | 'default' | 'requested' | 'self_assigned'
|
||||||
|
|
||||||
export type EnumPullReqState = 'closed' | 'merged' | 'open'
|
export type EnumPullReqState = 'closed' | 'merged' | 'open'
|
||||||
|
|
||||||
|
@ -183,11 +183,11 @@ export type EnumWebhookTrigger =
|
||||||
| 'pullreq_comment_created'
|
| 'pullreq_comment_created'
|
||||||
| 'pullreq_comment_status_updated'
|
| 'pullreq_comment_status_updated'
|
||||||
| 'pullreq_comment_updated'
|
| 'pullreq_comment_updated'
|
||||||
| 'pullreq_review_submitted'
|
|
||||||
| 'pullreq_created'
|
| 'pullreq_created'
|
||||||
| 'pullreq_label_assigned'
|
| 'pullreq_label_assigned'
|
||||||
| 'pullreq_merged'
|
| 'pullreq_merged'
|
||||||
| 'pullreq_reopened'
|
| 'pullreq_reopened'
|
||||||
|
| 'pullreq_review_submitted'
|
||||||
| 'pullreq_updated'
|
| 'pullreq_updated'
|
||||||
| 'tag_created'
|
| 'tag_created'
|
||||||
| 'tag_deleted'
|
| 'tag_deleted'
|
||||||
|
@ -344,6 +344,7 @@ export interface OpenapiCommentUpdatePullReqRequest {
|
||||||
|
|
||||||
export interface OpenapiCommitFilesRequest {
|
export interface OpenapiCommitFilesRequest {
|
||||||
actions?: RepoCommitFileAction[] | null
|
actions?: RepoCommitFileAction[] | null
|
||||||
|
author?: GitIdentity
|
||||||
branch?: string
|
branch?: string
|
||||||
bypass_rules?: boolean
|
bypass_rules?: boolean
|
||||||
dry_run_rules?: boolean
|
dry_run_rules?: boolean
|
||||||
|
@ -738,6 +739,7 @@ export interface OpenapiWebhookType {
|
||||||
latest_execution_result?: EnumWebhookExecutionResult
|
latest_execution_result?: EnumWebhookExecutionResult
|
||||||
parent_id?: number
|
parent_id?: number
|
||||||
parent_type?: EnumWebhookParent
|
parent_type?: EnumWebhookParent
|
||||||
|
scope?: number
|
||||||
triggers?: EnumWebhookTrigger[] | null
|
triggers?: EnumWebhookTrigger[] | null
|
||||||
updated?: number
|
updated?: number
|
||||||
url?: string
|
url?: string
|
||||||
|
@ -754,6 +756,7 @@ export interface ProtectionDefApprovals {
|
||||||
require_code_owners?: boolean
|
require_code_owners?: boolean
|
||||||
require_latest_commit?: boolean
|
require_latest_commit?: boolean
|
||||||
require_minimum_count?: number
|
require_minimum_count?: number
|
||||||
|
require_minimum_default_reviewer_count?: number
|
||||||
require_no_change_request?: boolean
|
require_no_change_request?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,6 +1122,14 @@ export interface TypesCreateBranchOutput {
|
||||||
sha?: ShaSHA
|
sha?: ShaSHA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TypesDefaultReviewerApprovalsResponse {
|
||||||
|
current_count?: number
|
||||||
|
minimum_required_count?: number
|
||||||
|
minimum_required_count_latest?: number
|
||||||
|
principals?: TypesPrincipalInfo[] | null
|
||||||
|
rule_info?: TypesRuleInfo
|
||||||
|
}
|
||||||
|
|
||||||
export interface TypesDeleteBranchOutput {
|
export interface TypesDeleteBranchOutput {
|
||||||
dry_run_rules?: boolean
|
dry_run_rules?: boolean
|
||||||
rule_violations?: TypesRuleViolations[]
|
rule_violations?: TypesRuleViolations[]
|
||||||
|
@ -1358,6 +1369,7 @@ export interface TypesMergeResponse {
|
||||||
allowed_methods?: EnumMergeMethod[]
|
allowed_methods?: EnumMergeMethod[]
|
||||||
branch_deleted?: boolean
|
branch_deleted?: boolean
|
||||||
conflict_files?: string[]
|
conflict_files?: string[]
|
||||||
|
default_reviewer_aprovals?: TypesDefaultReviewerApprovalsResponse[]
|
||||||
dry_run?: boolean
|
dry_run?: boolean
|
||||||
dry_run_rules?: boolean
|
dry_run_rules?: boolean
|
||||||
mergeable?: boolean
|
mergeable?: boolean
|
||||||
|
@ -1676,6 +1688,7 @@ export interface TypesServiceAccount {
|
||||||
created?: number
|
created?: number
|
||||||
display_name?: string
|
display_name?: string
|
||||||
email?: string
|
email?: string
|
||||||
|
id?: number
|
||||||
parent_id?: number
|
parent_id?: number
|
||||||
parent_type?: EnumParentResourceType
|
parent_type?: EnumParentResourceType
|
||||||
uid?: string
|
uid?: string
|
||||||
|
@ -1801,8 +1814,10 @@ export interface TypesUser {
|
||||||
|
|
||||||
export interface TypesUserGroupInfo {
|
export interface TypesUserGroupInfo {
|
||||||
description?: string
|
description?: string
|
||||||
|
id?: number
|
||||||
identifier?: string
|
identifier?: string
|
||||||
name?: string
|
name?: string
|
||||||
|
scope?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TypesUserGroupOwnerEvaluation {
|
export interface TypesUserGroupOwnerEvaluation {
|
||||||
|
@ -3761,7 +3776,7 @@ export interface ListRepoLabelsQueryParams {
|
||||||
*/
|
*/
|
||||||
limit?: number
|
limit?: number
|
||||||
/**
|
/**
|
||||||
* The result should inherit labels from parent parent spaces.
|
* The result should inherit entities from parent spaces.
|
||||||
*/
|
*/
|
||||||
inherited?: boolean
|
inherited?: boolean
|
||||||
/**
|
/**
|
||||||
|
@ -6636,11 +6651,11 @@ export const useRuleList = ({ repo_ref, ...props }: UseRuleListProps) =>
|
||||||
{ base: getConfig('code/api/v1'), pathParams: { repo_ref }, ...props }
|
{ base: getConfig('code/api/v1'), pathParams: { repo_ref }, ...props }
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface RuleAddPathParams {
|
export interface RepoRuleAddPathParams {
|
||||||
repo_ref: string
|
repo_ref: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuleAddRequestBody {
|
export interface RepoRuleAddRequestBody {
|
||||||
definition?: OpenapiRuleDefinition
|
definition?: OpenapiRuleDefinition
|
||||||
description?: string
|
description?: string
|
||||||
identifier?: string
|
identifier?: string
|
||||||
|
@ -6650,14 +6665,14 @@ export interface RuleAddRequestBody {
|
||||||
uid?: string
|
uid?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RuleAddProps = Omit<
|
export type RepoRuleAddProps = Omit<
|
||||||
MutateProps<OpenapiRule, UsererrorError, void, RuleAddRequestBody, RuleAddPathParams>,
|
MutateProps<OpenapiRule, UsererrorError, void, RepoRuleAddRequestBody, RepoRuleAddPathParams>,
|
||||||
'path' | 'verb'
|
'path' | 'verb'
|
||||||
> &
|
> &
|
||||||
RuleAddPathParams
|
RepoRuleAddPathParams
|
||||||
|
|
||||||
export const RuleAdd = ({ repo_ref, ...props }: RuleAddProps) => (
|
export const RepoRuleAdd = ({ repo_ref, ...props }: RepoRuleAddProps) => (
|
||||||
<Mutate<OpenapiRule, UsererrorError, void, RuleAddRequestBody, RuleAddPathParams>
|
<Mutate<OpenapiRule, UsererrorError, void, RepoRuleAddRequestBody, RepoRuleAddPathParams>
|
||||||
verb="POST"
|
verb="POST"
|
||||||
path={`/repos/${repo_ref}/rules`}
|
path={`/repos/${repo_ref}/rules`}
|
||||||
base={getConfig('code/api/v1')}
|
base={getConfig('code/api/v1')}
|
||||||
|
@ -6665,31 +6680,31 @@ export const RuleAdd = ({ repo_ref, ...props }: RuleAddProps) => (
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
export type UseRuleAddProps = Omit<
|
export type UseRepoRuleAddProps = Omit<
|
||||||
UseMutateProps<OpenapiRule, UsererrorError, void, RuleAddRequestBody, RuleAddPathParams>,
|
UseMutateProps<OpenapiRule, UsererrorError, void, RepoRuleAddRequestBody, RepoRuleAddPathParams>,
|
||||||
'path' | 'verb'
|
'path' | 'verb'
|
||||||
> &
|
> &
|
||||||
RuleAddPathParams
|
RepoRuleAddPathParams
|
||||||
|
|
||||||
export const useRuleAdd = ({ repo_ref, ...props }: UseRuleAddProps) =>
|
export const useRepoRuleAdd = ({ repo_ref, ...props }: UseRepoRuleAddProps) =>
|
||||||
useMutate<OpenapiRule, UsererrorError, void, RuleAddRequestBody, RuleAddPathParams>(
|
useMutate<OpenapiRule, UsererrorError, void, RepoRuleAddRequestBody, RepoRuleAddPathParams>(
|
||||||
'POST',
|
'POST',
|
||||||
(paramsInPath: RuleAddPathParams) => `/repos/${paramsInPath.repo_ref}/rules`,
|
(paramsInPath: RepoRuleAddPathParams) => `/repos/${paramsInPath.repo_ref}/rules`,
|
||||||
{ base: getConfig('code/api/v1'), pathParams: { repo_ref }, ...props }
|
{ base: getConfig('code/api/v1'), pathParams: { repo_ref }, ...props }
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface RuleDeletePathParams {
|
export interface RepoRuleDeletePathParams {
|
||||||
repo_ref: string
|
repo_ref: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RuleDeleteProps = Omit<
|
export type RepoRuleDeleteProps = Omit<
|
||||||
MutateProps<void, UsererrorError, void, string, RuleDeletePathParams>,
|
MutateProps<void, UsererrorError, void, string, RepoRuleDeletePathParams>,
|
||||||
'path' | 'verb'
|
'path' | 'verb'
|
||||||
> &
|
> &
|
||||||
RuleDeletePathParams
|
RepoRuleDeletePathParams
|
||||||
|
|
||||||
export const RuleDelete = ({ repo_ref, ...props }: RuleDeleteProps) => (
|
export const RepoRuleDelete = ({ repo_ref, ...props }: RepoRuleDeleteProps) => (
|
||||||
<Mutate<void, UsererrorError, void, string, RuleDeletePathParams>
|
<Mutate<void, UsererrorError, void, string, RepoRuleDeletePathParams>
|
||||||
verb="DELETE"
|
verb="DELETE"
|
||||||
path={`/repos/${repo_ref}/rules`}
|
path={`/repos/${repo_ref}/rules`}
|
||||||
base={getConfig('code/api/v1')}
|
base={getConfig('code/api/v1')}
|
||||||
|
@ -6697,50 +6712,50 @@ export const RuleDelete = ({ repo_ref, ...props }: RuleDeleteProps) => (
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
export type UseRuleDeleteProps = Omit<
|
export type UseRepoRuleDeleteProps = Omit<
|
||||||
UseMutateProps<void, UsererrorError, void, string, RuleDeletePathParams>,
|
UseMutateProps<void, UsererrorError, void, string, RepoRuleDeletePathParams>,
|
||||||
'path' | 'verb'
|
'path' | 'verb'
|
||||||
> &
|
> &
|
||||||
RuleDeletePathParams
|
RepoRuleDeletePathParams
|
||||||
|
|
||||||
export const useRuleDelete = ({ repo_ref, ...props }: UseRuleDeleteProps) =>
|
export const useRepoRuleDelete = ({ repo_ref, ...props }: UseRepoRuleDeleteProps) =>
|
||||||
useMutate<void, UsererrorError, void, string, RuleDeletePathParams>(
|
useMutate<void, UsererrorError, void, string, RepoRuleDeletePathParams>(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
(paramsInPath: RuleDeletePathParams) => `/repos/${paramsInPath.repo_ref}/rules`,
|
(paramsInPath: RepoRuleDeletePathParams) => `/repos/${paramsInPath.repo_ref}/rules`,
|
||||||
{ base: getConfig('code/api/v1'), pathParams: { repo_ref }, ...props }
|
{ base: getConfig('code/api/v1'), pathParams: { repo_ref }, ...props }
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface RuleGetPathParams {
|
export interface RepoRuleGetPathParams {
|
||||||
repo_ref: string
|
repo_ref: string
|
||||||
rule_identifier: string
|
rule_identifier: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RuleGetProps = Omit<GetProps<OpenapiRule, UsererrorError, void, RuleGetPathParams>, 'path'> &
|
export type RepoRuleGetProps = Omit<GetProps<OpenapiRule, UsererrorError, void, RepoRuleGetPathParams>, 'path'> &
|
||||||
RuleGetPathParams
|
RepoRuleGetPathParams
|
||||||
|
|
||||||
export const RuleGet = ({ repo_ref, rule_identifier, ...props }: RuleGetProps) => (
|
export const RepoRuleGet = ({ repo_ref, rule_identifier, ...props }: RepoRuleGetProps) => (
|
||||||
<Get<OpenapiRule, UsererrorError, void, RuleGetPathParams>
|
<Get<OpenapiRule, UsererrorError, void, RepoRuleGetPathParams>
|
||||||
path={`/repos/${repo_ref}/rules/${rule_identifier}`}
|
path={`/repos/${repo_ref}/rules/${rule_identifier}`}
|
||||||
base={getConfig('code/api/v1')}
|
base={getConfig('code/api/v1')}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
export type UseRuleGetProps = Omit<UseGetProps<OpenapiRule, UsererrorError, void, RuleGetPathParams>, 'path'> &
|
export type UseRepoRuleGetProps = Omit<UseGetProps<OpenapiRule, UsererrorError, void, RepoRuleGetPathParams>, 'path'> &
|
||||||
RuleGetPathParams
|
RepoRuleGetPathParams
|
||||||
|
|
||||||
export const useRuleGet = ({ repo_ref, rule_identifier, ...props }: UseRuleGetProps) =>
|
export const useRepoRuleGet = ({ repo_ref, rule_identifier, ...props }: UseRepoRuleGetProps) =>
|
||||||
useGet<OpenapiRule, UsererrorError, void, RuleGetPathParams>(
|
useGet<OpenapiRule, UsererrorError, void, RepoRuleGetPathParams>(
|
||||||
(paramsInPath: RuleGetPathParams) => `/repos/${paramsInPath.repo_ref}/rules/${paramsInPath.rule_identifier}`,
|
(paramsInPath: RepoRuleGetPathParams) => `/repos/${paramsInPath.repo_ref}/rules/${paramsInPath.rule_identifier}`,
|
||||||
{ base: getConfig('code/api/v1'), pathParams: { repo_ref, rule_identifier }, ...props }
|
{ base: getConfig('code/api/v1'), pathParams: { repo_ref, rule_identifier }, ...props }
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface RuleUpdatePathParams {
|
export interface RepoRuleUpdatePathParams {
|
||||||
repo_ref: string
|
repo_ref: string
|
||||||
rule_identifier: string
|
rule_identifier: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuleUpdateRequestBody {
|
export interface RepoRuleUpdateRequestBody {
|
||||||
definition?: OpenapiRuleDefinition
|
definition?: OpenapiRuleDefinition
|
||||||
description?: string | null
|
description?: string | null
|
||||||
identifier?: string | null
|
identifier?: string | null
|
||||||
|
@ -6750,14 +6765,14 @@ export interface RuleUpdateRequestBody {
|
||||||
uid?: string | null
|
uid?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RuleUpdateProps = Omit<
|
export type RepoRuleUpdateProps = Omit<
|
||||||
MutateProps<OpenapiRule, UsererrorError, void, RuleUpdateRequestBody, RuleUpdatePathParams>,
|
MutateProps<OpenapiRule, UsererrorError, void, RepoRuleUpdateRequestBody, RepoRuleUpdatePathParams>,
|
||||||
'path' | 'verb'
|
'path' | 'verb'
|
||||||
> &
|
> &
|
||||||
RuleUpdatePathParams
|
RepoRuleUpdatePathParams
|
||||||
|
|
||||||
export const RuleUpdate = ({ repo_ref, rule_identifier, ...props }: RuleUpdateProps) => (
|
export const RepoRuleUpdate = ({ repo_ref, rule_identifier, ...props }: RepoRuleUpdateProps) => (
|
||||||
<Mutate<OpenapiRule, UsererrorError, void, RuleUpdateRequestBody, RuleUpdatePathParams>
|
<Mutate<OpenapiRule, UsererrorError, void, RepoRuleUpdateRequestBody, RepoRuleUpdatePathParams>
|
||||||
verb="PATCH"
|
verb="PATCH"
|
||||||
path={`/repos/${repo_ref}/rules/${rule_identifier}`}
|
path={`/repos/${repo_ref}/rules/${rule_identifier}`}
|
||||||
base={getConfig('code/api/v1')}
|
base={getConfig('code/api/v1')}
|
||||||
|
@ -6765,16 +6780,16 @@ export const RuleUpdate = ({ repo_ref, rule_identifier, ...props }: RuleUpdatePr
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
export type UseRuleUpdateProps = Omit<
|
export type UseRepoRuleUpdateProps = Omit<
|
||||||
UseMutateProps<OpenapiRule, UsererrorError, void, RuleUpdateRequestBody, RuleUpdatePathParams>,
|
UseMutateProps<OpenapiRule, UsererrorError, void, RepoRuleUpdateRequestBody, RepoRuleUpdatePathParams>,
|
||||||
'path' | 'verb'
|
'path' | 'verb'
|
||||||
> &
|
> &
|
||||||
RuleUpdatePathParams
|
RepoRuleUpdatePathParams
|
||||||
|
|
||||||
export const useRuleUpdate = ({ repo_ref, rule_identifier, ...props }: UseRuleUpdateProps) =>
|
export const useRepoRuleUpdate = ({ repo_ref, rule_identifier, ...props }: UseRepoRuleUpdateProps) =>
|
||||||
useMutate<OpenapiRule, UsererrorError, void, RuleUpdateRequestBody, RuleUpdatePathParams>(
|
useMutate<OpenapiRule, UsererrorError, void, RepoRuleUpdateRequestBody, RepoRuleUpdatePathParams>(
|
||||||
'PATCH',
|
'PATCH',
|
||||||
(paramsInPath: RuleUpdatePathParams) => `/repos/${paramsInPath.repo_ref}/rules/${paramsInPath.rule_identifier}`,
|
(paramsInPath: RepoRuleUpdatePathParams) => `/repos/${paramsInPath.repo_ref}/rules/${paramsInPath.rule_identifier}`,
|
||||||
{ base: getConfig('code/api/v1'), pathParams: { repo_ref, rule_identifier }, ...props }
|
{ base: getConfig('code/api/v1'), pathParams: { repo_ref, rule_identifier }, ...props }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -12287,6 +12287,8 @@ components:
|
||||||
- deleted
|
- deleted
|
||||||
- starting
|
- starting
|
||||||
- stopping
|
- stopping
|
||||||
|
- cleaning
|
||||||
|
- cleaned
|
||||||
type: string
|
type: string
|
||||||
EnumGitspaceOwner:
|
EnumGitspaceOwner:
|
||||||
enum:
|
enum:
|
||||||
|
@ -12416,6 +12418,8 @@ components:
|
||||||
EnumPullReqReviewerType:
|
EnumPullReqReviewerType:
|
||||||
enum:
|
enum:
|
||||||
- assigned
|
- assigned
|
||||||
|
- code_owners
|
||||||
|
- default
|
||||||
- requested
|
- requested
|
||||||
- self_assigned
|
- self_assigned
|
||||||
type: string
|
type: string
|
||||||
|
@ -12468,11 +12472,15 @@ components:
|
||||||
type: string
|
type: string
|
||||||
EnumWebhookParent:
|
EnumWebhookParent:
|
||||||
enum:
|
enum:
|
||||||
|
- registry
|
||||||
- repo
|
- repo
|
||||||
- space
|
- space
|
||||||
type: string
|
type: string
|
||||||
EnumWebhookTrigger:
|
EnumWebhookTrigger:
|
||||||
enum:
|
enum:
|
||||||
|
- artifact_created
|
||||||
|
- artifact_deleted
|
||||||
|
- artifact_updated
|
||||||
- branch_created
|
- branch_created
|
||||||
- branch_deleted
|
- branch_deleted
|
||||||
- branch_updated
|
- branch_updated
|
||||||
|
@ -14439,8 +14447,13 @@ components:
|
||||||
properties:
|
properties:
|
||||||
created:
|
created:
|
||||||
type: integer
|
type: integer
|
||||||
|
deleted:
|
||||||
|
nullable: true
|
||||||
|
type: integer
|
||||||
identifier:
|
identifier:
|
||||||
type: string
|
type: string
|
||||||
|
is_deleted:
|
||||||
|
type: boolean
|
||||||
metadata:
|
metadata:
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
nullable: true
|
nullable: true
|
||||||
|
@ -15229,6 +15242,8 @@ components:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
parent_id:
|
parent_id:
|
||||||
type: integer
|
type: integer
|
||||||
parent_type:
|
parent_type:
|
||||||
|
@ -15469,6 +15484,8 @@ components:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
uid:
|
uid:
|
||||||
type: string
|
type: string
|
||||||
updated:
|
updated:
|
||||||
|
|
|
@ -28,7 +28,8 @@ import type {
|
||||||
TypesCommit,
|
TypesCommit,
|
||||||
TypesPullReq,
|
TypesPullReq,
|
||||||
RepoRepositoryOutput,
|
RepoRepositoryOutput,
|
||||||
TypesRuleViolations
|
TypesRuleViolations,
|
||||||
|
TypesDefaultReviewerApprovalsResponse
|
||||||
} from 'services/code'
|
} from 'services/code'
|
||||||
import { getConfig } from 'services/config'
|
import { getConfig } from 'services/config'
|
||||||
import { PullRequestSection, getErrorMessage } from './Utils'
|
import { PullRequestSection, getErrorMessage } from './Utils'
|
||||||
|
@ -173,7 +174,8 @@ export enum GitRefType {
|
||||||
|
|
||||||
export enum PrincipalUserType {
|
export enum PrincipalUserType {
|
||||||
USER = 'user',
|
USER = 'user',
|
||||||
SERVICE = 'service'
|
SERVICE = 'service',
|
||||||
|
SERVICE_ACCOUNT = 'serviceaccount'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SettingTypeMode {
|
export enum SettingTypeMode {
|
||||||
|
@ -540,9 +542,10 @@ export const dryMerge = (
|
||||||
setMinApproval?: (value: React.SetStateAction<number>) => void,
|
setMinApproval?: (value: React.SetStateAction<number>) => void,
|
||||||
setReqCodeOwnerLatestApproval?: (value: React.SetStateAction<boolean>) => void,
|
setReqCodeOwnerLatestApproval?: (value: React.SetStateAction<boolean>) => void,
|
||||||
setMinReqLatestApproval?: (value: React.SetStateAction<number>) => void,
|
setMinReqLatestApproval?: (value: React.SetStateAction<number>) => void,
|
||||||
setPRStateLoading?: (value: React.SetStateAction<boolean>) => void
|
setPRStateLoading?: (value: React.SetStateAction<boolean>) => void,
|
||||||
|
setDefaultReviewersInfoSet?: React.Dispatch<React.SetStateAction<TypesDefaultReviewerApprovalsResponse[]>>
|
||||||
) => {
|
) => {
|
||||||
if (isMounted.current && !isClosed && pullReqMetadata.state !== PullRequestState.MERGED) {
|
if (isMounted.current && !isClosed && pullReqMetadata?.state !== PullRequestState.MERGED) {
|
||||||
// Use an internal flag to prevent flickering during the loading state of buttons
|
// Use an internal flag to prevent flickering during the loading state of buttons
|
||||||
internalFlags.current.dryRun = true
|
internalFlags.current.dryRun = true
|
||||||
mergePR({ bypass_rules: true, dry_run: true, source_sha: pullReqMetadata?.source_sha })
|
mergePR({ bypass_rules: true, dry_run: true, source_sha: pullReqMetadata?.source_sha })
|
||||||
|
@ -558,6 +561,7 @@ export const dryMerge = (
|
||||||
setReqCodeOwnerLatestApproval?.(res.requires_code_owners_approval_latest)
|
setReqCodeOwnerLatestApproval?.(res.requires_code_owners_approval_latest)
|
||||||
setMinReqLatestApproval?.(res.minimum_required_approvals_count_latest)
|
setMinReqLatestApproval?.(res.minimum_required_approvals_count_latest)
|
||||||
setConflictingFiles?.(res.conflict_files)
|
setConflictingFiles?.(res.conflict_files)
|
||||||
|
setDefaultReviewersInfoSet?.(res.default_reviewer_aprovals)
|
||||||
} else {
|
} else {
|
||||||
setRuleViolation(false)
|
setRuleViolation(false)
|
||||||
setAllowedStrats(res.allowed_methods)
|
setAllowedStrats(res.allowed_methods)
|
||||||
|
@ -568,6 +572,7 @@ export const dryMerge = (
|
||||||
setReqCodeOwnerLatestApproval?.(res.requires_code_owners_approval_latest)
|
setReqCodeOwnerLatestApproval?.(res.requires_code_owners_approval_latest)
|
||||||
setMinReqLatestApproval?.(res.minimum_required_approvals_count_latest)
|
setMinReqLatestApproval?.(res.minimum_required_approvals_count_latest)
|
||||||
setConflictingFiles?.(res.conflict_files)
|
setConflictingFiles?.(res.conflict_files)
|
||||||
|
setDefaultReviewersInfoSet?.(res.default_reviewer_aprovals)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -582,6 +587,7 @@ export const dryMerge = (
|
||||||
setReqCodeOwnerLatestApproval?.(err.requires_code_owners_approval_latest)
|
setReqCodeOwnerLatestApproval?.(err.requires_code_owners_approval_latest)
|
||||||
setMinReqLatestApproval?.(err.minimum_required_approvals_count_latest)
|
setMinReqLatestApproval?.(err.minimum_required_approvals_count_latest)
|
||||||
setConflictingFiles?.(err.conflict_files)
|
setConflictingFiles?.(err.conflict_files)
|
||||||
|
setDefaultReviewersInfoSet?.(err.default_reviewer_aprovals)
|
||||||
} else if (
|
} else if (
|
||||||
err.status === 400 &&
|
err.status === 400 &&
|
||||||
[oldCommitRefetchRequired, prMergedRefetchRequired].includes(getErrorMessage(err) || '')
|
[oldCommitRefetchRequired, prMergedRefetchRequired].includes(getErrorMessage(err) || '')
|
||||||
|
|
|
@ -30,9 +30,11 @@ import type {
|
||||||
TypesLabel,
|
TypesLabel,
|
||||||
TypesLabelValue,
|
TypesLabelValue,
|
||||||
TypesPrincipalInfo,
|
TypesPrincipalInfo,
|
||||||
EnumMembershipRole
|
EnumMembershipRole,
|
||||||
|
TypesDefaultReviewerApprovalsResponse
|
||||||
} from 'services/code'
|
} from 'services/code'
|
||||||
import type { StringKeys } from 'framework/strings'
|
import type { StringKeys } from 'framework/strings'
|
||||||
|
import { PullReqReviewDecision } from 'pages/PullRequest/PullRequestUtils'
|
||||||
|
|
||||||
export enum ACCESS_MODES {
|
export enum ACCESS_MODES {
|
||||||
VIEW,
|
VIEW,
|
||||||
|
@ -361,8 +363,11 @@ export const rulesFormInitialPayload: RulesFormPayload = {
|
||||||
targetList: [] as string[][],
|
targetList: [] as string[][],
|
||||||
allRepoOwners: false,
|
allRepoOwners: false,
|
||||||
bypassList: [] as string[],
|
bypassList: [] as string[],
|
||||||
|
defaultReviewersList: [] as string[],
|
||||||
requireMinReviewers: false,
|
requireMinReviewers: false,
|
||||||
|
requireMinDefaultReviewers: false,
|
||||||
minReviewers: '',
|
minReviewers: '',
|
||||||
|
minDefaultReviewers: '',
|
||||||
requireCodeOwner: false,
|
requireCodeOwner: false,
|
||||||
requireNewChanges: false,
|
requireNewChanges: false,
|
||||||
reqResOfChanges: false,
|
reqResOfChanges: false,
|
||||||
|
@ -380,7 +385,9 @@ export const rulesFormInitialPayload: RulesFormPayload = {
|
||||||
blockForcePush: false,
|
blockForcePush: false,
|
||||||
requirePr: false,
|
requirePr: false,
|
||||||
bypassSet: false,
|
bypassSet: false,
|
||||||
targetSet: false
|
targetSet: false,
|
||||||
|
defaultReviewersSet: false,
|
||||||
|
defaultReviewersEnabled: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RulesFormPayload = {
|
export type RulesFormPayload = {
|
||||||
|
@ -392,8 +399,11 @@ export type RulesFormPayload = {
|
||||||
targetList: string[][]
|
targetList: string[][]
|
||||||
allRepoOwners?: boolean
|
allRepoOwners?: boolean
|
||||||
bypassList?: string[]
|
bypassList?: string[]
|
||||||
|
defaultReviewersList?: string[]
|
||||||
requireMinReviewers: boolean
|
requireMinReviewers: boolean
|
||||||
|
requireMinDefaultReviewers: boolean
|
||||||
minReviewers?: string | number
|
minReviewers?: string | number
|
||||||
|
minDefaultReviewers?: string | number
|
||||||
autoAddCodeOwner?: boolean
|
autoAddCodeOwner?: boolean
|
||||||
requireCodeOwner?: boolean
|
requireCodeOwner?: boolean
|
||||||
requireNewChanges?: boolean
|
requireNewChanges?: boolean
|
||||||
|
@ -414,6 +424,8 @@ export type RulesFormPayload = {
|
||||||
requirePr?: boolean
|
requirePr?: boolean
|
||||||
bypassSet: boolean
|
bypassSet: boolean
|
||||||
targetSet: boolean
|
targetSet: boolean
|
||||||
|
defaultReviewersSet: boolean
|
||||||
|
defaultReviewersEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1002,8 +1014,10 @@ export enum RuleFields {
|
||||||
APPROVALS_REQUIRE_MINIMUM_COUNT = 'pullreq.approvals.require_minimum_count',
|
APPROVALS_REQUIRE_MINIMUM_COUNT = 'pullreq.approvals.require_minimum_count',
|
||||||
APPROVALS_REQUIRE_CODE_OWNERS = 'pullreq.approvals.require_code_owners',
|
APPROVALS_REQUIRE_CODE_OWNERS = 'pullreq.approvals.require_code_owners',
|
||||||
APPROVALS_REQUIRE_NO_CHANGE_REQUEST = 'pullreq.approvals.require_no_change_request',
|
APPROVALS_REQUIRE_NO_CHANGE_REQUEST = 'pullreq.approvals.require_no_change_request',
|
||||||
|
APPROVALS_REQUIRE_MINIMUM_DEFAULT_REVIEWERS = 'pullreq.approvals.require_minimum_default_reviewer_count',
|
||||||
APPROVALS_REQUIRE_LATEST_COMMIT = 'pullreq.approvals.require_latest_commit',
|
APPROVALS_REQUIRE_LATEST_COMMIT = 'pullreq.approvals.require_latest_commit',
|
||||||
AUTO_ADD_CODE_OWNERS = 'pullreq.reviewers.request_code_owners',
|
AUTO_ADD_CODE_OWNERS = 'pullreq.reviewers.request_code_owners',
|
||||||
|
DEFAULT_REVIEWERS_ADDED = 'pullreq.reviewers.default_reviewer_ids',
|
||||||
COMMENTS_REQUIRE_RESOLVE_ALL = 'pullreq.comments.require_resolve_all',
|
COMMENTS_REQUIRE_RESOLVE_ALL = 'pullreq.comments.require_resolve_all',
|
||||||
STATUS_CHECKS_ALL_MUST_SUCCEED = 'pullreq.status_checks.all_must_succeed',
|
STATUS_CHECKS_ALL_MUST_SUCCEED = 'pullreq.status_checks.all_must_succeed',
|
||||||
STATUS_CHECKS_REQUIRE_IDENTIFIERS = 'pullreq.status_checks.require_identifiers',
|
STATUS_CHECKS_REQUIRE_IDENTIFIERS = 'pullreq.status_checks.require_identifiers',
|
||||||
|
@ -1034,10 +1048,12 @@ export type BranchProtectionRulesMapType = Record<string, BranchProtectionRule>
|
||||||
export function createRuleFieldsMap(ruleDefinition: Rule): RuleFieldsMap {
|
export function createRuleFieldsMap(ruleDefinition: Rule): RuleFieldsMap {
|
||||||
const ruleFieldsMap: RuleFieldsMap = {
|
const ruleFieldsMap: RuleFieldsMap = {
|
||||||
[RuleFields.APPROVALS_REQUIRE_MINIMUM_COUNT]: false,
|
[RuleFields.APPROVALS_REQUIRE_MINIMUM_COUNT]: false,
|
||||||
[RuleFields.AUTO_ADD_CODE_OWNERS]: false,
|
|
||||||
[RuleFields.APPROVALS_REQUIRE_CODE_OWNERS]: false,
|
[RuleFields.APPROVALS_REQUIRE_CODE_OWNERS]: false,
|
||||||
[RuleFields.APPROVALS_REQUIRE_NO_CHANGE_REQUEST]: false,
|
[RuleFields.APPROVALS_REQUIRE_NO_CHANGE_REQUEST]: false,
|
||||||
[RuleFields.APPROVALS_REQUIRE_LATEST_COMMIT]: false,
|
[RuleFields.APPROVALS_REQUIRE_LATEST_COMMIT]: false,
|
||||||
|
[RuleFields.APPROVALS_REQUIRE_MINIMUM_DEFAULT_REVIEWERS]: false,
|
||||||
|
[RuleFields.AUTO_ADD_CODE_OWNERS]: false,
|
||||||
|
[RuleFields.DEFAULT_REVIEWERS_ADDED]: false,
|
||||||
[RuleFields.COMMENTS_REQUIRE_RESOLVE_ALL]: false,
|
[RuleFields.COMMENTS_REQUIRE_RESOLVE_ALL]: false,
|
||||||
[RuleFields.STATUS_CHECKS_ALL_MUST_SUCCEED]: false,
|
[RuleFields.STATUS_CHECKS_ALL_MUST_SUCCEED]: false,
|
||||||
[RuleFields.STATUS_CHECKS_REQUIRE_IDENTIFIERS]: false,
|
[RuleFields.STATUS_CHECKS_REQUIRE_IDENTIFIERS]: false,
|
||||||
|
@ -1058,6 +1074,8 @@ export function createRuleFieldsMap(ruleDefinition: Rule): RuleFieldsMap {
|
||||||
typeof ruleDefinition.pullreq.approvals.require_minimum_count === 'number'
|
typeof ruleDefinition.pullreq.approvals.require_minimum_count === 'number'
|
||||||
ruleFieldsMap[RuleFields.APPROVALS_REQUIRE_NO_CHANGE_REQUEST] =
|
ruleFieldsMap[RuleFields.APPROVALS_REQUIRE_NO_CHANGE_REQUEST] =
|
||||||
!!ruleDefinition.pullreq.approvals.require_no_change_request
|
!!ruleDefinition.pullreq.approvals.require_no_change_request
|
||||||
|
ruleFieldsMap[RuleFields.APPROVALS_REQUIRE_MINIMUM_DEFAULT_REVIEWERS] =
|
||||||
|
!!ruleDefinition.pullreq.approvals.require_minimum_default_reviewer_count
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ruleDefinition.pullreq.comments) {
|
if (ruleDefinition.pullreq.comments) {
|
||||||
|
@ -1080,6 +1098,9 @@ export function createRuleFieldsMap(ruleDefinition: Rule): RuleFieldsMap {
|
||||||
|
|
||||||
if (ruleDefinition.pullreq.reviewers) {
|
if (ruleDefinition.pullreq.reviewers) {
|
||||||
ruleFieldsMap[RuleFields.AUTO_ADD_CODE_OWNERS] = !!ruleDefinition.pullreq.reviewers.request_code_owners
|
ruleFieldsMap[RuleFields.AUTO_ADD_CODE_OWNERS] = !!ruleDefinition.pullreq.reviewers.request_code_owners
|
||||||
|
ruleFieldsMap[RuleFields.DEFAULT_REVIEWERS_ADDED] =
|
||||||
|
Array.isArray(ruleDefinition.pullreq.reviewers.default_reviewer_ids) &&
|
||||||
|
ruleDefinition.pullreq.reviewers.default_reviewer_ids.length > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1122,3 +1143,43 @@ export const formatListWithAnd = (list: string[]): string => {
|
||||||
return `${list.slice(0, -1).join(', ')} and ${list[list.length - 1]}`
|
return `${list.slice(0, -1).join(', ')} and ${list[list.length - 1]}`
|
||||||
} else return ''
|
} else return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TypesPrincipalInfoWithReviewDecision extends TypesPrincipalInfo {
|
||||||
|
review_decision?: PullReqReviewDecision
|
||||||
|
review_sha?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypesDefaultReviewerApprovalsResponseWithRevDecision extends TypesDefaultReviewerApprovalsResponse {
|
||||||
|
principals?: TypesPrincipalInfoWithReviewDecision[] | null // Override the 'principals' field
|
||||||
|
}
|
||||||
|
export const getUnifiedDefaultReviewersState = (info: TypesDefaultReviewerApprovalsResponseWithRevDecision[]) => {
|
||||||
|
const defaultReviewState = {
|
||||||
|
defReviewerApprovalRequiredByRule: false,
|
||||||
|
defReviewerLatestApprovalRequiredByRule: false,
|
||||||
|
defReviewerApprovedLatestChanges: true,
|
||||||
|
defReviewerApprovedChanges: true,
|
||||||
|
changesRequestedByDefReviewersArr: [] as TypesPrincipalInfoWithReviewDecision[]
|
||||||
|
}
|
||||||
|
|
||||||
|
info?.forEach(item => {
|
||||||
|
if (item?.minimum_required_count !== undefined && item.minimum_required_count > 0) {
|
||||||
|
defaultReviewState.defReviewerApprovalRequiredByRule = true
|
||||||
|
if (item.current_count !== undefined && item.current_count < item.minimum_required_count) {
|
||||||
|
defaultReviewState.defReviewerApprovedChanges = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item?.minimum_required_count_latest !== undefined && item.minimum_required_count_latest > 0) {
|
||||||
|
defaultReviewState.defReviewerLatestApprovalRequiredByRule = true
|
||||||
|
if (item.current_count !== undefined && item.current_count < item.minimum_required_count_latest) {
|
||||||
|
defaultReviewState.defReviewerApprovedLatestChanges = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item?.principals?.forEach(principal => {
|
||||||
|
if (principal?.review_decision === PullReqReviewDecision.CHANGEREQ)
|
||||||
|
defaultReviewState.changesRequestedByDefReviewersArr.push(principal)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return defaultReviewState
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue