fix: [CODE-2493] codeowners status in PR overview page (#2833)

* fix: [CODE-2493] resolve comments
* fix: [CODE-2493] fix codeowners status in PR overview page
This commit is contained in:
Ritik Kapoor 2024-10-22 13:29:42 +00:00 committed by Harness
parent 9a12d72a10
commit 32e817888a
7 changed files with 80 additions and 32 deletions

View File

@ -37,6 +37,7 @@ export interface StringsMap {
applyChanges: string applyChanges: string
approve: string approve: string
approved: string approved: string
approvedBy: string
artifacts: string artifacts: string
ascending: string ascending: string
assignPeople: string assignPeople: string
@ -156,6 +157,7 @@ export interface StringsMap {
changeRole: string changeRole: string
changedSinceLastView: string changedSinceLastView: string
changes: string changes: string
changesRequested: string
changesRequestedBy: string changesRequestedBy: string
'changesSection.approvalPending': string 'changesSection.approvalPending': string
'changesSection.changesAppByRev': string 'changesSection.changesAppByRev': string
@ -665,6 +667,8 @@ export interface StringsMap {
optionalExtendedDescription: string optionalExtendedDescription: string
overview: string overview: string
owner: string owner: string
owners: string
ownersHeading: string
pageLoading: string pageLoading: string
pageNotFound: string pageNotFound: string
'pageTitle.accessControl': string 'pageTitle.accessControl': string

View File

@ -1167,11 +1167,15 @@ upload: Upload
unorderedList: Unordered list unorderedList: Unordered list
checklist: Check list checklist: Check list
code: Code code: Code
approvedBy: Approved By
ownersHeading: OWNERS
changesRequestedBy: Changes Requested By
changesRequested: changesRequested
owners: Owners
showMoreText: Show More showMoreText: Show More
addOptionalCommitMessage: Add (optional) commit message addOptionalCommitMessage: Add (optional) commit message
confirmStrat: 'Confirm {{strat}}' confirmStrat: 'Confirm {{strat}}'
waiting: Waiting waiting: Waiting
changesRequestedBy: CHANGES REQUESTED BY
mergeBranchTitle: Merge branch {{branchName}} of {{repoPath}} (#{{prNum}}) mergeBranchTitle: Merge branch {{branchName}} of {{repoPath}} (#{{prNum}})
http: HTTP http: HTTP
ssh: SSH ssh: SSH

View File

@ -39,10 +39,12 @@ import { useShowRequestError } from 'hooks/useShowRequestError'
import type { TypesCodeOwnerEvaluation, TypesCodeOwnerEvaluationEntry } from 'services/code' import type { TypesCodeOwnerEvaluation, TypesCodeOwnerEvaluationEntry } from 'services/code'
import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision' import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision'
import { CodeOwnerReqDecision, findChangeReqDecisions, findWaitingDecisions } from 'utils/Utils' import { CodeOwnerReqDecision, findChangeReqDecisions, findWaitingDecisions } from 'utils/Utils'
import { PullReqReviewDecision } from '../PullRequestUtils'
import css from './CodeOwnersOverview.module.scss' import css from './CodeOwnersOverview.module.scss'
interface ChecksOverviewProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> { interface ChecksOverviewProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> {
prChecksDecisionResult: PRChecksDecisionResult prChecksDecisionResult: PRChecksDecisionResult
reqCodeOwnerLatestApproval: boolean
codeOwners?: TypesCodeOwnerEvaluation codeOwners?: TypesCodeOwnerEvaluation
standalone: boolean standalone: boolean
} }
@ -52,6 +54,7 @@ export function CodeOwnersOverview({
repoMetadata, repoMetadata,
pullReqMetadata, pullReqMetadata,
prChecksDecisionResult, prChecksDecisionResult,
reqCodeOwnerLatestApproval,
standalone standalone
}: ChecksOverviewProps) { }: ChecksOverviewProps) {
const { getString } = useStrings() const { getString } = useStrings()
@ -61,7 +64,11 @@ export function CodeOwnersOverview({
useShowRequestError(error) useShowRequestError(error)
const changeReqEntries = findChangeReqDecisions(codeOwners?.evaluation_entries, CodeOwnerReqDecision.CHANGEREQ) const changeReqEntries = findChangeReqDecisions(codeOwners?.evaluation_entries, CodeOwnerReqDecision.CHANGEREQ)
const waitingEntries = findWaitingDecisions(codeOwners?.evaluation_entries) const waitingEntries = findWaitingDecisions(
codeOwners?.evaluation_entries,
pullReqMetadata,
reqCodeOwnerLatestApproval
)
const approvalEntries = findChangeReqDecisions(codeOwners?.evaluation_entries, CodeOwnerReqDecision.APPROVED) const approvalEntries = findChangeReqDecisions(codeOwners?.evaluation_entries, CodeOwnerReqDecision.APPROVED)
@ -133,6 +140,7 @@ export function CodeOwnersOverview({
interface CodeOwnerSectionsProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> { interface CodeOwnerSectionsProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> {
data: TypesCodeOwnerEvaluation data: TypesCodeOwnerEvaluation
reqCodeOwnerLatestApproval?: boolean
} }
const CodeOwnerSections: React.FC<CodeOwnerSectionsProps> = ({ repoMetadata, pullReqMetadata, data }) => { const CodeOwnerSections: React.FC<CodeOwnerSectionsProps> = ({ repoMetadata, pullReqMetadata, data }) => {
@ -145,7 +153,11 @@ const CodeOwnerSections: React.FC<CodeOwnerSectionsProps> = ({ repoMetadata, pul
) )
} }
export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({ data }) => { export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({
data,
pullReqMetadata,
reqCodeOwnerLatestApproval
}) => {
const { getString } = useStrings() const { getString } = useStrings()
const columns = useMemo( const columns = useMemo(
@ -155,11 +167,15 @@ export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({ data }) =>
id: 'CODE', id: 'CODE',
width: '45%', width: '45%',
sort: true, sort: true,
Header: 'CODE', Header: getString('code'),
accessor: 'CODE', accessor: 'CODE',
Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => { Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => {
return ( return (
<Text lineClamp={1} padding={{ left: 'small', right: 'small' }} color={Color.BLACK}> <Text
lineClamp={1}
padding={{ left: 'small', right: 'small' }}
color={Color.BLACK}
flex={{ justifyContent: 'space-between' }}>
{row.original.pattern} {row.original.pattern}
</Text> </Text>
) )
@ -169,7 +185,7 @@ export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({ data }) =>
id: 'Owners', id: 'Owners',
width: '13%', width: '13%',
sort: true, sort: true,
Header: 'OWNERS', Header: getString('ownersHeading'),
accessor: 'OWNERS', accessor: 'OWNERS',
Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => { Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => {
return ( return (
@ -233,7 +249,7 @@ export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({ data }) =>
accessor: 'ChangesRequested', accessor: 'ChangesRequested',
Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => { Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => {
const changeReqEvaluations = row?.original?.owner_evaluations?.filter( const changeReqEvaluations = row?.original?.owner_evaluations?.filter(
evaluation => evaluation.review_decision === 'changereq' evaluation => evaluation.review_decision === PullReqReviewDecision.CHANGEREQ
) )
return ( return (
<Layout.Horizontal className={css.ownerContainer} spacing="tiny"> <Layout.Horizontal className={css.ownerContainer} spacing="tiny">
@ -279,13 +295,15 @@ export const CodeOwnerSection: React.FC<CodeOwnerSectionsProps> = ({ data }) =>
}, },
{ {
id: 'approvedBy', id: 'approvedBy',
Header: 'APPROVED BY', Header: getString('approvedBy'),
sort: true, sort: true,
width: '15%', width: '15%',
accessor: 'APPROVED BY', accessor: 'APPROVED BY',
Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => { Cell: ({ row }: CellProps<TypesCodeOwnerEvaluationEntry>) => {
const approvedEvaluations = row?.original?.owner_evaluations?.filter( const approvedEvaluations = row?.original?.owner_evaluations?.filter(
evaluation => evaluation.review_decision === 'approved' evaluation =>
evaluation.review_decision === PullReqReviewDecision.APPROVED &&
(reqCodeOwnerLatestApproval ? evaluation.review_sha === pullReqMetadata?.source_sha : true)
) )
return ( return (
<Layout.Horizontal className={css.ownerContainer} spacing="tiny"> <Layout.Horizontal className={css.ownerContainer} spacing="tiny">

View File

@ -122,7 +122,11 @@ const ChangesSection = (props: ChangesSectionProps) => {
}) // eslint-disable-next-line @typescript-eslint/no-explicit-any }) // eslint-disable-next-line @typescript-eslint/no-explicit-any
.filter((entry: any) => entry !== null && entry !== undefined) // Filter out the null entries .filter((entry: any) => entry !== null && entry !== undefined) // Filter out the null entries
const codeOwnerPendingEntries = findWaitingDecisions(codeOwners?.evaluation_entries) const codeOwnerPendingEntries = findWaitingDecisions(
codeOwners?.evaluation_entries,
pullReqMetadata,
reqCodeOwnerLatestApproval
)
const approvedEvaluations = reviewers?.filter(evaluation => evaluation.review_decision === 'approved') const approvedEvaluations = reviewers?.filter(evaluation => evaluation.review_decision === 'approved')
const latestApprovalArr = approvedEvaluations?.filter( const latestApprovalArr = approvedEvaluations?.filter(
approved => !checkIfOutdatedSha(approved.sha, pullReqMetadata?.source_sha as string) approved => !checkIfOutdatedSha(approved.sha, pullReqMetadata?.source_sha as string)
@ -610,7 +614,12 @@ const ChangesSection = (props: ChangesSectionProps) => {
<Container <Container
className={css.codeOwnerContainer} className={css.codeOwnerContainer}
padding={{ top: 'small', bottom: 'small', left: 'xxxlarge', right: 'small' }}> padding={{ top: 'small', bottom: 'small', left: 'xxxlarge', right: 'small' }}>
<CodeOwnerSection data={codeOwners} pullReqMetadata={pullReqMetadata} repoMetadata={repoMetadata} /> <CodeOwnerSection
data={codeOwners}
pullReqMetadata={pullReqMetadata}
repoMetadata={repoMetadata}
reqCodeOwnerLatestApproval={reqCodeOwnerLatestApproval}
/>
</Container> </Container>
)} )}
</Render> </Render>

View File

@ -55,7 +55,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
const { getString } = useStrings() const { getString } = useStrings()
const { showError, showSuccess } = useToaster() const { showError, showSuccess } = useToaster()
const generateReviewDecisionInfo = ( const generateReviewDecisionInfo = (
reviewDecision: EnumPullReqReviewDecision | PullReqReviewDecision.outdated reviewDecision: EnumPullReqReviewDecision | PullReqReviewDecision.OUTDATED
): { ): {
name: IconName name: IconName
color?: Color color?: Color
@ -76,7 +76,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
} }
switch (reviewDecision) { switch (reviewDecision) {
case PullReqReviewDecision.changeReq: case PullReqReviewDecision.CHANGEREQ:
info = { info = {
name: 'error-transparent-no-outline', name: 'error-transparent-no-outline',
color: Color.RED_700, color: Color.RED_700,
@ -87,7 +87,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
message: 'requested changes' message: 'requested changes'
} }
break break
case PullReqReviewDecision.approved: case PullReqReviewDecision.APPROVED:
info = { info = {
name: 'tick-circle', name: 'tick-circle',
color: Color.GREEN_700, color: Color.GREEN_700,
@ -97,7 +97,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
message: 'approved changes' message: 'approved changes'
} }
break break
case PullReqReviewDecision.pending: case PullReqReviewDecision.PENDING:
info = { info = {
name: 'waiting', name: 'waiting',
color: Color.GREY_700, color: Color.GREY_700,
@ -107,7 +107,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
message: 'pending review' message: 'pending review'
} }
break break
case PullReqReviewDecision.outdated: case PullReqReviewDecision.OUTDATED:
info = { info = {
name: 'dot', name: 'dot',
color: Color.GREY_100, color: Color.GREY_100,
@ -273,7 +273,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
</Text> </Text>
} }
tooltipProps={{ isDark: true, interactionKind: PopoverInteractionKind.HOVER }}> tooltipProps={{ isDark: true, interactionKind: PopoverInteractionKind.HOVER }}>
{updatedReviewDecision === PullReqReviewDecision.outdated ? ( {updatedReviewDecision === PullReqReviewDecision.OUTDATED ? (
<img className={css.svgOutdated} src={ignoreFailed} width={20} height={20} /> <img className={css.svgOutdated} src={ignoreFailed} width={20} height={20} />
) : ( ) : (
<Icon {...omit(reviewerInfo, 'iconProps')} /> <Icon {...omit(reviewerInfo, 'iconProps')} />
@ -281,7 +281,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
</Utils.WrapOptionalTooltip> </Utils.WrapOptionalTooltip>
<Avatar <Avatar
className={cx(css.reviewerAvatar, { className={cx(css.reviewerAvatar, {
[css.iconPadding]: updatedReviewDecision !== PullReqReviewDecision.changeReq [css.iconPadding]: updatedReviewDecision !== PullReqReviewDecision.CHANGEREQ
})} })}
name={reviewer.reviewer.display_name} name={reviewer.reviewer.display_name}
size="small" size="small"

View File

@ -49,10 +49,10 @@ export function isSystemComment(commentItems: CommentItem<TypesPullReqActivity>[
} }
export enum PullReqReviewDecision { export enum PullReqReviewDecision {
approved = 'approved', APPROVED = 'approved',
changeReq = 'changereq', CHANGEREQ = 'changereq',
pending = 'pending', PENDING = 'pending',
outdated = 'outdated' OUTDATED = 'outdated'
} }
export const processReviewDecision = ( export const processReviewDecision = (
@ -60,8 +60,8 @@ export const processReviewDecision = (
reviewedSHA?: string, reviewedSHA?: string,
sourceSHA?: string sourceSHA?: string
) => ) =>
review_decision === PullReqReviewDecision.approved && reviewedSHA !== sourceSHA review_decision === PullReqReviewDecision.APPROVED && reviewedSHA !== sourceSHA
? PullReqReviewDecision.outdated ? PullReqReviewDecision.OUTDATED
: review_decision : review_decision
export function getActivePullReqPageSection(): PullRequestSection | undefined { export function getActivePullReqPageSection(): PullRequestSection | undefined {

View File

@ -28,8 +28,11 @@ import type {
TypesCodeOwnerEvaluationEntry, TypesCodeOwnerEvaluationEntry,
RepoRepositoryOutput, RepoRepositoryOutput,
TypesLabel, TypesLabel,
TypesLabelValue TypesLabelValue,
TypesPullReq,
TypesOwnerEvaluation
} from 'services/code' } from 'services/code'
import { PullReqReviewDecision } from 'pages/PullRequest/PullRequestUtils'
export enum ACCESS_MODES { export enum ACCESS_MODES {
VIEW, VIEW,
@ -290,18 +293,28 @@ export const findChangeReqDecisions = (
} }
} }
export const findWaitingDecisions = (entries: TypesCodeOwnerEvaluationEntry[] | null | undefined) => { export const findWaitingDecisions = (
entries: TypesCodeOwnerEvaluationEntry[] | null | undefined,
pullReqMetadata: TypesPullReq,
reqCodeOwnerLatestApproval: boolean
) => {
if (entries === null || entries === undefined) { if (entries === null || entries === undefined) {
return [] return []
} else { } else {
return entries.filter((entry: TypesCodeOwnerEvaluationEntry) => { return entries.filter((entry: TypesCodeOwnerEvaluationEntry) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // skip entry if all the owners have not given any review_decision yet
const hasEmptyDecision = entry?.owner_evaluations?.some((evaluation: any) => evaluation?.review_decision === '') const hasNoReview = entry?.owner_evaluations?.every(
const hasNoApprovedDecision = !entry?.owner_evaluations?.some( (evaluation: TypesOwnerEvaluation | { review_decision: string }) => evaluation.review_decision === ''
evaluation => evaluation.review_decision === 'approved'
) )
if (hasNoReview) return false
return hasEmptyDecision && hasNoApprovedDecision // add entry to waiting decision array if approved changes are outdated or no approvals are found for the given entry
// ( this will hence include pending or change requested entries)
const hasApprovedDecision = entry?.owner_evaluations?.some(
evaluation =>
evaluation.review_decision === PullReqReviewDecision.APPROVED &&
(reqCodeOwnerLatestApproval ? evaluation.review_sha === pullReqMetadata.source_sha : true)
)
return !hasApprovedDecision
}) })
} }
} }