drone/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/ChangesSection.tsx

767 lines
32 KiB
TypeScript

/*
* 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 { Color, FontVariation } from '@harnessio/design-system'
import {
Button,
ButtonSize,
Text,
ButtonVariation,
Container,
Layout,
useToggle,
stringSubstitute
} from '@harnessio/uicore'
import React, { useEffect, useMemo, useState } from 'react'
import cx from 'classnames'
import { Render } from 'react-jsx-match'
import { isEmpty } from 'lodash-es'
import type { IconName } from '@blueprintjs/core'
import { Icon } from '@harnessio/icons'
import { CodeOwnerReqDecision, findChangeReqDecisions, getUnifiedDefaultReviewersState } from 'utils/Utils'
import { CodeOwnerSection } from 'pages/PullRequest/CodeOwners/CodeOwnersOverview'
import { useStrings } from 'framework/strings'
import type {
TypesCodeOwnerEvaluation,
TypesCodeOwnerEvaluationEntry,
TypesPullReq,
TypesPullReqReviewer,
RepoRepositoryOutput,
TypesDefaultReviewerApprovalsResponse
} from 'services/code'
import { capitalizeFirstLetter } from 'pages/PullRequest/Checks/ChecksUtils'
import { defaultReviewerResponseWithDecision, findWaitingDecisions } from 'pages/PullRequest/PullRequestUtils'
import { DefaultReviewersPanel } from 'pages/PullRequest/DefaultReviewers/DefaultReviewersPanel'
import greyCircle from '../../../../../icons/greyCircle.svg?url'
import emptyStatus from '../../../../../icons/emptyStatus.svg?url'
import Success from '../../../../../icons/code-success.svg?url'
import Fail from '../../../../../icons/code-fail.svg?url'
import FailGrey from '../../../../../icons/code-fail-grey.svg?url'
import Timeout from '../../../../../icons/code-timeout.svg?url'
import css from '../PullRequestOverviewPanel.module.scss'
interface ChangesSectionProps {
repoMetadata: RepoRepositoryOutput
pullReqMetadata: TypesPullReq
codeOwners: TypesCodeOwnerEvaluation | null
atLeastOneReviewerRule: boolean
reqCodeOwnerApproval: boolean
minApproval: number
reviewers: TypesPullReqReviewer[] | null
defaultReviewersInfoSet: TypesDefaultReviewerApprovalsResponse[]
minReqLatestApproval: number
reqCodeOwnerLatestApproval: boolean
mergeBlockedRule: boolean
loadingReviewers: boolean
refetchReviewers: () => void
refetchCodeOwners: () => void
}
const ChangesSection = (props: ChangesSectionProps) => {
const {
reviewers: currReviewers,
defaultReviewersInfoSet,
minApproval,
reqCodeOwnerApproval,
repoMetadata,
pullReqMetadata,
codeOwners: currCodeOwners,
atLeastOneReviewerRule,
reqCodeOwnerLatestApproval,
minReqLatestApproval,
loadingReviewers,
mergeBlockedRule,
refetchReviewers,
refetchCodeOwners
} = props
const reqNoChangeReq = atLeastOneReviewerRule
const { getString } = useStrings()
const [headerText, setHeaderText] = useState('')
const [contentText, setContentText] = useState('')
const [color, setColor] = useState('')
const [loading, setLoading] = useState(true)
const [status, setStatus] = useState('tick-circle')
const [isExpanded, toggleExpanded] = useToggle(false)
const [isNotRequiredFlag, setIsNotRequired] = useState(false)
const reviewers = useMemo(() => {
refetchCodeOwners()
return currReviewers // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currReviewers, refetchReviewers, mergeBlockedRule])
const codeOwners = useMemo(() => {
return currCodeOwners // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currCodeOwners, refetchCodeOwners, refetchReviewers])
const checkIfOutdatedSha = (reviewedSHA?: string, sourceSHA?: string) => reviewedSHA !== sourceSHA
const codeOwnerChangeReqEntries = findChangeReqDecisions(
codeOwners?.evaluation_entries,
CodeOwnerReqDecision.CHANGEREQ
)
const codeOwnerApprovalEntries = findChangeReqDecisions(codeOwners?.evaluation_entries, CodeOwnerReqDecision.APPROVED)
const latestCodeOwnerApprovalArr = codeOwnerApprovalEntries
?.map(entry => {
// Filter the owner_evaluations for 'changereq' decisions
const entryEvaluation = entry?.owner_evaluations.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(evaluation: any) => !checkIfOutdatedSha(evaluation?.review_sha, pullReqMetadata?.source_sha as string)
)
// If there are any 'changereq' decisions, return the entry along with them
if (entryEvaluation && entryEvaluation?.length > 0) {
return { entryEvaluation }
}
}) // eslint-disable-next-line @typescript-eslint/no-explicit-any
.filter((entry: any) => entry !== null && entry !== undefined) // Filter out the null entries
const codeOwnerPendingEntries = findWaitingDecisions(
pullReqMetadata,
reqCodeOwnerLatestApproval,
codeOwners?.evaluation_entries
)
const approvedEvaluations = reviewers?.filter(evaluation => evaluation.review_decision === 'approved')
const latestApprovalArr = approvedEvaluations?.filter(
approved => !checkIfOutdatedSha(approved.sha, pullReqMetadata?.source_sha as string)
)
const changeReqEvaluations = reviewers?.filter(evaluation => evaluation.review_decision === 'changereq')
const changeReqReviewer =
changeReqEvaluations && !isEmpty(changeReqEvaluations)
? capitalizeFirstLetter(
changeReqEvaluations[0].reviewer?.display_name || changeReqEvaluations[0].reviewer?.uid || ''
)
: 'Reviewer'
const updatedDefaultApprovalRes = reviewers
? defaultReviewerResponseWithDecision(defaultReviewersInfoSet, reviewers)
: defaultReviewersInfoSet
const {
defReviewerApprovalRequiredByRule,
defReviewerLatestApprovalRequiredByRule,
defReviewerApprovedLatestChanges,
defReviewerApprovedChanges
} = getUnifiedDefaultReviewersState(updatedDefaultApprovalRes)
const extractInfoForCodeOwnerContent = () => {
let statusMessage = ''
let statusColor = 'grey' // Default color for no rules required
let title = ''
let statusIcon = ''
let isNotRequired = false
if (
reqNoChangeReq ||
reqCodeOwnerApproval ||
minApproval > 0 ||
reqCodeOwnerLatestApproval ||
minReqLatestApproval > 0 ||
defReviewerApprovalRequiredByRule ||
defReviewerLatestApprovalRequiredByRule ||
mergeBlockedRule
) {
if (mergeBlockedRule) {
title = getString('changesSection.prMergeBlockedTitle')
// statusMessage = getString('changesSection.prMergeBlockedMessage')
statusColor = Color.RED_700
statusIcon = 'warning-icon'
} else if (codeOwnerChangeReqEntries.length > 0 && (reqCodeOwnerApproval || reqCodeOwnerLatestApproval)) {
title = getString('changesSection.reqChangeFromCodeOwners')
statusMessage = getString('changesSection.codeOwnerReqChanges')
statusColor = Color.RED_700
statusIcon = 'warning-icon'
} else if (changeReqEvaluations && changeReqEvaluations?.length > 0 && reqNoChangeReq) {
title = getString('requestChanges')
statusMessage = getString('pr.requestedChanges', { user: changeReqReviewer })
statusColor = Color.RED_700
statusIcon = 'warning-icon'
} else if (codeOwnerPendingEntries && codeOwnerPendingEntries?.length > 0 && reqCodeOwnerLatestApproval) {
title = getString('changesSection.pendingAppFromCodeOwners')
statusMessage = getString('changesSection.pendingLatestApprovalCodeOwners')
statusColor = Color.ORANGE_500
statusIcon = 'execution-waiting'
} else if (codeOwnerPendingEntries && codeOwnerPendingEntries?.length > 0 && reqCodeOwnerApproval) {
title = getString('changesSection.pendingAppFromCodeOwners')
statusMessage = getString('changesSection.waitingOnCodeOwner')
statusColor = Color.ORANGE_500
statusIcon = 'execution-waiting'
} else if (minReqLatestApproval > 0 && latestApprovalArr && latestApprovalArr?.length < minReqLatestApproval) {
title = getString('changesSection.approvalPending')
statusMessage = getString('changesSection.latestChangesPendingReqRev')
statusColor = Color.ORANGE_500
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) {
title = getString('changesSection.approvalPending')
statusMessage = stringSubstitute(getString('changesSection.waitingOnReviewers'), {
count: approvedEvaluations?.length || '0',
total: minApproval
}) 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
statusIcon = 'execution-waiting'
} else if (reqCodeOwnerLatestApproval && latestCodeOwnerApprovalArr?.length > 0) {
title = getString('changesSection.changesApproved')
statusMessage = getString('changesSection.latestChangesWereAppByCodeOwner')
statusColor = Color.GREEN_700
statusIcon = 'tick-circle'
} else if (reqCodeOwnerApproval && codeOwnerApprovalEntries?.length > 0) {
title = getString('changesSection.changesApproved')
statusMessage = getString('changesSection.changesWereAppByCodeOwner')
statusColor = Color.GREEN_700
statusIcon = 'tick-circle'
} else if (minReqLatestApproval > 0 && latestApprovalArr && latestApprovalArr?.length >= minReqLatestApproval) {
title = getString('changesSection.changesApproved')
statusMessage = getString('changesSection.latestChangesWereApprovedByReq')
statusColor = Color.GREEN_700
statusIcon = 'tick-circle'
} else if (minApproval > 0 && approvedEvaluations && approvedEvaluations?.length >= minApproval) {
title = getString('changesSection.changesApproved')
statusMessage = getString('changesSection.changesWereAppByLatestReqRev')
statusColor = Color.GREEN_700
statusIcon = 'tick-circle'
} else if (approvedEvaluations && approvedEvaluations?.length > 0) {
title = getString('changesSection.changesApproved')
statusMessage = stringSubstitute(getString('changesSection.changesAppByRev')) as string
statusColor = Color.GREEN_700
statusIcon = 'tick-circle'
} else {
title = getString('changesSection.noReviewsReq')
statusMessage = getString('changesSection.pullReqWithoutAnyReviews')
statusColor = Color.GREY_500
statusIcon = 'tick-circle'
}
} else {
// When no rules are required
if (codeOwnerChangeReqEntries && codeOwnerChangeReqEntries?.length > 0) {
title = getString('changesSection.reqChangeFromCodeOwners')
statusMessage = getString('changesSection.codeOwnerReqChanges')
statusIcon = 'warning-icon'
isNotRequired = true
} else if (changeReqEvaluations && changeReqEvaluations?.length > 0) {
title = getString('requestChanges')
statusMessage = getString('pr.requestedChanges', { user: changeReqReviewer })
statusIcon = 'warning-icon'
isNotRequired = true
} else if (approvedEvaluations?.length && approvedEvaluations?.length > 0) {
title = getString('changesSection.changesApproved')
statusMessage = stringSubstitute(getString('changesSection.changesAppByRev')) as string
statusIcon = 'tick-circle'
} else {
title = getString('changesSection.noReviewsReq')
statusMessage = getString('changesSection.pullReqWithoutAnyReviews')
statusIcon = 'tick-circle'
}
}
return { title, statusMessage, statusColor, statusIcon, isNotRequired }
}
useEffect(() => {
setLoading(true)
const { title, statusColor, statusMessage, statusIcon: curStatus, isNotRequired } = extractInfoForCodeOwnerContent()
setHeaderText(title)
setContentText(statusMessage)
setColor(statusColor)
setLoading(false)
setStatus(curStatus)
setIsNotRequired(isNotRequired) // eslint-disable-next-line react-hooks/exhaustive-deps
}, [
currReviewers,
currCodeOwners,
reqNoChangeReq,
reqCodeOwnerApproval,
minApproval,
reqCodeOwnerLatestApproval,
minReqLatestApproval,
refetchReviewers,
refetchCodeOwners,
mergeBlockedRule,
approvedEvaluations
])
function renderCodeOwnerStatus() {
if (codeOwnerPendingEntries?.length > 0 && reqCodeOwnerLatestApproval) {
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.waitingOnLatestCodeOwner')}
</Text>
</Layout.Horizontal>
)
}
if (codeOwnerPendingEntries?.length > 0 && reqCodeOwnerApproval) {
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.waitingOnCodeOwner')}
</Text>
</Layout.Horizontal>
)
}
if (
(codeOwnerApprovalEntries as TypesCodeOwnerEvaluationEntry[])?.length > 0 &&
codeOwnerPendingEntries?.length > 0
) {
return (
<Layout.Horizontal>
<Container padding={{ left: 'large' }}>
<img alt="greyCircle" width={16} height={16} src={greyCircle} />
</Container>
<Text padding={{ left: 'medium' }} className={css.sectionSubheader}>
{getString('changesSection.someChangesWereAppByCodeOwner')}
</Text>
</Layout.Horizontal>
)
}
if (latestCodeOwnerApprovalArr?.length > 0 && reqCodeOwnerLatestApproval) {
return (
<Text
icon={'tick-circle'}
iconProps={{
size: 16,
color: Color.GREEN_700,
padding: { right: 'medium' }
}}
padding={{ left: 'large' }}
className={css.sectionSubheader}>
{getString('changesSection.latestChangesWereAppByCodeOwner')}
</Text>
)
}
if (codeOwnerApprovalEntries?.length > 0 && reqCodeOwnerApproval) {
return (
<Text
icon={'tick-circle'}
iconProps={{
size: 16,
color: Color.GREEN_700,
padding: { right: 'medium' }
}}
padding={{ left: 'large' }}
className={css.sectionSubheader}>
{getString('changesSection.changesWereAppByCodeOwner')}
</Text>
)
}
if (codeOwnerApprovalEntries?.length > 0) {
if (reqCodeOwnerLatestApproval && latestCodeOwnerApprovalArr.length < minReqLatestApproval) {
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.latestChangesPendingReqRev')}
</Text>
</Layout.Horizontal>
)
}
return (
<Text
icon={'tick-circle'}
iconProps={{
size: 16,
color: Color.GREEN_700,
padding: { right: 'medium' }
}}
padding={{ left: 'large' }}
className={css.sectionSubheader}>
{getString('changesSection.changesWereAppByCodeOwner')}
</Text>
)
}
return (
<Layout.Horizontal>
<Container padding={{ left: 'large' }}>
<img alt="greyCircle" width={16} height={16} src={greyCircle} />
</Container>
<Text padding={{ left: 'medium' }} className={css.sectionSubheader}>
{getString('changesSection.noCodeOwnerReviewsReq')}
</Text>
</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 =
!mergeBlockedRule &&
(minApproval > minReqLatestApproval ||
(!isEmpty(approvedEvaluations) && minReqLatestApproval === 0) ||
(minApproval > 0 && minReqLatestApproval === undefined) ||
minReqLatestApproval > 0 ||
!isEmpty(changeReqEvaluations) ||
!isEmpty(codeOwners) ||
!isEmpty(defaultReviewersInfoSet) ||
false)
return (
<Render when={!loading && !loadingReviewers && status}>
<Container className={cx(css.sectionContainer, css.borderContainer)}>
<Layout.Horizontal flex={{ justifyContent: 'space-between' }}>
<Layout.Horizontal flex={{ alignItems: 'center' }}>
{status === 'tick-circle' ? (
<img alt={getString('success')} width={26} height={26} src={Success} />
) : status === 'warning-icon' ? (
<img alt={getString('failed')} width={26} height={26} src={isNotRequiredFlag ? FailGrey : Fail} />
) : status === 'execution-waiting' ? (
<img alt={getString('waiting')} width={27} height={27} src={Timeout} className={css.timeoutIcon} />
) : (
<Icon className={css.statusIcon} name={status as IconName} size={25} color={color} />
)}
<Layout.Vertical padding={{ left: 'medium' }}>
<Text
padding={contentText ? { bottom: 'xsmall' } : undefined}
className={css.sectionTitle}
color={
headerText === getString('changesSection.noReviewsReq')
? color
: headerText === getString('changesSection.changesApproved')
? Color.GREEN_800
: color
}>
{headerText}
</Text>
<Text className={css.sectionSubheader} color={Color.GREY_450} font={{ variation: FontVariation.BODY }}>
{contentText}
</Text>
</Layout.Vertical>
</Layout.Horizontal>
{viewBtn && (
<Button
padding={{ right: 'unset' }}
className={cx(css.blueText, css.buttonPadding)}
variation={ButtonVariation.LINK}
size={ButtonSize.SMALL}
text={getString(isExpanded ? 'showLessMatches' : 'showMoreText')}
onClick={toggleExpanded}
rightIcon={isExpanded ? 'main-chevron-up' : 'main-chevron-down'}
iconProps={{ size: 10, margin: { left: 'xsmall' } }}
/>
)}
</Layout.Horizontal>
</Container>
<Render when={isExpanded}>
<Container className={css.greyContainer}>
{minApproval > minReqLatestApproval ||
(!isEmpty(approvedEvaluations) && minReqLatestApproval === 0) ||
(minApproval > 0 && minReqLatestApproval === undefined && (
<Container padding={{ left: 'xlarge', right: 'small' }} className={css.borderContainer}>
<Layout.Horizontal
className={css.paddingContainer}
flex={{ justifyContent: 'space-between', alignItems: 'center' }}>
{approvedEvaluations && minApproval <= approvedEvaluations?.length ? (
<Text
icon="tick-circle"
iconProps={{ size: 16, color: Color.GREEN_700 }}
padding={{
left: 'large'
}}>
<Text className={cx(css.sectionSubheader, css.sectionPadding)}>
{
stringSubstitute(getString('changesSection.changesApprovedByXReviewers'), {
length: approvedEvaluations?.length || '0'
}) as string
}
</Text>
</Text>
) : (
<Layout.Horizontal>
<Container padding={{ left: 'large' }}>
<img alt="emptyStatus" width={16} height={16} src={emptyStatus} />
</Container>
<Text
className={css.sectionSubheader}
padding={{
left: 'medium'
}}>
{
stringSubstitute(getString('codeOwner.approvalCompleted'), {
count: approvedEvaluations?.length || '0',
total: minApproval
}) as string
}
</Text>
</Layout.Horizontal>
)}
<Container margin={{ bottom: `2px` }} className={css.changeContainerPadding}>
<Container className={css.requiredContainer}>
<Text className={css.requiredText}>{getString('required')}</Text>
</Container>
</Container>
</Layout.Horizontal>
</Container>
))}
{minReqLatestApproval > 0 && (
<Container padding={{ left: 'xlarge', right: 'small' }} className={css.borderContainer}>
<Layout.Horizontal
className={css.paddingContainer}
flex={{ justifyContent: 'space-between', alignItems: 'center' }}>
{latestApprovalArr && minReqLatestApproval <= latestApprovalArr?.length ? (
<Text
icon="tick-circle"
className={css.successIcon}
iconProps={{ size: 16, color: Color.GREEN_700 }}
padding={{
left: 'large'
}}>
<Text
className={css.sectionSubheader}
padding={{
left: 'medium'
}}>
{
stringSubstitute(getString('changesSection.latestChangesApprovedByXReviewers'), {
length: latestApprovalArr?.length || minReqLatestApproval || 0
}) as string
}
</Text>
</Text>
) : (
<Layout.Horizontal>
<Container padding={{ left: 'large' }}>
<img alt="emptyStatus" width={16} height={16} src={emptyStatus} />
</Container>
<Text
className={css.sectionSubheader}
padding={{
left: 'medium'
}}>
{
stringSubstitute(getString('codeOwner.pendingLatestApprovals'), {
count: latestApprovalArr?.length || minReqLatestApproval || 0
}) as string
}
</Text>
</Layout.Horizontal>
)}
<Container margin={{ bottom: `2px` }} className={css.changeContainerPadding}>
<Container className={css.requiredContainer}>
<Text className={css.requiredText}>{getString('required')}</Text>
</Container>
</Container>
</Layout.Horizontal>
</Container>
)}
{!isEmpty(changeReqEvaluations) && (
<Container className={css.borderContainer} padding={{ left: 'xlarge', right: 'small' }}>
<Layout.Horizontal className={css.paddingContainer} flex={{ justifyContent: 'space-between' }}>
<Text
className={cx(
css.sectionSubheader,
{
[css.greyIcon]: !reqNoChangeReq
},
{
[css.redIcon]: reqNoChangeReq
}
)}
icon={'error-transparent-no-outline'}
iconProps={{ size: 17, color: reqNoChangeReq ? Color.RED_600 : '', padding: { right: 'medium' } }}
padding={{ left: 'large' }}>
{getString('pr.requestedChanges', { user: changeReqReviewer })}
</Text>
{reqNoChangeReq && (
<Container className={css.changeContainerPadding}>
<Container className={css.requiredContainer}>
<Text className={css.requiredText}>{getString('required')}</Text>
</Container>
</Container>
)}
</Layout.Horizontal>
</Container>
)}
{!isEmpty(defaultReviewersInfoSet) &&
(defReviewerApprovalRequiredByRule || defReviewerLatestApprovalRequiredByRule) && (
<Container className={css.borderContainer} padding={{ left: 'xlarge', right: 'small' }}>
<Layout.Horizontal className={css.paddingContainer} flex={{ justifyContent: 'space-between' }}>
{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) && (
<Container className={css.borderContainer} padding={{ left: 'xlarge', right: 'small' }}>
<Layout.Horizontal className={css.paddingContainer} flex={{ justifyContent: 'space-between' }}>
{codeOwnerChangeReqEntries && codeOwnerChangeReqEntries?.length > 0 ? (
<Text
className={cx(
css.sectionSubheader,
reqCodeOwnerApproval || reqCodeOwnerLatestApproval ? css.redIcon : css.greyIcon
)}
icon={'error-transparent-no-outline'}
iconProps={{
size: 17,
color: reqCodeOwnerApproval || reqCodeOwnerLatestApproval ? Color.RED_600 : '',
padding: { right: 'medium' }
}}
padding={{ left: 'large' }}>
{getString('changesSection.codeOwnerReqChangesToPr')}
</Text>
) : (
renderCodeOwnerStatus()
)}
{(reqCodeOwnerApproval || reqCodeOwnerLatestApproval) && (
<Container className={css.changeContainerPadding}>
<Container className={css.requiredContainer}>
<Text className={css.requiredText}>{getString('required')}</Text>
</Container>
</Container>
)}
</Layout.Horizontal>
</Container>
)}
{codeOwners && !isEmpty(codeOwners?.evaluation_entries) && (
<Container
className={css.codeOwnerContainer}
padding={{ top: 'small', bottom: 'small', left: 'xxxlarge', right: 'small' }}>
<CodeOwnerSection
data={codeOwners}
pullReqMetadata={pullReqMetadata}
repoMetadata={repoMetadata}
reqCodeOwnerLatestApproval={reqCodeOwnerLatestApproval}
/>
</Container>
)}
</Container>
</Render>
</Render>
)
}
export default ChangesSection