mirror of
https://github.com/harness/drone.git
synced 2025-05-01 21:21:11 +00:00
feat: [code-218]: pr review button
This commit is contained in:
parent
abd3110a5b
commit
c55cb3ae0a
@ -56,3 +56,45 @@
|
||||
.repeatBtn {
|
||||
margin-left: var(--spacing-xsmall) !important;
|
||||
}
|
||||
|
||||
.popover {
|
||||
transform: translateY(5px) !important;
|
||||
|
||||
.menuItem {
|
||||
strong {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
padding-left: 27px;
|
||||
line-height: 16px;
|
||||
margin: 5px 0;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
.menuReviewItem {
|
||||
.reviewIcon {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
padding-left: var(--spacing-small);
|
||||
line-height: 16px;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
&.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
@ -11,5 +11,11 @@ declare const styles: {
|
||||
readonly hideBtn: string
|
||||
readonly refreshIcon: string
|
||||
readonly repeatBtn: string
|
||||
readonly popover: string
|
||||
readonly menuItem: string
|
||||
readonly menuReviewItem: string
|
||||
readonly reviewIcon: string
|
||||
readonly btn: string
|
||||
readonly hide: string
|
||||
}
|
||||
export default styles
|
||||
|
@ -35,7 +35,7 @@ import { useShowRequestError } from 'hooks/useShowRequestError'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||
import { ChangesDropdown } from './ChangesDropdown'
|
||||
import { DiffViewConfiguration } from './DiffViewConfiguration'
|
||||
import { ReviewDecisionButton } from './ReviewDecisionButton/ReviewDecisionButton'
|
||||
import ReviewSplitButton from './ReviewSplitButton/ReviewSplitButton'
|
||||
import css from './Changes.module.scss'
|
||||
|
||||
const STICKY_TOP_POSITION = 64
|
||||
@ -73,6 +73,7 @@ export const Changes: React.FC<ChangesProps> = ({
|
||||
const [lineBreaks, setLineBreaks] = useUserPreference(UserPreference.DIFF_LINE_BREAKS, false)
|
||||
const [diffs, setDiffs] = useState<DiffFileEntry[]>([])
|
||||
const [isSticky, setSticky] = useState(false)
|
||||
|
||||
const {
|
||||
data: rawDiff,
|
||||
error,
|
||||
@ -209,11 +210,16 @@ export const Changes: React.FC<ChangesProps> = ({
|
||||
</Container>
|
||||
<FlexExpander />
|
||||
|
||||
<ReviewDecisionButton
|
||||
<ReviewSplitButton
|
||||
shouldHide={readOnly || pullRequestMetadata?.state === 'merged'}
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
/>
|
||||
{/* <ReviewDecisionButton
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
shouldHide={readOnly || pullRequestMetadata?.state === 'merged'}
|
||||
/>
|
||||
/> */}
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
|
||||
|
@ -0,0 +1,121 @@
|
||||
import {
|
||||
ButtonVariation,
|
||||
Color,
|
||||
Container,
|
||||
Icon,
|
||||
IconName,
|
||||
SplitButton,
|
||||
useToaster,
|
||||
Text,
|
||||
FontVariation,
|
||||
Layout
|
||||
} from '@harness/uicore'
|
||||
import { Menu, PopoverPosition } from '@blueprintjs/core'
|
||||
import cx from 'classnames'
|
||||
import { useMutate } from 'restful-react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import type { EnumPullReqReviewDecision, TypesPullReq } from 'services/code'
|
||||
import type { GitInfoProps } from 'utils/GitUtils'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import css from '../Changes.module.scss'
|
||||
|
||||
interface PrReviewOption {
|
||||
method: EnumPullReqReviewDecision | 'reject'
|
||||
title: string
|
||||
disabled?: boolean
|
||||
icon: IconName
|
||||
color: Color
|
||||
}
|
||||
|
||||
interface ReviewSplitButtonProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||
shouldHide: boolean
|
||||
pullRequestMetadata?: TypesPullReq
|
||||
}
|
||||
const ReviewSplitButton = (props: ReviewSplitButtonProps) => {
|
||||
const { pullRequestMetadata, repoMetadata, shouldHide } = props
|
||||
const { getString } = useStrings()
|
||||
const { showError, showSuccess } = useToaster()
|
||||
|
||||
const prDecisionOptions: PrReviewOption[] = [
|
||||
{
|
||||
method: 'approved',
|
||||
title: getString('approve'),
|
||||
icon: 'tick-circle' as IconName,
|
||||
color: Color.GREEN_700
|
||||
},
|
||||
{
|
||||
method: 'changereq',
|
||||
title: getString('requestChanges'),
|
||||
icon: 'error' as IconName,
|
||||
color: Color.ORANGE_700
|
||||
},
|
||||
{
|
||||
method: 'reject',
|
||||
title: getString('reject'),
|
||||
disabled: true,
|
||||
icon: 'danger-icon' as IconName,
|
||||
color: Color.RED_700
|
||||
}
|
||||
]
|
||||
|
||||
const [decisionOption, setDecisionOption] = useState<PrReviewOption>(prDecisionOptions[0])
|
||||
const { mutate, loading } = useMutate({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata?.number}/reviews`
|
||||
})
|
||||
const submitReview = useCallback(() => {
|
||||
mutate({ decision: decisionOption.method })
|
||||
.then(() => {
|
||||
// setReset(true)
|
||||
showSuccess(getString('pr.reviewSubmitted'))
|
||||
})
|
||||
.catch(exception => showError(getErrorMessage(exception)))
|
||||
}, [decisionOption, mutate, showError, showSuccess, getString])
|
||||
return (
|
||||
<Container className={cx(css.btn, { [css.hide]: shouldHide })}>
|
||||
<SplitButton
|
||||
text={decisionOption.title}
|
||||
disabled={loading}
|
||||
variation={ButtonVariation.SECONDARY}
|
||||
popoverProps={{
|
||||
interactionKind: 'click',
|
||||
usePortal: true,
|
||||
popoverClassName: css.popover,
|
||||
position: PopoverPosition.BOTTOM_RIGHT,
|
||||
transitionDuration: 1000
|
||||
}}
|
||||
onClick={() => {
|
||||
submitReview()
|
||||
}}>
|
||||
{prDecisionOptions.map(option => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={option.method}
|
||||
className={css.menuReviewItem}
|
||||
disabled={option.disabled}
|
||||
text={
|
||||
<Layout.Horizontal>
|
||||
<Icon
|
||||
className={css.reviewIcon}
|
||||
{...(option.icon === 'danger-icon' ? null : { color: option.color })}
|
||||
size={16}
|
||||
name={option.icon}
|
||||
/>
|
||||
<Text flex width={'fit-content'} font={{ variation: FontVariation.BODY }}>
|
||||
{option.title}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
}
|
||||
onClick={() => {
|
||||
setDecisionOption(option)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</SplitButton>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReviewSplitButton
|
@ -29,11 +29,11 @@
|
||||
|
||||
> ::before {
|
||||
position: absolute;
|
||||
top: 34px;
|
||||
top: 28px;
|
||||
left: 15px;
|
||||
content: '';
|
||||
width: 1px;
|
||||
height: 28px;
|
||||
height: 25px;
|
||||
border: 1px dashed var(--grey-200);
|
||||
opacity: 0.7;
|
||||
z-index: 2;
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
> ::after {
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
bottom: -10px;
|
||||
left: 15px;
|
||||
content: '';
|
||||
width: 1px;
|
||||
|
@ -241,6 +241,7 @@ export interface StringsMap {
|
||||
quote: string
|
||||
readMe: string
|
||||
refresh: string
|
||||
reject: string
|
||||
rejected: string
|
||||
remove: string
|
||||
renameFile: string
|
||||
|
@ -224,6 +224,7 @@ open: Open
|
||||
merged: Merged
|
||||
enabled: Enabled
|
||||
closed: Closed
|
||||
reject: Reject
|
||||
rejected: Rejected
|
||||
yours: Yours
|
||||
all: All
|
||||
|
@ -538,7 +538,7 @@ const SystemBox: React.FC<SystemBoxProps> = ({ pullRequestMetadata, commentItems
|
||||
return (
|
||||
<Container>
|
||||
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.mergedBox}>
|
||||
<Container width={24} height={24} className={css.mergeContainer}>
|
||||
<Container margin={{ left: 'xsmall' }} width={24} height={24} className={css.mergeContainer}>
|
||||
<Icon name={CodeIcon.Merged} size={16} color={Color.PURPLE_700} />
|
||||
</Container>
|
||||
|
||||
|
@ -63,6 +63,20 @@
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
.menuReviewItem {
|
||||
strong {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
padding-left: 2px;
|
||||
line-height: 16px;
|
||||
margin: 0px 1px;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btnWrapper {
|
||||
|
@ -11,6 +11,7 @@ declare const styles: {
|
||||
readonly sub: string
|
||||
readonly popover: string
|
||||
readonly menuItem: string
|
||||
readonly menuReviewItem: string
|
||||
readonly btnWrapper: string
|
||||
readonly hasError: string
|
||||
readonly mergeContainer: string
|
||||
|
@ -17,10 +17,17 @@ import { Case, Else, Match, Render, Truthy } from 'react-jsx-match'
|
||||
import { Menu, PopoverPosition, Icon as BIcon } from '@blueprintjs/core'
|
||||
import cx from 'classnames'
|
||||
import ReactTimeago from 'react-timeago'
|
||||
import type { EnumMergeMethod, OpenapiMergePullReq, OpenapiStatePullReqRequest, TypesPullReq } from 'services/code'
|
||||
import type {
|
||||
EnumMergeMethod,
|
||||
EnumPullReqState,
|
||||
OpenapiMergePullReq,
|
||||
OpenapiStatePullReqRequest,
|
||||
TypesPullReq
|
||||
} from 'services/code'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { CodeIcon, GitInfoProps, PullRequestFilterOption, PullRequestState } from 'utils/GitUtils'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import ReviewSplitButton from 'components/Changes/ReviewSplitButton/ReviewSplitButton'
|
||||
import css from './PullRequestActionsBox.module.scss'
|
||||
|
||||
interface PullRequestActionsBoxProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
|
||||
@ -135,46 +142,53 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
||||
/>
|
||||
</Case>
|
||||
<Case val={PullRequestState.OPEN}>
|
||||
<Container
|
||||
inline
|
||||
className={cx({
|
||||
[css.btnWrapper]: mergeOption.method !== 'close',
|
||||
[css.hasError]: mergeable === false
|
||||
})}>
|
||||
<SplitButton
|
||||
text={mergeOption.title}
|
||||
disabled={loading}
|
||||
<Layout.Horizontal>
|
||||
<ReviewSplitButton
|
||||
shouldHide={(pullRequestMetadata?.state as EnumPullReqState) === 'merged'}
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
/>
|
||||
<Container
|
||||
inline
|
||||
padding={{ left: 'medium' }}
|
||||
className={cx({
|
||||
[css.secondaryButton]: mergeOption.method === 'close' || mergeable === false
|
||||
})}
|
||||
variation={
|
||||
mergeOption.method === 'close' || mergeable === false
|
||||
? ButtonVariation.TERTIARY
|
||||
: ButtonVariation.PRIMARY
|
||||
}
|
||||
popoverProps={{
|
||||
interactionKind: 'click',
|
||||
usePortal: true,
|
||||
popoverClassName: css.popover,
|
||||
position: PopoverPosition.BOTTOM_RIGHT,
|
||||
transitionDuration: 1000
|
||||
}}
|
||||
onClick={() => {
|
||||
if (mergeOption.method !== 'close') {
|
||||
const payload: OpenapiMergePullReq = { method: mergeOption.method }
|
||||
|
||||
mergePR(payload)
|
||||
.then(onPRStateChanged)
|
||||
.catch(exception => showError(getErrorMessage(exception)))
|
||||
} else {
|
||||
const payload: OpenapiStatePullReqRequest = { state: 'closed' }
|
||||
|
||||
updatePRState(payload)
|
||||
.then(onPRStateChanged)
|
||||
.catch(exception => showError(getErrorMessage(exception)))
|
||||
[css.btnWrapper]: mergeOption.method !== 'close',
|
||||
[css.hasError]: mergeable === false
|
||||
})}>
|
||||
<SplitButton
|
||||
text={mergeOption.title}
|
||||
disabled={loading}
|
||||
className={cx({
|
||||
[css.secondaryButton]: mergeOption.method === 'close' || mergeable === false
|
||||
})}
|
||||
variation={
|
||||
mergeOption.method === 'close' || mergeable === false
|
||||
? ButtonVariation.TERTIARY
|
||||
: ButtonVariation.PRIMARY
|
||||
}
|
||||
}}>
|
||||
{/* TODO: These two items are used for creating a PR
|
||||
popoverProps={{
|
||||
interactionKind: 'click',
|
||||
usePortal: true,
|
||||
popoverClassName: css.popover,
|
||||
position: PopoverPosition.BOTTOM_RIGHT,
|
||||
transitionDuration: 1000
|
||||
}}
|
||||
onClick={() => {
|
||||
if (mergeOption.method !== 'close') {
|
||||
const payload: OpenapiMergePullReq = { method: mergeOption.method }
|
||||
|
||||
mergePR(payload)
|
||||
.then(onPRStateChanged)
|
||||
.catch(exception => showError(getErrorMessage(exception)))
|
||||
} else {
|
||||
const payload: OpenapiStatePullReqRequest = { state: 'closed' }
|
||||
|
||||
updatePRState(payload)
|
||||
.then(onPRStateChanged)
|
||||
.catch(exception => showError(getErrorMessage(exception)))
|
||||
}
|
||||
}}>
|
||||
{/* TODO: These two items are used for creating a PR
|
||||
<Menu.Item
|
||||
className={css.menuItem}
|
||||
text={
|
||||
@ -197,25 +211,26 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
||||
</>
|
||||
}
|
||||
/> */}
|
||||
{mergeOptions.map(option => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={option.method}
|
||||
className={css.menuItem}
|
||||
disabled={option.disabled}
|
||||
text={
|
||||
<>
|
||||
<BIcon icon={mergeOption.method === option.method ? 'tick' : 'blank'} />
|
||||
<strong>{option.title}</strong>
|
||||
<p>{option.desc}</p>
|
||||
</>
|
||||
}
|
||||
onClick={() => setMergeOption(option)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</SplitButton>
|
||||
</Container>
|
||||
{mergeOptions.map(option => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={option.method}
|
||||
className={css.menuItem}
|
||||
disabled={option.disabled}
|
||||
text={
|
||||
<>
|
||||
<BIcon icon={mergeOption.method === option.method ? 'tick' : 'blank'} />
|
||||
<strong>{option.title}</strong>
|
||||
<p>{option.desc}</p>
|
||||
</>
|
||||
}
|
||||
onClick={() => setMergeOption(option)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</SplitButton>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Case>
|
||||
</Match>
|
||||
</Container>
|
||||
|
@ -172,7 +172,6 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
|
||||
setEditDesc(ACCESS_MODES.EDIT)
|
||||
}}
|
||||
{...permissionProps(permEditResult, standalone)}
|
||||
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
@ -191,8 +190,7 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
|
||||
}}
|
||||
variation={ButtonVariation.SECONDARY}
|
||||
text={getString('delete')}
|
||||
{...permissionProps(permDeleteResult, standalone)}
|
||||
></Button>
|
||||
{...permissionProps(permDeleteResult, standalone)}></Button>
|
||||
</Container>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
|
Loading…
x
Reference in New Issue
Block a user