API integration for different type of merges (#268)
Co-authored-by: Tan Nhu <tnhu@users.noreply.github.com>
@ -16,7 +16,7 @@ const ExactSharedPackages = [
|
|||||||
'@harness/monaco-yaml',
|
'@harness/monaco-yaml',
|
||||||
'monaco-editor',
|
'monaco-editor',
|
||||||
'monaco-editor-core',
|
'monaco-editor-core',
|
||||||
'monaco-languages',
|
// 'monaco-languages',
|
||||||
'monaco-plugin-helpers',
|
'monaco-plugin-helpers',
|
||||||
'react-monaco-editor'
|
'react-monaco-editor'
|
||||||
]
|
]
|
||||||
|
@ -37,10 +37,10 @@
|
|||||||
"@blueprintjs/datetime": "3.13.0",
|
"@blueprintjs/datetime": "3.13.0",
|
||||||
"@blueprintjs/select": "3.12.3",
|
"@blueprintjs/select": "3.12.3",
|
||||||
"@harness/design-system": "1.4.0",
|
"@harness/design-system": "1.4.0",
|
||||||
"@harness/icons": "1.95.1",
|
"@harness/icons": "1.101.1",
|
||||||
"@harness/ng-tooltip": ">=1.31.25",
|
"@harness/ng-tooltip": ">=1.31.25",
|
||||||
"@harness/telemetry": ">=1.0.42",
|
"@harness/telemetry": ">=1.0.42",
|
||||||
"@harness/uicore": "3.95.1",
|
"@harness/uicore": "3.106.3",
|
||||||
"@harness/use-modal": "1.3.0",
|
"@harness/use-modal": "1.3.0",
|
||||||
"@popperjs/core": "^2.4.2",
|
"@popperjs/core": "^2.4.2",
|
||||||
"@uiw/react-markdown-editor": "^5.10.1",
|
"@uiw/react-markdown-editor": "^5.10.1",
|
||||||
|
@ -49,6 +49,7 @@ interface ChangesProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
|||||||
emptyMessage: string
|
emptyMessage: string
|
||||||
pullRequestMetadata?: TypesPullReq
|
pullRequestMetadata?: TypesPullReq
|
||||||
className?: string
|
className?: string
|
||||||
|
onCommentUpdate: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Changes: React.FC<ChangesProps> = ({
|
export const Changes: React.FC<ChangesProps> = ({
|
||||||
@ -59,6 +60,7 @@ export const Changes: React.FC<ChangesProps> = ({
|
|||||||
emptyTitle,
|
emptyTitle,
|
||||||
emptyMessage,
|
emptyMessage,
|
||||||
pullRequestMetadata,
|
pullRequestMetadata,
|
||||||
|
onCommentUpdate,
|
||||||
className
|
className
|
||||||
}) => {
|
}) => {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
@ -72,7 +74,9 @@ export const Changes: React.FC<ChangesProps> = ({
|
|||||||
loading,
|
loading,
|
||||||
refetch
|
refetch
|
||||||
} = useGet<string>({
|
} = useGet<string>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/compare/${targetBranch}...${sourceBranch}`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/${
|
||||||
|
pullRequestMetadata ? `pullreq/${pullRequestMetadata.number}/diff` : `compare/${targetBranch}...${sourceBranch}`
|
||||||
|
}`,
|
||||||
lazy: !targetBranch || !sourceBranch
|
lazy: !targetBranch || !sourceBranch
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
@ -214,6 +218,7 @@ export const Changes: React.FC<ChangesProps> = ({
|
|||||||
stickyTopPosition={STICKY_TOP_POSITION}
|
stickyTopPosition={STICKY_TOP_POSITION}
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
pullRequestMetadata={pullRequestMetadata}
|
pullRequestMetadata={pullRequestMetadata}
|
||||||
|
onCommentUpdate={onCommentUpdate}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
|
@ -225,20 +225,20 @@ const CommentsThread = <T = unknown,>({
|
|||||||
title={
|
title={
|
||||||
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
||||||
<Text inline icon="code-chat"></Text>
|
<Text inline icon="code-chat"></Text>
|
||||||
<Avatar name={commentItem.author} size="small" hoverCard={false} />
|
<Avatar name={commentItem?.author} size="small" hoverCard={false} />
|
||||||
<Text inline>
|
<Text inline>
|
||||||
<strong>{commentItem.author}</strong>
|
<strong>{commentItem?.author}</strong>
|
||||||
</Text>
|
</Text>
|
||||||
<PipeSeparator height={8} />
|
<PipeSeparator height={8} />
|
||||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
||||||
<ReactTimeago date={new Date(commentItem.updated)} />
|
<ReactTimeago date={new Date(commentItem?.updated)} />
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Render when={commentItem.updated !== commentItem.created || !!commentItem.deleted}>
|
<Render when={commentItem?.updated !== commentItem?.created || !!commentItem?.deleted}>
|
||||||
<>
|
<>
|
||||||
<PipeSeparator height={8} />
|
<PipeSeparator height={8} />
|
||||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
||||||
{getString(commentItem.deleted ? 'deleted' : 'edited')}
|
{getString(commentItem?.deleted ? 'deleted' : 'edited')}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
</Render>
|
</Render>
|
||||||
@ -249,7 +249,7 @@ const CommentsThread = <T = unknown,>({
|
|||||||
icon="Options"
|
icon="Options"
|
||||||
iconProps={{ size: 14 }}
|
iconProps={{ size: 14 }}
|
||||||
style={{ padding: '5px' }}
|
style={{ padding: '5px' }}
|
||||||
disabled={!!commentItem.deleted}
|
disabled={!!commentItem?.deleted}
|
||||||
width="100px"
|
width="100px"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
@ -258,7 +258,7 @@ const CommentsThread = <T = unknown,>({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: getString('quote'),
|
text: getString('quote'),
|
||||||
onClick: () => onQuote(commentItem.content)
|
onClick: () => onQuote(commentItem?.content)
|
||||||
},
|
},
|
||||||
'-',
|
'-',
|
||||||
{
|
{
|
||||||
@ -309,12 +309,12 @@ const CommentsThread = <T = unknown,>({
|
|||||||
</Container>
|
</Container>
|
||||||
</Truthy>
|
</Truthy>
|
||||||
<Else>
|
<Else>
|
||||||
<Match expr={commentItem.deleted}>
|
<Match expr={commentItem?.deleted}>
|
||||||
<Truthy>
|
<Truthy>
|
||||||
<Text className={css.deleted}>{getString('commentDeleted')}</Text>
|
<Text className={css.deleted}>{getString('commentDeleted')}</Text>
|
||||||
</Truthy>
|
</Truthy>
|
||||||
<Else>
|
<Else>
|
||||||
<MarkdownEditor.Markdown source={commentItem.content} />
|
<MarkdownEditor.Markdown source={commentItem?.content} />
|
||||||
</Else>
|
</Else>
|
||||||
</Match>
|
</Match>
|
||||||
</Else>
|
</Else>
|
||||||
|
@ -6,4 +6,11 @@
|
|||||||
.description textarea {
|
.description textarea {
|
||||||
height: 300px !important;
|
height: 300px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
color: var(--grey-600);
|
||||||
|
font-size: var(--form-input-font-size) !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,6 @@ declare const styles: {
|
|||||||
readonly main: string
|
readonly main: string
|
||||||
readonly title: string
|
readonly title: string
|
||||||
readonly description: string
|
readonly description: string
|
||||||
|
readonly checkbox: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dialog, Intent } from '@blueprintjs/core'
|
import { Dialog, Intent } from '@blueprintjs/core'
|
||||||
import * as yup from 'yup'
|
import * as yup from 'yup'
|
||||||
|
import { Render } from 'react-jsx-match'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonProps,
|
ButtonProps,
|
||||||
@ -35,6 +36,7 @@ import css from './CreatePullRequestModal.module.scss'
|
|||||||
interface FormData {
|
interface FormData {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
|
draft: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreatePullRequestModalProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
interface CreatePullRequestModalProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||||
@ -65,7 +67,8 @@ export function useCreatePullRequestModal({
|
|||||||
target_branch: targetGitRef,
|
target_branch: targetGitRef,
|
||||||
source_branch: sourceGitRef,
|
source_branch: sourceGitRef,
|
||||||
title: title,
|
title: title,
|
||||||
description: description
|
description: description,
|
||||||
|
is_draft: formData.draft
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -97,7 +100,8 @@ export function useCreatePullRequestModal({
|
|||||||
<Formik<FormData>
|
<Formik<FormData>
|
||||||
initialValues={{
|
initialValues={{
|
||||||
title: '',
|
title: '',
|
||||||
description: ''
|
description: '',
|
||||||
|
draft: false
|
||||||
}}
|
}}
|
||||||
formName="createPullRequest"
|
formName="createPullRequest"
|
||||||
enableReinitialize={true}
|
enableReinitialize={true}
|
||||||
@ -129,6 +133,7 @@ export function useCreatePullRequestModal({
|
|||||||
className={css.description}
|
className={css.description}
|
||||||
maxLength={1024 * 50}
|
maxLength={1024 * 50}
|
||||||
/>
|
/>
|
||||||
|
<FormInput.CheckBox label={getString('pr.createDraftPR')} name="draft" className={css.checkbox} />
|
||||||
|
|
||||||
<Layout.Horizontal
|
<Layout.Horizontal
|
||||||
spacing="small"
|
spacing="small"
|
||||||
@ -143,7 +148,9 @@ export function useCreatePullRequestModal({
|
|||||||
<Button text={getString('cancel')} variation={ButtonVariation.LINK} onClick={hideModal} />
|
<Button text={getString('cancel')} variation={ButtonVariation.LINK} onClick={hideModal} />
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
|
|
||||||
{loading && <Icon intent={Intent.PRIMARY} name="spinner" size={16} />}
|
<Render when={loading}>
|
||||||
|
<Icon intent={Intent.PRIMARY} name="spinner" size={16} />
|
||||||
|
</Render>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</FormikForm>
|
</FormikForm>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
@ -49,6 +49,7 @@ interface DiffViewerProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
|||||||
stickyTopPosition?: number
|
stickyTopPosition?: number
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
pullRequestMetadata?: TypesPullReq
|
pullRequestMetadata?: TypesPullReq
|
||||||
|
onCommentUpdate: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -62,7 +63,8 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
|||||||
stickyTopPosition = 0,
|
stickyTopPosition = 0,
|
||||||
readOnly,
|
readOnly,
|
||||||
repoMetadata,
|
repoMetadata,
|
||||||
pullRequestMetadata
|
pullRequestMetadata,
|
||||||
|
onCommentUpdate
|
||||||
}) => {
|
}) => {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const [viewed, setViewed] = useState(false)
|
const [viewed, setViewed] = useState(false)
|
||||||
@ -379,6 +381,10 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
onCommentUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
return [result, updatedItem]
|
return [result, updatedItem]
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
|
@ -16,7 +16,9 @@ export enum CommentType {
|
|||||||
CODE_COMMENT = 'code-comment',
|
CODE_COMMENT = 'code-comment',
|
||||||
TITLE_CHANGE = 'title-change',
|
TITLE_CHANGE = 'title-change',
|
||||||
REVIEW_SUBMIT = 'review-submit',
|
REVIEW_SUBMIT = 'review-submit',
|
||||||
MERGE = 'merge'
|
MERGE = 'merge',
|
||||||
|
BRANCH_UPDATE = 'branch-update',
|
||||||
|
STATE_CHANGE = 'state-change'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PR_CODE_COMMENT_PAYLOAD_VERSION = '0.1'
|
export const PR_CODE_COMMENT_PAYLOAD_VERSION = '0.1'
|
||||||
@ -194,7 +196,7 @@ export const activityToCommentItem = (activity: TypesPullReqActivity): CommentIt
|
|||||||
created: activity.created as number,
|
created: activity.created as number,
|
||||||
updated: activity.edited as number,
|
updated: activity.edited as number,
|
||||||
deleted: activity.deleted as number,
|
deleted: activity.deleted as number,
|
||||||
content: (activity.text || activity.payload?.Message) as string,
|
content: (activity.text || (activity.payload as Unknown)?.Message) as string,
|
||||||
payload: activity
|
payload: activity
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -85,8 +85,14 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
.wmde-markdown .anchor {
|
.wmde-markdown {
|
||||||
display: none;
|
.anchor {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ import { Button, Container, ButtonVariation, NoDataCard, IconName } from '@harne
|
|||||||
import { noop } from 'lodash-es'
|
import { noop } from 'lodash-es'
|
||||||
import { CodeIcon } from 'utils/GitUtils'
|
import { CodeIcon } from 'utils/GitUtils'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import emptyStateImage from 'images/empty-state.svg'
|
import { Images } from 'images'
|
||||||
import css from './NoResultCard.module.scss'
|
import css from './NoResultCard.module.scss'
|
||||||
|
|
||||||
interface NoResultCardProps {
|
interface NoResultCardProps {
|
||||||
showWhen: () => boolean
|
showWhen?: () => boolean
|
||||||
forSearch: boolean
|
forSearch: boolean
|
||||||
title?: string
|
title?: string
|
||||||
message?: string
|
message?: string
|
||||||
@ -18,7 +18,7 @@ interface NoResultCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const NoResultCard: React.FC<NoResultCardProps> = ({
|
export const NoResultCard: React.FC<NoResultCardProps> = ({
|
||||||
showWhen,
|
showWhen = () => true,
|
||||||
forSearch,
|
forSearch,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
@ -36,7 +36,7 @@ export const NoResultCard: React.FC<NoResultCardProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Container className={css.main}>
|
<Container className={css.main}>
|
||||||
<NoDataCard
|
<NoDataCard
|
||||||
image={emptyStateImage}
|
image={Images.EmptyState}
|
||||||
messageTitle={forSearch ? title || getString('noResultTitle') : undefined}
|
messageTitle={forSearch ? title || getString('noResultTitle') : undefined}
|
||||||
message={
|
message={
|
||||||
forSearch ? emptySearchMessage || getString('noResultMessage') : message || getString('noResultMessage')
|
forSearch ? emptySearchMessage || getString('noResultMessage') : message || getString('noResultMessage')
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
.state {
|
|
||||||
--color: var(--green-700) !important;
|
|
||||||
--bg: var(--green-50) !important;
|
|
||||||
|
|
||||||
color: var(--color) !important;
|
|
||||||
background-color: var(--bg) !important;
|
|
||||||
font-size: var(--font-size-small) !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
padding: 4px 8px !important;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&.merged {
|
|
||||||
--color: var(--purple-700) !important;
|
|
||||||
--bg: var(--purple-50) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.closed {
|
|
||||||
--color: var(--grey-700) !important;
|
|
||||||
--bg: var(--grey-100) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.rejected {
|
|
||||||
--color: var(--red-700) !important;
|
|
||||||
--bg: var(--red-50) !important;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Text, Color, StringSubstitute, IconName } from '@harness/uicore'
|
|
||||||
import cx from 'classnames'
|
|
||||||
import { CodeIcon, PullRequestState } from 'utils/GitUtils'
|
|
||||||
import { useStrings } from 'framework/strings'
|
|
||||||
import css from './PRStateLabel.module.scss'
|
|
||||||
|
|
||||||
export const PRStateLabel: React.FC<{ state: PullRequestState }> = ({ state }) => {
|
|
||||||
const { getString } = useStrings()
|
|
||||||
|
|
||||||
let color = Color.GREEN_700
|
|
||||||
let icon: IconName = CodeIcon.PullRequest
|
|
||||||
let clazz: typeof css | string = ''
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case PullRequestState.MERGED:
|
|
||||||
color = Color.PURPLE_700
|
|
||||||
icon = CodeIcon.PullRequest
|
|
||||||
clazz = css.merged
|
|
||||||
break
|
|
||||||
case PullRequestState.CLOSED:
|
|
||||||
color = Color.GREY_600
|
|
||||||
icon = CodeIcon.PullRequest
|
|
||||||
clazz = css.closed
|
|
||||||
break
|
|
||||||
case PullRequestState.REJECTED:
|
|
||||||
color = Color.RED_600
|
|
||||||
icon = CodeIcon.PullRequestRejected
|
|
||||||
clazz = css.rejected
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text inline className={cx(css.state, clazz)} icon={icon} iconProps={{ color, size: 9 }}>
|
|
||||||
<StringSubstitute str={getString('pr.state')} vars={{ state }} />
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
@ -0,0 +1,58 @@
|
|||||||
|
.prStatus {
|
||||||
|
--fg: var(--green-500);
|
||||||
|
--bg: var(--green-50);
|
||||||
|
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
line-height: 12px !important;
|
||||||
|
|
||||||
|
color: var(--fg) !important;
|
||||||
|
background-color: var(--bg) !important;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.iconOnly {
|
||||||
|
padding: 5px !important;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
path {
|
||||||
|
fill: var(--fg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
g {
|
||||||
|
stroke: var(--fg) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
--fg: var(--green-700);
|
||||||
|
--bg: var(--green-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.merged {
|
||||||
|
--fg: var(--blue-800);
|
||||||
|
--bg: var(--blue-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closed {
|
||||||
|
--fg: var(--grey-600);
|
||||||
|
--bg: var(--grey-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.draft {
|
||||||
|
--fg: var(--orange-900);
|
||||||
|
--bg: var(--orange-100);
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly state: string
|
readonly prStatus: string
|
||||||
|
readonly iconOnly: string
|
||||||
|
readonly open: string
|
||||||
readonly merged: string
|
readonly merged: string
|
||||||
readonly closed: string
|
readonly closed: string
|
||||||
readonly rejected: string
|
readonly draft: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Text, StringSubstitute, IconName } from '@harness/uicore'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import { CodeIcon } from 'utils/GitUtils'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import type { TypesPullReq } from 'services/code'
|
||||||
|
import css from './PullRequestStateLabel.module.scss'
|
||||||
|
|
||||||
|
export const PullRequestStateLabel: React.FC<{ data: TypesPullReq; iconSize?: number; iconOnly?: boolean }> = ({
|
||||||
|
data,
|
||||||
|
iconSize = 20,
|
||||||
|
iconOnly = false
|
||||||
|
}) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const maps = {
|
||||||
|
open: {
|
||||||
|
icon: CodeIcon.PullRequest,
|
||||||
|
css: css.open
|
||||||
|
},
|
||||||
|
merged: {
|
||||||
|
icon: CodeIcon.Merged,
|
||||||
|
css: css.merged
|
||||||
|
},
|
||||||
|
closed: {
|
||||||
|
icon: CodeIcon.Merged,
|
||||||
|
css: css.closed
|
||||||
|
},
|
||||||
|
draft: {
|
||||||
|
icon: CodeIcon.Draft,
|
||||||
|
css: css.draft
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
icon: CodeIcon.PullRequest,
|
||||||
|
css: css.open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const map = data.is_draft ? maps.draft : maps[data.state || 'unknown']
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
tag="span"
|
||||||
|
className={cx(css.prStatus, map.css, { [css.iconOnly]: iconOnly })}
|
||||||
|
icon={map.icon as IconName}
|
||||||
|
iconProps={{ size: iconOnly ? iconSize : 12 }}>
|
||||||
|
{!iconOnly && (
|
||||||
|
<StringSubstitute str={getString('pr.state')} vars={{ state: data.is_draft ? 'draft' : data.state }} />
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
@ -67,9 +67,9 @@ export default function MonacoSourceCodeEditor({
|
|||||||
const scrollbar = autoHeight ? 'hidden' : 'auto'
|
const scrollbar = autoHeight ? 'hidden' : 'auto'
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions)
|
monaco.languages.typescript?.typescriptDefaults?.setDiagnosticsOptions?.(diagnosticsOptions)
|
||||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions)
|
monaco.languages.typescript?.javascriptDefaults?.setDiagnosticsOptions?.(diagnosticsOptions)
|
||||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions)
|
monaco.languages.typescript?.typescriptDefaults?.setCompilerOptions?.(compilerOptions)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
.main {
|
||||||
|
:global {
|
||||||
|
.wmde-markdown {
|
||||||
|
pre {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// this is an auto-generated file
|
||||||
|
declare const styles: {
|
||||||
|
readonly main: string
|
||||||
|
}
|
||||||
|
export default styles
|
@ -4,6 +4,7 @@ import MarkdownEditor from '@uiw/react-markdown-editor'
|
|||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor'
|
import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor'
|
||||||
import type { SourceCodeEditorProps } from 'utils/Utils'
|
import type { SourceCodeEditorProps } from 'utils/Utils'
|
||||||
|
import css from './SourceCodeViewer.module.scss'
|
||||||
|
|
||||||
interface MarkdownViewerProps {
|
interface MarkdownViewerProps {
|
||||||
source: string
|
source: string
|
||||||
@ -13,7 +14,7 @@ export function MarkdownViewer({ source }: MarkdownViewerProps) {
|
|||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container className={css.main}>
|
||||||
<Suspense fallback={<Text>{getString('loading')}</Text>}>
|
<Suspense fallback={<Text>{getString('loading')}</Text>}>
|
||||||
<MarkdownEditor.Markdown
|
<MarkdownEditor.Markdown
|
||||||
source={source}
|
source={source}
|
||||||
|
@ -80,6 +80,7 @@ export interface StringsMap {
|
|||||||
deployKeys: string
|
deployKeys: string
|
||||||
description: string
|
description: string
|
||||||
diff: string
|
diff: string
|
||||||
|
draft: string
|
||||||
edit: string
|
edit: string
|
||||||
editFile: string
|
editFile: string
|
||||||
editNotAllowed: string
|
editNotAllowed: string
|
||||||
@ -149,9 +150,11 @@ export interface StringsMap {
|
|||||||
payloadUrl: string
|
payloadUrl: string
|
||||||
payloadUrlLabel: string
|
payloadUrlLabel: string
|
||||||
'pr.ableToMerge': string
|
'pr.ableToMerge': string
|
||||||
|
'pr.authorCommentedPR': string
|
||||||
'pr.branchHasNoConflicts': string
|
'pr.branchHasNoConflicts': string
|
||||||
'pr.buttonText': string
|
'pr.buttonText': string
|
||||||
'pr.cantMerge': string
|
'pr.cantMerge': string
|
||||||
|
'pr.createDraftPR': string
|
||||||
'pr.descriptionPlaceHolder': string
|
'pr.descriptionPlaceHolder': string
|
||||||
'pr.diffStatsLabel': string
|
'pr.diffStatsLabel': string
|
||||||
'pr.diffStatus': string
|
'pr.diffStatus': string
|
||||||
@ -164,12 +167,25 @@ export interface StringsMap {
|
|||||||
'pr.failedToUpdateTitle': string
|
'pr.failedToUpdateTitle': string
|
||||||
'pr.fileDeleted': string
|
'pr.fileDeleted': string
|
||||||
'pr.fileUnchanged': string
|
'pr.fileUnchanged': string
|
||||||
|
'pr.mergeOptions.close': string
|
||||||
|
'pr.mergeOptions.closeDesc': string
|
||||||
|
'pr.mergeOptions.createMergeCommit': string
|
||||||
|
'pr.mergeOptions.createMergeCommitDesc': string
|
||||||
|
'pr.mergeOptions.rebaseAndMerge': string
|
||||||
|
'pr.mergeOptions.rebaseAndMergeDesc': string
|
||||||
|
'pr.mergeOptions.squashAndMerge': string
|
||||||
|
'pr.mergeOptions.squashAndMergeDesc': string
|
||||||
'pr.mergePR': string
|
'pr.mergePR': string
|
||||||
'pr.metaLine': string
|
'pr.metaLine': string
|
||||||
'pr.modalTitle': string
|
'pr.modalTitle': string
|
||||||
|
'pr.openForReview': string
|
||||||
|
'pr.prBranchPushInfo': string
|
||||||
'pr.prCanBeMerged': string
|
'pr.prCanBeMerged': string
|
||||||
'pr.prMerged': string
|
'pr.prMerged': string
|
||||||
'pr.prMergedInfo': string
|
'pr.prMergedInfo': string
|
||||||
|
'pr.prStateChanged': string
|
||||||
|
'pr.prStateChangedDraft': string
|
||||||
|
'pr.readyForReview': string
|
||||||
'pr.reviewChanges': string
|
'pr.reviewChanges': string
|
||||||
'pr.reviewSubmitted': string
|
'pr.reviewSubmitted': string
|
||||||
'pr.showDiff': string
|
'pr.showDiff': string
|
||||||
@ -181,6 +197,8 @@ export interface StringsMap {
|
|||||||
'pr.titleChangedTable': string
|
'pr.titleChangedTable': string
|
||||||
'pr.titlePlaceHolder': string
|
'pr.titlePlaceHolder': string
|
||||||
'pr.unified': string
|
'pr.unified': string
|
||||||
|
'prState.draftDesc': string
|
||||||
|
'prState.draftHeading': string
|
||||||
prefixBase: string
|
prefixBase: string
|
||||||
prefixCompare: string
|
prefixCompare: string
|
||||||
prev: string
|
prev: string
|
||||||
|
@ -156,6 +156,7 @@ enableSSLVerification: 'Enable SSL verification'
|
|||||||
createWebhook: Create Webhook
|
createWebhook: Create Webhook
|
||||||
webhook: Webhook
|
webhook: Webhook
|
||||||
diff: Diff
|
diff: Diff
|
||||||
|
draft: Draft
|
||||||
conversation: Conversation
|
conversation: Conversation
|
||||||
pr:
|
pr:
|
||||||
ableToMerge: Able to merge.
|
ableToMerge: Able to merge.
|
||||||
@ -164,8 +165,9 @@ pr:
|
|||||||
titlePlaceHolder: Enter the pull request title
|
titlePlaceHolder: Enter the pull request title
|
||||||
descriptionPlaceHolder: Leave pull request comment here
|
descriptionPlaceHolder: Leave pull request comment here
|
||||||
modalTitle: Open a pull request
|
modalTitle: Open a pull request
|
||||||
buttonText: Open pull request
|
buttonText: Create pull request
|
||||||
metaLine: '{user} wants to merge {number} {number|1:commit,commits} into {target} from {source}'
|
createDraftPR: Create draft pull request
|
||||||
|
metaLine: '{user} wants to merge {commits} {commitsCount|1:commit,commits} into {target} from {source}'
|
||||||
state: '{state|closed:Closed,merged:Merged,rejected:Rejected,draft:Draft,Open}'
|
state: '{state|closed:Closed,merged:Merged,rejected:Rejected,draft:Draft,Open}'
|
||||||
statusLine: '#{number} {state|merged:merged,closed:closed,rejected:rejected,opened} {time} by {user}'
|
statusLine: '#{number} {state|merged:merged,closed:closed,rejected:rejected,opened} {time} by {user}'
|
||||||
diffStatus: '{status|deleted:Deleted,new:Added,renamed:Renamed,copied:Copied,Changed}'
|
diffStatus: '{status|deleted:Deleted,new:Added,renamed:Renamed,copied:Copied,Changed}'
|
||||||
@ -189,11 +191,29 @@ pr:
|
|||||||
prMerged: This Pull Request was merged
|
prMerged: This Pull Request was merged
|
||||||
reviewSubmitted: Review submitted.
|
reviewSubmitted: Review submitted.
|
||||||
prMergedInfo: '{user} merged branch {source} into {target} {time}.'
|
prMergedInfo: '{user} merged branch {source} into {target} {time}.'
|
||||||
|
prBranchPushInfo: '{user} pushed a new commit {commit}.'
|
||||||
|
prStateChanged: '{user} changed pull request state from {old} to {new}.'
|
||||||
|
prStateChangedDraft: '{user} opened pull request for review.'
|
||||||
titleChanged: '{user} changed title from {old} to {new}.'
|
titleChanged: '{user} changed title from {old} to {new}.'
|
||||||
titleChangedTable: |
|
titleChangedTable: |
|
||||||
### Other title changes in history
|
### Other title changes in history
|
||||||
| Author | Old Name | New Name | Date |
|
| Author | Old Name | New Name | Date |
|
||||||
| ----------- | -------- | -------- | ---- |
|
| ----------- | -------- | -------- | ---- |
|
||||||
|
readyForReview: Ready for review
|
||||||
|
openForReview: Open for review
|
||||||
|
authorCommentedPR: '{author} submitted this pull request {time}'
|
||||||
|
mergeOptions:
|
||||||
|
squashAndMerge: Squash and merge
|
||||||
|
squashAndMergeDesc: All commits from this branch will be combined into one commit in the base branch.
|
||||||
|
createMergeCommit: Create a merge commit
|
||||||
|
createMergeCommitDesc: All commits from this branch will be added to the base branch via a merge commit.
|
||||||
|
rebaseAndMerge: Rebase and merge
|
||||||
|
rebaseAndMergeDesc: All commits from this branch will be rebased and added to the base branch.
|
||||||
|
close: Close pull request
|
||||||
|
closeDesc: Close this pull request. You can still re-open the request after closing.
|
||||||
|
prState:
|
||||||
|
draftHeading: This pull request is still a work in progress
|
||||||
|
draftDesc: Draft pull requests cannot be merged.
|
||||||
webhookListingContent: 'create,delete,deployment ...'
|
webhookListingContent: 'create,delete,deployment ...'
|
||||||
general: 'General'
|
general: 'General'
|
||||||
webhooks: 'Webhooks'
|
webhooks: 'Webhooks'
|
||||||
@ -287,8 +307,8 @@ repoCloneLabel: Clone with HTTPS
|
|||||||
emptyRepoHeader: This repository is empty. Let's get started...
|
emptyRepoHeader: This repository is empty. Let's get started...
|
||||||
addNewFile: + New File
|
addNewFile: + New File
|
||||||
# emptyRepoInclude: We recommend every repository include a [README](README_URL), [LICENSE](LICENSE_URL), and [.gitignore](GITIGNORE_URL).
|
# emptyRepoInclude: We recommend every repository include a [README](README_URL), [LICENSE](LICENSE_URL), and [.gitignore](GITIGNORE_URL).
|
||||||
emptyRepoInclude: We recommend every repository include a
|
emptyRepoInclude: We recommend every repository include a
|
||||||
readMe: README,
|
readMe: README,
|
||||||
license: LICENSE
|
license: LICENSE
|
||||||
gitIgnore: .gitignore
|
gitIgnore: .gitignore
|
||||||
and: and
|
and: and
|
||||||
|
16
web/src/images/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import PrOpen from 'images/pull-request-open.svg'
|
||||||
|
import PrMerged from 'images/pull-request-merged.svg'
|
||||||
|
import PrClosed from 'images/pull-request-closed.svg'
|
||||||
|
import PrRejected from 'images/pull-request-rejected.svg'
|
||||||
|
import PrDraft from 'images/pull-request-draft.svg'
|
||||||
|
import EmptyState from 'images/empty-state.svg'
|
||||||
|
|
||||||
|
export const Images = {
|
||||||
|
PrOpen,
|
||||||
|
PrMerged,
|
||||||
|
PrClosed,
|
||||||
|
PrRejected,
|
||||||
|
PrDraft,
|
||||||
|
|
||||||
|
EmptyState
|
||||||
|
}
|
Before Width: | Height: | Size: 387 B After Width: | Height: | Size: 387 B |
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 387 B After Width: | Height: | Size: 387 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 549 B After Width: | Height: | Size: 549 B |
@ -1,3 +1,4 @@
|
|||||||
|
import { noop } from 'lodash-es'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Container, PageBody, NoDataCard, Tabs } from '@harness/uicore'
|
import { Container, PageBody, NoDataCard, Tabs } from '@harness/uicore'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
@ -7,7 +8,7 @@ import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
|||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||||
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
||||||
import emptyStateImage from 'images/empty-state.svg'
|
import { Images } from 'images'
|
||||||
import { makeDiffRefs } from 'utils/GitUtils'
|
import { makeDiffRefs } from 'utils/GitUtils'
|
||||||
import { CommitsView } from 'components/CommitsView/CommitsView'
|
import { CommitsView } from 'components/CommitsView/CommitsView'
|
||||||
import { Changes } from 'components/Changes/Changes'
|
import { Changes } from 'components/Changes/Changes'
|
||||||
@ -81,7 +82,7 @@ export default function Compare() {
|
|||||||
|
|
||||||
{(!targetGitRef || !sourceGitRef) && (
|
{(!targetGitRef || !sourceGitRef) && (
|
||||||
<Container className={css.noDataContainer}>
|
<Container className={css.noDataContainer}>
|
||||||
<NoDataCard image={emptyStateImage} message={getString('selectToViewMore')} />
|
<NoDataCard image={Images.EmptyState} message={getString('selectToViewMore')} />
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -120,6 +121,7 @@ export default function Compare() {
|
|||||||
sourceBranch={sourceGitRef}
|
sourceBranch={sourceGitRef}
|
||||||
emptyTitle={getString('noChanges')}
|
emptyTitle={getString('noChanges')}
|
||||||
emptyMessage={getString('noChangesCompare')}
|
emptyMessage={getString('noChangesCompare')}
|
||||||
|
onCommentUpdate={noop} // TODO: Update tab stats
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
@ -4,8 +4,15 @@
|
|||||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16),
|
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16),
|
||||||
0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
||||||
|
|
||||||
border-radius: 5px;
|
border-radius: 4px;
|
||||||
padding: var(--spacing-xlarge) !important;
|
padding: var(--spacing-xlarge) var(--spacing-large) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: This styling per design is too white */
|
||||||
|
.descBox {
|
||||||
|
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: var(--spacing-xlarge) var(--spacing-large) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.snapshot {
|
.snapshot {
|
||||||
@ -70,7 +77,7 @@
|
|||||||
.newCommentCreated {
|
.newCommentCreated {
|
||||||
box-shadow: 0px 0px 5px rgb(37 41 192);
|
box-shadow: 0px 0px 5px rgb(37 41 192);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: box-shadow 1s ease-in-out;
|
transition: box-shadow 0.5s ease-in-out;
|
||||||
|
|
||||||
&.clear {
|
&.clear {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly box: string
|
readonly box: string
|
||||||
|
readonly descBox: string
|
||||||
readonly snapshot: string
|
readonly snapshot: string
|
||||||
readonly title: string
|
readonly title: string
|
||||||
readonly fname: string
|
readonly fname: string
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Color,
|
Color,
|
||||||
@ -20,9 +20,8 @@ import { CodeIcon, GitInfoProps } from 'utils/GitUtils'
|
|||||||
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import type { TypesPullReqActivity } from 'services/code'
|
import type { OpenapiUpdatePullReqRequest, TypesPullReqActivity } from 'services/code'
|
||||||
import { CommentAction, CommentBox, CommentBoxOutletPosition, CommentItem } from 'components/CommentBox/CommentBox'
|
import { CommentAction, CommentBox, CommentBoxOutletPosition, CommentItem } from 'components/CommentBox/CommentBox'
|
||||||
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
|
|
||||||
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||||
import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
|
import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
|
||||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||||
@ -33,18 +32,14 @@ import {
|
|||||||
PullRequestCodeCommentPayload
|
PullRequestCodeCommentPayload
|
||||||
} from 'components/DiffViewer/DiffViewerUtils'
|
} from 'components/DiffViewer/DiffViewerUtils'
|
||||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||||
import { PullRequestStatusInfo } from './PullRequestStatusInfo/PullRequestStatusInfo'
|
import { PullRequestActionsBox } from './PullRequestActionsBox/PullRequestActionsBox'
|
||||||
import css from './Conversation.module.scss'
|
import css from './Conversation.module.scss'
|
||||||
|
|
||||||
interface ConversationProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
|
interface ConversationProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
|
||||||
refreshPullRequestMetadata: () => void
|
onCommentUpdate: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Conversation: React.FC<ConversationProps> = ({
|
export const Conversation: React.FC<ConversationProps> = ({ repoMetadata, pullRequestMetadata, onCommentUpdate }) => {
|
||||||
repoMetadata,
|
|
||||||
pullRequestMetadata,
|
|
||||||
refreshPullRequestMetadata
|
|
||||||
}) => {
|
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { currentUser } = useAppContext()
|
const { currentUser } = useAppContext()
|
||||||
const {
|
const {
|
||||||
@ -62,12 +57,8 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
// contains a parent comment and multiple replied comments
|
// contains a parent comment and multiple replied comments
|
||||||
const blocks: CommentItem<TypesPullReqActivity>[][] = []
|
const blocks: CommentItem<TypesPullReqActivity>[][] = []
|
||||||
|
|
||||||
if (newComments.length) {
|
|
||||||
blocks.push(orderBy(newComments, 'edited', 'desc').map(activityToCommentItem))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine all parent activities
|
// Determine all parent activities
|
||||||
const parentActivities = orderBy(activities?.filter(activity => !activity.parent_id) || [], 'edited', 'desc').map(
|
const parentActivities = orderBy(activities?.filter(activity => !activity.parent_id) || [], 'edited', 'asc').map(
|
||||||
_comment => [_comment]
|
_comment => [_comment]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,20 +75,26 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
blocks.push(parentActivity.map(activityToCommentItem))
|
blocks.push(parentActivity.map(activityToCommentItem))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (newComments.length) {
|
||||||
|
blocks.push(orderBy(newComments, 'edited', 'asc').map(activityToCommentItem))
|
||||||
|
}
|
||||||
|
|
||||||
// Group title-change events into one single block
|
// Group title-change events into one single block
|
||||||
const titleChangeItems =
|
// Disabled for now, @see https://harness.atlassian.net/browse/SCM-79
|
||||||
blocks.filter(
|
// const titleChangeItems =
|
||||||
_activities => isSystemComment(_activities) && _activities[0].payload?.type === CommentType.TITLE_CHANGE
|
// blocks.filter(
|
||||||
) || []
|
// _activities => isSystemComment(_activities) && _activities[0].payload?.type === CommentType.TITLE_CHANGE
|
||||||
|
// ) || []
|
||||||
|
|
||||||
titleChangeItems.forEach((value, index) => {
|
// titleChangeItems.forEach((value, index) => {
|
||||||
if (index > 0) {
|
// if (index > 0) {
|
||||||
titleChangeItems[0].push(...value)
|
// titleChangeItems[0].push(...value)
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
titleChangeItems.shift()
|
// titleChangeItems.shift()
|
||||||
|
// return blocks.filter(_activities => !titleChangeItems.includes(_activities))
|
||||||
|
|
||||||
return blocks.filter(_activities => !titleChangeItems.includes(_activities))
|
return blocks
|
||||||
}, [activities, newComments])
|
}, [activities, newComments])
|
||||||
const path = useMemo(
|
const path = useMemo(
|
||||||
() => `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/comments`,
|
() => `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/comments`,
|
||||||
@ -108,6 +105,10 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
const { mutate: deleteComment } = useMutate({ verb: 'DELETE', path: ({ id }) => `${path}/${id}` })
|
const { mutate: deleteComment } = useMutate({ verb: 'DELETE', path: ({ id }) => `${path}/${id}` })
|
||||||
const confirmAct = useConfirmAct()
|
const confirmAct = useConfirmAct()
|
||||||
const [commentCreated, setCommentCreated] = useState(false)
|
const [commentCreated, setCommentCreated] = useState(false)
|
||||||
|
const refreshPR = useCallback(() => {
|
||||||
|
onCommentUpdate()
|
||||||
|
refetchActivities()
|
||||||
|
}, [onCommentUpdate, refetchActivities])
|
||||||
|
|
||||||
useAnimateNewCommentBox(commentCreated, setCommentCreated)
|
useAnimateNewCommentBox(commentCreated, setCommentCreated)
|
||||||
|
|
||||||
@ -115,20 +116,17 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
<PullRequestTabContentWrapper loading={loading} error={error} onRetry={refetchActivities}>
|
<PullRequestTabContentWrapper loading={loading} error={error} onRetry={refetchActivities}>
|
||||||
<Container>
|
<Container>
|
||||||
<Layout.Vertical spacing="xlarge">
|
<Layout.Vertical spacing="xlarge">
|
||||||
<PullRequestStatusInfo
|
<PullRequestActionsBox
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
pullRequestMetadata={pullRequestMetadata}
|
pullRequestMetadata={pullRequestMetadata}
|
||||||
onMerge={() => {
|
onPRStateChanged={refreshPR}
|
||||||
refreshPullRequestMetadata()
|
|
||||||
refetchActivities()
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Container>
|
<Container>
|
||||||
<Layout.Vertical spacing="xlarge">
|
<Layout.Vertical spacing="xlarge">
|
||||||
<DescriptionBox
|
<DescriptionBox
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
pullRequestMetadata={pullRequestMetadata}
|
pullRequestMetadata={pullRequestMetadata}
|
||||||
refreshPullRequestMetadata={refreshPullRequestMetadata}
|
onCommentUpdate={onCommentUpdate}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{activityBlocks?.map((blocks, index) => {
|
{activityBlocks?.map((blocks, index) => {
|
||||||
@ -145,7 +143,7 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
<CommentBox
|
<CommentBox
|
||||||
key={threadId}
|
key={threadId}
|
||||||
fluid
|
fluid
|
||||||
className={cx({ [css.newCommentCreated]: commentCreated && !index })}
|
className={cx({ [css.newCommentCreated]: commentCreated && index === activityBlocks.length - 1 })}
|
||||||
getString={getString}
|
getString={getString}
|
||||||
commentItems={commentItems}
|
commentItems={commentItems}
|
||||||
currentUserName={currentUser.display_name}
|
currentUserName={currentUser.display_name}
|
||||||
@ -195,6 +193,10 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
onCommentUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
return [result, updatedItem]
|
return [result, updatedItem]
|
||||||
}}
|
}}
|
||||||
outlets={{
|
outlets={{
|
||||||
@ -227,6 +229,11 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
result = false
|
result = false
|
||||||
showError(getErrorMessage(exception), 0)
|
showError(getErrorMessage(exception), 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
onCommentUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
return [result, updatedItem]
|
return [result, updatedItem]
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -241,10 +248,10 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
const DescriptionBox: React.FC<ConversationProps> = ({
|
const DescriptionBox: React.FC<ConversationProps> = ({
|
||||||
repoMetadata,
|
repoMetadata,
|
||||||
pullRequestMetadata,
|
pullRequestMetadata,
|
||||||
refreshPullRequestMetadata
|
onCommentUpdate: refreshPullRequestMetadata
|
||||||
}) => {
|
}) => {
|
||||||
const [edit, setEdit] = useState(false)
|
const [edit, setEdit] = useState(false)
|
||||||
const [updated, setUpdated] = useState(pullRequestMetadata.edited as number)
|
// const [updated, setUpdated] = useState(pullRequestMetadata.edited as number)
|
||||||
const [originalContent, setOriginalContent] = useState(pullRequestMetadata.description as string)
|
const [originalContent, setOriginalContent] = useState(pullRequestMetadata.description as string)
|
||||||
const [content, setContent] = useState(originalContent)
|
const [content, setContent] = useState(originalContent)
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
@ -259,15 +266,29 @@ const DescriptionBox: React.FC<ConversationProps> = ({
|
|||||||
<Container className={css.box}>
|
<Container className={css.box}>
|
||||||
<Layout.Vertical spacing="medium">
|
<Layout.Vertical spacing="medium">
|
||||||
<Container>
|
<Container>
|
||||||
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
<Layout.Horizontal spacing="xsmall" style={{ alignItems: 'center' }}>
|
||||||
<Avatar name={name} size="small" hoverCard={false} />
|
<StringSubstitute
|
||||||
<Text inline>
|
str={getString('pr.authorCommentedPR')}
|
||||||
<strong>{name}</strong>
|
vars={{
|
||||||
</Text>
|
author: (
|
||||||
<PipeSeparator height={8} />
|
<>
|
||||||
|
<Avatar name={name} size="small" hoverCard={false} />
|
||||||
|
<Text inline margin={{ right: 'xsmall' }}>
|
||||||
|
<strong>{name}</strong>
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
time: (
|
||||||
|
<Text inline>
|
||||||
|
<ReactTimeago date={pullRequestMetadata.created as number} />
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* <PipeSeparator height={8} />
|
||||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
||||||
<ReactTimeago date={updated} />
|
</Text> */}
|
||||||
</Text>
|
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
<OptionsMenuButton
|
<OptionsMenuButton
|
||||||
isDark={false}
|
isDark={false}
|
||||||
@ -288,15 +309,16 @@ const DescriptionBox: React.FC<ConversationProps> = ({
|
|||||||
<MarkdownEditorWithPreview
|
<MarkdownEditorWithPreview
|
||||||
value={content}
|
value={content}
|
||||||
onSave={value => {
|
onSave={value => {
|
||||||
mutate({
|
const payload: OpenapiUpdatePullReqRequest = {
|
||||||
title: pullRequestMetadata.title,
|
title: pullRequestMetadata.title,
|
||||||
description: value
|
description: value
|
||||||
})
|
}
|
||||||
|
mutate(payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setContent(value)
|
setContent(value)
|
||||||
setOriginalContent(value)
|
setOriginalContent(value)
|
||||||
setEdit(false)
|
setEdit(false)
|
||||||
setUpdated(Date.now())
|
// setUpdated(Date.now())
|
||||||
refreshPullRequestMetadata()
|
refreshPullRequestMetadata()
|
||||||
})
|
})
|
||||||
.catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate')))
|
.catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate')))
|
||||||
@ -322,7 +344,7 @@ const DescriptionBox: React.FC<ConversationProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isCodeComment(commentItems: CommentItem<TypesPullReqActivity>[]) {
|
function isCodeComment(commentItems: CommentItem<TypesPullReqActivity>[]) {
|
||||||
return commentItems[0]?.payload?.payload?.type === CommentType.CODE_COMMENT
|
return (commentItems[0]?.payload?.payload as Unknown)?.type === CommentType.CODE_COMMENT
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CodeCommentHeaderProps {
|
interface CodeCommentHeaderProps {
|
||||||
@ -400,6 +422,68 @@ const SystemBox: React.FC<SystemBoxProps> = ({ pullRequestMetadata, commentItems
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CommentType.BRANCH_UPDATE: {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.box}>
|
||||||
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
||||||
|
<Text>
|
||||||
|
<StringSubstitute
|
||||||
|
str={getString('pr.prBranchPushInfo')}
|
||||||
|
vars={{
|
||||||
|
user: <strong>{payload?.author?.display_name}</strong>,
|
||||||
|
commit: <strong>{(payload?.payload as Unknown)?.new}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
<FlexExpander />
|
||||||
|
<Text
|
||||||
|
inline
|
||||||
|
font={{ variation: FontVariation.SMALL }}
|
||||||
|
color={Color.GREY_400}
|
||||||
|
width={100}
|
||||||
|
style={{ textAlign: 'right' }}>
|
||||||
|
<ReactTimeago date={payload?.created as number} />
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommentType.STATE_CHANGE: {
|
||||||
|
const openFromDraft =
|
||||||
|
(payload?.payload as Unknown)?.old === (payload?.payload as Unknown)?.new &&
|
||||||
|
(payload?.payload as Unknown)?.new === 'open' &&
|
||||||
|
(payload?.payload as Unknown)?.is_draft === false
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.box}>
|
||||||
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
||||||
|
<Text>
|
||||||
|
<StringSubstitute
|
||||||
|
str={getString(openFromDraft ? 'pr.prStateChangedDraft' : 'pr.prStateChanged')}
|
||||||
|
vars={{
|
||||||
|
user: <strong>{payload?.author?.display_name}</strong>,
|
||||||
|
old: <strong>{(payload?.payload as Unknown)?.old}</strong>,
|
||||||
|
new: <strong>{(payload?.payload as Unknown)?.new}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
<FlexExpander />
|
||||||
|
<Text
|
||||||
|
inline
|
||||||
|
font={{ variation: FontVariation.SMALL }}
|
||||||
|
color={Color.GREY_400}
|
||||||
|
width={100}
|
||||||
|
style={{ textAlign: 'right' }}>
|
||||||
|
<ReactTimeago date={payload?.created as number} />
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
case CommentType.TITLE_CHANGE: {
|
case CommentType.TITLE_CHANGE: {
|
||||||
return (
|
return (
|
||||||
<Container className={css.box}>
|
<Container className={css.box}>
|
||||||
@ -412,15 +496,20 @@ const SystemBox: React.FC<SystemBoxProps> = ({ pullRequestMetadata, commentItems
|
|||||||
user: <strong>{payload?.author?.display_name}</strong>,
|
user: <strong>{payload?.author?.display_name}</strong>,
|
||||||
old: (
|
old: (
|
||||||
<strong>
|
<strong>
|
||||||
<s>{payload?.payload?.old}</s>
|
<s>{(payload?.payload as Unknown)?.old}</s>
|
||||||
</strong>
|
</strong>
|
||||||
),
|
),
|
||||||
new: <strong>{payload?.payload?.new}</strong>
|
new: <strong>{(payload?.payload as Unknown)?.new}</strong>
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
<Text
|
||||||
|
inline
|
||||||
|
font={{ variation: FontVariation.SMALL }}
|
||||||
|
color={Color.GREY_400}
|
||||||
|
width={100}
|
||||||
|
style={{ textAlign: 'right' }}>
|
||||||
<ReactTimeago date={payload?.created as number} />
|
<ReactTimeago date={payload?.created as number} />
|
||||||
</Text>
|
</Text>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
@ -435,8 +524,8 @@ const SystemBox: React.FC<SystemBoxProps> = ({ pullRequestMetadata, commentItems
|
|||||||
.filter((_, index) => index > 0)
|
.filter((_, index) => index > 0)
|
||||||
.map(
|
.map(
|
||||||
item =>
|
item =>
|
||||||
`|${item.author}|<s>${item.payload?.payload?.old}</s>|${
|
`|${item.author}|<s>${(item.payload?.payload as Unknown)?.old}</s>|${
|
||||||
item.payload?.payload?.new
|
(item.payload?.payload as Unknown)?.new
|
||||||
}|${formatDate(item.updated)} ${formatTime(item.updated)}|`
|
}|${formatDate(item.updated)} ${formatTime(item.updated)}|`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
.main {
|
||||||
|
--bar-height: 60px;
|
||||||
|
|
||||||
|
background-color: var(--green-50) !important; // #f6fff2
|
||||||
|
margin: -24px -24px 0 !important;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
&.merged {
|
||||||
|
border-color: transparent !important;
|
||||||
|
background: var(--purple-50) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
height: var(--bar-height);
|
||||||
|
padding: 0 var(--spacing-xlarge) !important;
|
||||||
|
|
||||||
|
.secondaryButton,
|
||||||
|
[class*='Button--variation-tertiary'] {
|
||||||
|
--box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.04), 0px 2px 4px rgba(96, 97, 112, 0.16) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: var(--green-800) !important;
|
||||||
|
color: var(--white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 24px !important;
|
||||||
|
color: var(--grey-700) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub {
|
||||||
|
font-weight: 500 !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
line-height: 20px !important;
|
||||||
|
color: var(--grey-500) !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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnWrapper {
|
||||||
|
button {
|
||||||
|
--background-color: var(--green-800) !important;
|
||||||
|
--background-color-hover: var(--green-900) !important;
|
||||||
|
--background-color-active: var(--green-900) !important;
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,13 @@
|
|||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly main: string
|
readonly main: string
|
||||||
readonly merged: string
|
readonly merged: string
|
||||||
|
readonly layout: string
|
||||||
|
readonly secondaryButton: string
|
||||||
readonly btn: string
|
readonly btn: string
|
||||||
readonly heading: string
|
readonly heading: string
|
||||||
readonly sub: string
|
readonly sub: string
|
||||||
|
readonly popover: string
|
||||||
|
readonly menuItem: string
|
||||||
|
readonly btnWrapper: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
@ -0,0 +1,244 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonVariation,
|
||||||
|
Color,
|
||||||
|
Container,
|
||||||
|
FlexExpander,
|
||||||
|
Icon,
|
||||||
|
Layout,
|
||||||
|
SplitButton,
|
||||||
|
StringSubstitute,
|
||||||
|
Text,
|
||||||
|
useToaster
|
||||||
|
} from '@harness/uicore'
|
||||||
|
import { useMutate } from 'restful-react'
|
||||||
|
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 { useStrings } from 'framework/strings'
|
||||||
|
import { CodeIcon, GitInfoProps, PullRequestFilterOption, PullRequestState } from 'utils/GitUtils'
|
||||||
|
import { getErrorMessage } from 'utils/Utils'
|
||||||
|
import css from './PullRequestActionsBox.module.scss'
|
||||||
|
|
||||||
|
interface PullRequestActionsBoxProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
|
||||||
|
onPRStateChanged: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PRMergeOption {
|
||||||
|
method: EnumMergeMethod | 'close'
|
||||||
|
title: string
|
||||||
|
desc: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
||||||
|
repoMetadata,
|
||||||
|
pullRequestMetadata,
|
||||||
|
onPRStateChanged
|
||||||
|
}) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const { showError } = useToaster()
|
||||||
|
const { mutate: mergePR, loading } = useMutate({
|
||||||
|
verb: 'POST',
|
||||||
|
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/merge`
|
||||||
|
})
|
||||||
|
const { mutate: updatePRState, loading: loadingState } = useMutate({
|
||||||
|
verb: 'POST',
|
||||||
|
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/state`
|
||||||
|
})
|
||||||
|
|
||||||
|
const isDraft = pullRequestMetadata.is_draft
|
||||||
|
const mergeOptions: PRMergeOption[] = [
|
||||||
|
{
|
||||||
|
method: 'squash',
|
||||||
|
title: getString('pr.mergeOptions.squashAndMerge'),
|
||||||
|
desc: getString('pr.mergeOptions.squashAndMergeDesc')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'merge',
|
||||||
|
title: getString('pr.mergeOptions.createMergeCommit'),
|
||||||
|
desc: getString('pr.mergeOptions.createMergeCommitDesc')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'rebase',
|
||||||
|
title: getString('pr.mergeOptions.rebaseAndMerge'),
|
||||||
|
desc: getString('pr.mergeOptions.rebaseAndMergeDesc')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'close',
|
||||||
|
title: getString('pr.mergeOptions.close'),
|
||||||
|
desc: getString('pr.mergeOptions.closeDesc')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const [mergeOption, setMergeOption] = useState<PRMergeOption>(mergeOptions[0])
|
||||||
|
|
||||||
|
if (pullRequestMetadata.state === PullRequestFilterOption.MERGED) {
|
||||||
|
return <MergeInfo pullRequestMetadata={pullRequestMetadata} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className={css.main}>
|
||||||
|
<Layout.Vertical spacing="xlarge">
|
||||||
|
<Container>
|
||||||
|
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center' }} className={css.layout}>
|
||||||
|
<Icon
|
||||||
|
name={isDraft ? CodeIcon.Draft : 'tick-circle'}
|
||||||
|
size={20}
|
||||||
|
color={isDraft ? Color.ORANGE_900 : Color.GREEN_700}
|
||||||
|
/>
|
||||||
|
<Text className={css.sub}>{getString(isDraft ? 'prState.draftHeading' : 'pr.branchHasNoConflicts')}</Text>
|
||||||
|
<FlexExpander />
|
||||||
|
<Render when={loading || loadingState}>
|
||||||
|
<Icon name={CodeIcon.InputSpinner} size={16} margin={{ right: 'xsmall' }} />
|
||||||
|
</Render>
|
||||||
|
<Match expr={isDraft}>
|
||||||
|
<Truthy>
|
||||||
|
<Button
|
||||||
|
className={css.secondaryButton}
|
||||||
|
text={getString('pr.readyForReview')}
|
||||||
|
variation={ButtonVariation.TERTIARY}
|
||||||
|
onClick={() => {
|
||||||
|
const payload: OpenapiStatePullReqRequest = { is_draft: false, state: 'open' }
|
||||||
|
|
||||||
|
updatePRState(payload)
|
||||||
|
.then(onPRStateChanged)
|
||||||
|
.catch(exception => showError(getErrorMessage(exception)))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Truthy>
|
||||||
|
<Else>
|
||||||
|
<Container>
|
||||||
|
<Match expr={pullRequestMetadata.state}>
|
||||||
|
<Case val={PullRequestState.CLOSED}>
|
||||||
|
<Button
|
||||||
|
className={css.secondaryButton}
|
||||||
|
text={getString('pr.openForReview')}
|
||||||
|
variation={ButtonVariation.TERTIARY}
|
||||||
|
onClick={() => {
|
||||||
|
const payload: OpenapiStatePullReqRequest = { state: 'open' }
|
||||||
|
|
||||||
|
updatePRState(payload)
|
||||||
|
.then(onPRStateChanged)
|
||||||
|
.catch(exception => showError(getErrorMessage(exception)))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Case>
|
||||||
|
<Case val={PullRequestState.OPEN}>
|
||||||
|
<Layout.Horizontal
|
||||||
|
inline
|
||||||
|
spacing="huge"
|
||||||
|
className={cx({ [css.btnWrapper]: mergeOption.method !== 'close' })}>
|
||||||
|
<SplitButton
|
||||||
|
text={mergeOption.title}
|
||||||
|
className={cx({ [css.secondaryButton]: mergeOption.method === 'close' })}
|
||||||
|
variation={
|
||||||
|
mergeOption.method === 'close' ? 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,
|
||||||
|
force: false,
|
||||||
|
delete_branch: false
|
||||||
|
}
|
||||||
|
|
||||||
|
mergePR(payload)
|
||||||
|
.then(onPRStateChanged)
|
||||||
|
.catch(exception => showError(getErrorMessage(exception)))
|
||||||
|
} else {
|
||||||
|
const payload: OpenapiStatePullReqRequest = { state: 'closed' }
|
||||||
|
|
||||||
|
updatePRState(payload)
|
||||||
|
.then(onPRStateChanged)
|
||||||
|
.catch(exception => showError(getErrorMessage(exception)))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
dropdownDisabled={loading}>
|
||||||
|
{/* <Menu.Item
|
||||||
|
className={css.menuItem}
|
||||||
|
text={
|
||||||
|
<>
|
||||||
|
<BIcon icon="blank" />
|
||||||
|
<strong>Create pull request</strong>
|
||||||
|
<p>Open a pull request that is ready for review</p>
|
||||||
|
<p>Automatically request reviews from code owners</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Menu.Item
|
||||||
|
className={css.menuItem}
|
||||||
|
text={
|
||||||
|
<>
|
||||||
|
<BIcon icon="blank" />
|
||||||
|
<strong>Create draft pull request</strong>
|
||||||
|
<p>Does not request code reviews and cannot be merged</p>
|
||||||
|
<p>Cannot be merged until marked ready for review</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/> */}
|
||||||
|
{mergeOptions.map(option => {
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
key={option.method}
|
||||||
|
className={css.menuItem}
|
||||||
|
text={
|
||||||
|
<>
|
||||||
|
<BIcon icon={mergeOption.method === option.method ? 'tick' : 'blank'} />
|
||||||
|
<strong>{option.title}</strong>
|
||||||
|
<p>{option.desc}</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
onClick={() => setMergeOption(option)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SplitButton>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Case>
|
||||||
|
</Match>
|
||||||
|
</Container>
|
||||||
|
</Else>
|
||||||
|
</Match>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
</Layout.Vertical>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MergeInfo: React.FC<{ pullRequestMetadata: TypesPullReq }> = ({ pullRequestMetadata }) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className={cx(css.main, css.merged)}>
|
||||||
|
<Layout.Horizontal spacing="medium" flex={{ alignItems: 'center' }} className={css.layout}>
|
||||||
|
<Icon name={CodeIcon.PullRequest} size={20} color={Color.PURPLE_700} />
|
||||||
|
<Container>
|
||||||
|
{/* <Text className={css.heading}>{getString('pr.prMerged')}</Text> */}
|
||||||
|
<Text className={css.sub}>
|
||||||
|
<StringSubstitute
|
||||||
|
str={getString('pr.prMergedInfo')}
|
||||||
|
vars={{
|
||||||
|
user: <strong>{pullRequestMetadata.merger?.display_name}</strong>,
|
||||||
|
source: <strong>{pullRequestMetadata.source_branch}</strong>,
|
||||||
|
target: <strong>{pullRequestMetadata.target_branch} </strong>,
|
||||||
|
time: <ReactTimeago date={pullRequestMetadata.merged as number} />
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
<FlexExpander />
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
.main {
|
|
||||||
border: 2px solid var(--green-700);
|
|
||||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: var(--white);
|
|
||||||
|
|
||||||
&.merged {
|
|
||||||
border-color: transparent !important;
|
|
||||||
background: var(--purple-50) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
background-color: var(--green-800) !important;
|
|
||||||
color: var(--white) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
font-weight: 600 !important;
|
|
||||||
font-size: 16px !important;
|
|
||||||
line-height: 24px !important;
|
|
||||||
color: var(--grey-700) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub {
|
|
||||||
font-weight: 500 !important;
|
|
||||||
font-size: 13px !important;
|
|
||||||
line-height: 20px !important;
|
|
||||||
color: var(--grey-500) !important;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Color,
|
|
||||||
Container,
|
|
||||||
FlexExpander,
|
|
||||||
Icon,
|
|
||||||
Layout,
|
|
||||||
StringSubstitute,
|
|
||||||
Text,
|
|
||||||
useToaster
|
|
||||||
} from '@harness/uicore'
|
|
||||||
import { useMutate } from 'restful-react'
|
|
||||||
import cx from 'classnames'
|
|
||||||
import ReactTimeago from 'react-timeago'
|
|
||||||
import type { TypesPullReq } from 'services/code'
|
|
||||||
import { useStrings } from 'framework/strings'
|
|
||||||
import { CodeIcon, GitInfoProps, PullRequestFilterOption } from 'utils/GitUtils'
|
|
||||||
import { getErrorMessage } from 'utils/Utils'
|
|
||||||
import css from './PullRequestStatusInfo.module.scss'
|
|
||||||
|
|
||||||
interface PullRequestStatusInfoProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
|
|
||||||
onMerge: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PullRequestStatusInfo: React.FC<PullRequestStatusInfoProps> = ({
|
|
||||||
repoMetadata,
|
|
||||||
pullRequestMetadata,
|
|
||||||
onMerge
|
|
||||||
}) => {
|
|
||||||
const { getString } = useStrings()
|
|
||||||
const { showError } = useToaster()
|
|
||||||
const { mutate: mergePR } = useMutate({
|
|
||||||
verb: 'POST',
|
|
||||||
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/merge`
|
|
||||||
})
|
|
||||||
|
|
||||||
if (pullRequestMetadata.state === PullRequestFilterOption.MERGED) {
|
|
||||||
return <MergeInfo pullRequestMetadata={pullRequestMetadata} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container className={css.main} padding="xlarge">
|
|
||||||
<Layout.Vertical spacing="xlarge">
|
|
||||||
<Container>
|
|
||||||
<Layout.Horizontal spacing="medium" flex={{ alignItems: 'center' }}>
|
|
||||||
<Icon name="tick-circle" size={28} color={Color.GREEN_700} />
|
|
||||||
<Container>
|
|
||||||
<Text className={css.heading}>{getString('pr.branchHasNoConflicts')}</Text>
|
|
||||||
<Text className={css.sub}>{getString('pr.prCanBeMerged')}</Text>
|
|
||||||
</Container>
|
|
||||||
<FlexExpander />
|
|
||||||
</Layout.Horizontal>
|
|
||||||
</Container>
|
|
||||||
<Container>
|
|
||||||
<Button
|
|
||||||
className={css.btn}
|
|
||||||
text={getString('pr.mergePR')}
|
|
||||||
onClick={() => {
|
|
||||||
mergePR({})
|
|
||||||
.then(onMerge)
|
|
||||||
.catch(exception => showError(getErrorMessage(exception)))
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
</Layout.Vertical>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MergeInfo: React.FC<{ pullRequestMetadata: TypesPullReq }> = ({ pullRequestMetadata }) => {
|
|
||||||
const { getString } = useStrings()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container className={cx(css.main, css.merged)} padding="xlarge">
|
|
||||||
<Layout.Horizontal spacing="medium" flex={{ alignItems: 'center' }}>
|
|
||||||
<Icon name={CodeIcon.PullRequest} size={28} color={Color.PURPLE_700} />
|
|
||||||
<Container>
|
|
||||||
<Text className={css.heading}>{getString('pr.prMerged')}</Text>
|
|
||||||
<Text className={css.sub}>
|
|
||||||
<StringSubstitute
|
|
||||||
str={getString('pr.prMergedInfo')}
|
|
||||||
vars={{
|
|
||||||
user: <strong>{pullRequestMetadata.merger?.display_name}</strong>,
|
|
||||||
source: <strong>{pullRequestMetadata.source_branch}</strong>,
|
|
||||||
target: <strong>{pullRequestMetadata.target_branch} </strong>,
|
|
||||||
time: <ReactTimeago date={pullRequestMetadata.merged as number} />
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
<FlexExpander />
|
|
||||||
</Layout.Horizontal>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
@ -104,18 +104,30 @@ export default function PullRequest() {
|
|||||||
tabList={[
|
tabList={[
|
||||||
{
|
{
|
||||||
id: PullRequestSection.CONVERSATION,
|
id: PullRequestSection.CONVERSATION,
|
||||||
title: <TabTitle icon={CodeIcon.Chat} title={getString('conversation')} count={0} />,
|
title: (
|
||||||
|
<TabTitle
|
||||||
|
icon={CodeIcon.Chat}
|
||||||
|
title={getString('conversation')}
|
||||||
|
count={prData?.stats?.conversations || 0}
|
||||||
|
/>
|
||||||
|
),
|
||||||
panel: (
|
panel: (
|
||||||
<Conversation
|
<Conversation
|
||||||
repoMetadata={repoMetadata as TypesRepository}
|
repoMetadata={repoMetadata as TypesRepository}
|
||||||
pullRequestMetadata={prData as TypesPullReq}
|
pullRequestMetadata={prData as TypesPullReq}
|
||||||
refreshPullRequestMetadata={voidFn(refetchPullRequest)}
|
onCommentUpdate={voidFn(refetchPullRequest)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: PullRequestSection.COMMITS,
|
id: PullRequestSection.COMMITS,
|
||||||
title: <TabTitle icon={CodeIcon.Commit} title={getString('commits')} count={0} />,
|
title: (
|
||||||
|
<TabTitle
|
||||||
|
icon={CodeIcon.Commit}
|
||||||
|
title={getString('commits')}
|
||||||
|
count={prData?.stats?.commits || 0}
|
||||||
|
/>
|
||||||
|
),
|
||||||
panel: (
|
panel: (
|
||||||
<PullRequestCommits
|
<PullRequestCommits
|
||||||
repoMetadata={repoMetadata as TypesRepository}
|
repoMetadata={repoMetadata as TypesRepository}
|
||||||
@ -125,7 +137,13 @@ export default function PullRequest() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: PullRequestSection.FILES_CHANGED,
|
id: PullRequestSection.FILES_CHANGED,
|
||||||
title: <TabTitle icon={CodeIcon.File} title={getString('filesChanged')} count={0} />,
|
title: (
|
||||||
|
<TabTitle
|
||||||
|
icon={CodeIcon.File}
|
||||||
|
title={getString('filesChanged')}
|
||||||
|
count={prData?.stats?.files_changed || 0}
|
||||||
|
/>
|
||||||
|
),
|
||||||
panel: (
|
panel: (
|
||||||
<Container className={css.changes}>
|
<Container className={css.changes}>
|
||||||
<Changes
|
<Changes
|
||||||
@ -135,6 +153,7 @@ export default function PullRequest() {
|
|||||||
sourceBranch={prData?.source_branch}
|
sourceBranch={prData?.source_branch}
|
||||||
emptyTitle={getString('noChanges')}
|
emptyTitle={getString('noChanges')}
|
||||||
emptyMessage={getString('noChangesPR')}
|
emptyMessage={getString('noChangesPR')}
|
||||||
|
onCommentUpdate={voidFn(refetchPullRequest)}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
@ -23,7 +23,7 @@ export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'p
|
|||||||
refetch,
|
refetch,
|
||||||
response
|
response
|
||||||
} = useGet<TypesCommit[]>({
|
} = useGet<TypesCommit[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullRequestMetadata.number}/commits`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit,
|
limit,
|
||||||
page,
|
page,
|
||||||
|
@ -2,11 +2,11 @@ import React from 'react'
|
|||||||
import { Container, Text, Layout, StringSubstitute } from '@harness/uicore'
|
import { Container, Text, Layout, StringSubstitute } from '@harness/uicore'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import ReactTimeago from 'react-timeago'
|
import ReactTimeago from 'react-timeago'
|
||||||
import { GitInfoProps, PullRequestState } from 'utils/GitUtils'
|
import type { GitInfoProps } from 'utils/GitUtils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import type { TypesPullReq } from 'services/code'
|
import type { TypesPullReq } from 'services/code'
|
||||||
import { PRStateLabel } from 'components/PRStateLabel/PRStateLabel'
|
import { PullRequestStateLabel } from 'components/PullRequestStateLabel/PullRequestStateLabel'
|
||||||
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
|
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
|
||||||
import { GitRefLink } from 'components/GitRefLink/GitRefLink'
|
import { GitRefLink } from 'components/GitRefLink/GitRefLink'
|
||||||
import css from './PullRequestMetaLine.module.scss'
|
import css from './PullRequestMetaLine.module.scss'
|
||||||
@ -17,14 +17,16 @@ export const PullRequestMetaLine: React.FC<TypesPullReq & Pick<GitInfoProps, 're
|
|||||||
source_branch,
|
source_branch,
|
||||||
author,
|
author,
|
||||||
edited,
|
edited,
|
||||||
merged,
|
state,
|
||||||
state
|
is_draft,
|
||||||
|
stats
|
||||||
}) => {
|
}) => {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const vars = {
|
const vars = {
|
||||||
user: <strong>{author?.display_name}</strong>,
|
user: <strong>{author?.display_name}</strong>,
|
||||||
number: <strong>5</strong>, // TODO: No data from backend now
|
commits: <strong>{stats?.commits}</strong>,
|
||||||
|
commitsCount: stats?.commits,
|
||||||
target: (
|
target: (
|
||||||
<GitRefLink
|
<GitRefLink
|
||||||
text={target_branch as string}
|
text={target_branch as string}
|
||||||
@ -42,7 +44,7 @@ export const PullRequestMetaLine: React.FC<TypesPullReq & Pick<GitInfoProps, 're
|
|||||||
return (
|
return (
|
||||||
<Container padding={{ left: 'xlarge' }} className={css.main}>
|
<Container padding={{ left: 'xlarge' }} className={css.main}>
|
||||||
<Layout.Horizontal spacing="small" className={css.layout}>
|
<Layout.Horizontal spacing="small" className={css.layout}>
|
||||||
<PRStateLabel state={merged ? PullRequestState.MERGED : (state as PullRequestState)} />
|
<PullRequestStateLabel data={{ is_draft, state }} />
|
||||||
<Text className={css.metaline}>
|
<Text className={css.metaline}>
|
||||||
<StringSubstitute str={getString('pr.metaLine')} vars={vars} />
|
<StringSubstitute str={getString('pr.metaLine')} vars={vars} />
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -14,31 +14,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.titleRow {
|
.titleRow {
|
||||||
padding-left: var(--spacing-medium);
|
padding-left: var(--spacing-small);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rowImg {
|
|
||||||
padding: 4px;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&.open {
|
|
||||||
background-color: var(--green-50);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.merged {
|
|
||||||
background-color: var(--blue-50);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.closed {
|
|
||||||
background-color: var(--grey-50);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.draft {
|
|
||||||
background-color: var(--orange-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6,10 +6,5 @@ declare const styles: {
|
|||||||
readonly row: string
|
readonly row: string
|
||||||
readonly title: string
|
readonly title: string
|
||||||
readonly titleRow: string
|
readonly titleRow: string
|
||||||
readonly rowImg: string
|
|
||||||
readonly open: string
|
|
||||||
readonly merged: string
|
|
||||||
readonly closed: string
|
|
||||||
readonly draft: string
|
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { Container, PageBody, Text, Color, TableV2, Layout, StringSubstitute } from '@harness/uicore'
|
import { Container, PageBody, Text, Color, TableV2, Layout, StringSubstitute } from '@harness/uicore'
|
||||||
import cx from 'classnames'
|
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
import { useGet } from 'restful-react'
|
import { useGet } from 'restful-react'
|
||||||
import type { CellProps, Column } from 'react-table'
|
import type { CellProps, Column } from 'react-table'
|
||||||
|
import { Case, Match, Render, Truthy } from 'react-jsx-match'
|
||||||
import ReactTimeago from 'react-timeago'
|
import ReactTimeago from 'react-timeago'
|
||||||
import { makeDiffRefs, PullRequestFilterOption } from 'utils/GitUtils'
|
import { makeDiffRefs, PullRequestFilterOption } from 'utils/GitUtils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
@ -12,15 +12,12 @@ import { useStrings } from 'framework/strings'
|
|||||||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||||
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
import type { TypesPullReq } from 'services/code'
|
import type { TypesPullReq, TypesRepository } from 'services/code'
|
||||||
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
||||||
|
import { PullRequestStateLabel } from 'components/PullRequestStateLabel/PullRequestStateLabel'
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
import { PullRequestsContentHeader } from './PullRequestsContentHeader/PullRequestsContentHeader'
|
import { PullRequestsContentHeader } from './PullRequestsContentHeader/PullRequestsContentHeader'
|
||||||
import prImgOpen from './pull-request-open.svg'
|
|
||||||
import prImgMerged from './pull-request-merged.svg'
|
|
||||||
import prImgClosed from './pull-request-closed.svg'
|
|
||||||
// import prImgDraft from './pull-request-draft.svg'
|
|
||||||
import css from './PullRequests.module.scss'
|
import css from './PullRequests.module.scss'
|
||||||
|
|
||||||
export default function PullRequests() {
|
export default function PullRequests() {
|
||||||
@ -56,7 +53,7 @@ export default function PullRequests() {
|
|||||||
Cell: ({ row }: CellProps<TypesPullReq>) => {
|
Cell: ({ row }: CellProps<TypesPullReq>) => {
|
||||||
return (
|
return (
|
||||||
<Layout.Horizontal className={css.titleRow} spacing="medium">
|
<Layout.Horizontal className={css.titleRow} spacing="medium">
|
||||||
<img {...stateToImageProps(row.original)} />
|
<PullRequestStateLabel data={row.original} iconOnly />
|
||||||
<Container padding={{ left: 'small' }}>
|
<Container padding={{ left: 'small' }}>
|
||||||
<Layout.Vertical spacing="small">
|
<Layout.Vertical spacing="small">
|
||||||
<Text icon="success-tick" color={Color.GREY_800} className={css.title}>
|
<Text icon="success-tick" color={Color.GREY_800} className={css.title}>
|
||||||
@ -99,11 +96,11 @@ export default function PullRequests() {
|
|||||||
<PageBody error={getErrorMessage(error || prError)} retryOnError={voidFn(refetch)}>
|
<PageBody error={getErrorMessage(error || prError)} retryOnError={voidFn(refetch)}>
|
||||||
<LoadingSpinner visible={loading} />
|
<LoadingSpinner visible={loading} />
|
||||||
|
|
||||||
{repoMetadata && (
|
<Render when={repoMetadata}>
|
||||||
<Layout.Vertical>
|
<Layout.Vertical>
|
||||||
<PullRequestsContentHeader
|
<PullRequestsContentHeader
|
||||||
loading={prLoading}
|
loading={prLoading}
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata as TypesRepository}
|
||||||
onPullRequestFilterChanged={_filter => {
|
onPullRequestFilterChanged={_filter => {
|
||||||
setFilter(_filter)
|
setFilter(_filter)
|
||||||
setPage(1)
|
setPage(1)
|
||||||
@ -114,71 +111,47 @@ export default function PullRequests() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Container padding="xlarge">
|
<Container padding="xlarge">
|
||||||
{!!data?.length && (
|
<Match expr={data?.length}>
|
||||||
<>
|
<Truthy>
|
||||||
<TableV2<TypesPullReq>
|
<>
|
||||||
className={css.table}
|
<TableV2<TypesPullReq>
|
||||||
hideHeaders
|
className={css.table}
|
||||||
columns={columns}
|
hideHeaders
|
||||||
data={data}
|
columns={columns}
|
||||||
getRowClassName={() => css.row}
|
data={data || []}
|
||||||
onRowClick={row => {
|
getRowClassName={() => css.row}
|
||||||
|
onRowClick={row => {
|
||||||
|
history.push(
|
||||||
|
routes.toCODEPullRequest({
|
||||||
|
repoPath: repoMetadata?.path as string,
|
||||||
|
pullRequestId: String(row.number)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
|
</>
|
||||||
|
</Truthy>
|
||||||
|
<Case val={0}>
|
||||||
|
<NoResultCard
|
||||||
|
forSearch={!!searchTerm}
|
||||||
|
message={getString('pullRequestEmpty')}
|
||||||
|
buttonText={getString('newPullRequest')}
|
||||||
|
onButtonClick={() =>
|
||||||
history.push(
|
history.push(
|
||||||
routes.toCODEPullRequest({
|
routes.toCODECompare({
|
||||||
repoPath: repoMetadata.path as string,
|
repoPath: repoMetadata?.path as string,
|
||||||
pullRequestId: String(row.number)
|
diffRefs: makeDiffRefs(repoMetadata?.default_branch as string, '')
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
</Case>
|
||||||
</>
|
</Match>
|
||||||
)}
|
|
||||||
<NoResultCard
|
|
||||||
showWhen={() => data?.length === 0}
|
|
||||||
forSearch={!!searchTerm}
|
|
||||||
message={getString('pullRequestEmpty')}
|
|
||||||
buttonText={getString('newPullRequest')}
|
|
||||||
onButtonClick={() =>
|
|
||||||
history.push(
|
|
||||||
routes.toCODECompare({
|
|
||||||
repoPath: repoMetadata?.path as string,
|
|
||||||
diffRefs: makeDiffRefs(repoMetadata?.default_branch as string, '')
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
)}
|
</Render>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateToImageProps = (pr: TypesPullReq) => {
|
|
||||||
let src = prImgClosed
|
|
||||||
let clazz = css.open
|
|
||||||
|
|
||||||
switch (pr.state) {
|
|
||||||
case PullRequestFilterOption.OPEN:
|
|
||||||
src = prImgOpen
|
|
||||||
clazz = css.open
|
|
||||||
break
|
|
||||||
case PullRequestFilterOption.MERGED:
|
|
||||||
src = prImgMerged
|
|
||||||
clazz = css.merged
|
|
||||||
break
|
|
||||||
case PullRequestFilterOption.CLOSED:
|
|
||||||
src = prImgClosed
|
|
||||||
clazz = css.closed
|
|
||||||
break
|
|
||||||
// TODO: Not supported yet from backend
|
|
||||||
// case PullRequestFilterOption.DRAFT:
|
|
||||||
// src = prImgDraft
|
|
||||||
// clazz = css.draft
|
|
||||||
// break
|
|
||||||
}
|
|
||||||
|
|
||||||
return { src, title: pr.state, className: cx(css.rowImg, clazz) }
|
|
||||||
}
|
|
||||||
|
@ -31,7 +31,7 @@ export function PullRequestsContentHeader({
|
|||||||
{ label: getString('open'), value: PullRequestFilterOption.OPEN },
|
{ label: getString('open'), value: PullRequestFilterOption.OPEN },
|
||||||
{ label: getString('merged'), value: PullRequestFilterOption.MERGED },
|
{ label: getString('merged'), value: PullRequestFilterOption.MERGED },
|
||||||
{ label: getString('closed'), value: PullRequestFilterOption.CLOSED },
|
{ label: getString('closed'), value: PullRequestFilterOption.CLOSED },
|
||||||
{ label: getString('rejected'), value: PullRequestFilterOption.REJECTED },
|
{ label: getString('draft'), value: PullRequestFilterOption.DRAFT },
|
||||||
// { label: getString('yours'), value: PullRequestFilterOption.YOURS },
|
// { label: getString('yours'), value: PullRequestFilterOption.YOURS },
|
||||||
{ label: getString('all'), value: PullRequestFilterOption.ALL }
|
{ label: getString('all'), value: PullRequestFilterOption.ALL }
|
||||||
],
|
],
|
||||||
|
@ -8,7 +8,7 @@ import { useAppContext } from 'AppContext'
|
|||||||
import { CodeIcon } from 'utils/GitUtils'
|
import { CodeIcon } from 'utils/GitUtils'
|
||||||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||||
import { getErrorMessage } from 'utils/Utils'
|
import { getErrorMessage } from 'utils/Utils'
|
||||||
import emptyStateImage from 'images/empty-state.svg'
|
import { Images } from 'images'
|
||||||
import hooks from './mockWebhooks.json'
|
import hooks from './mockWebhooks.json'
|
||||||
import { SettingsContent } from './SettingsContent'
|
import { SettingsContent } from './SettingsContent'
|
||||||
import css from './RepositorySettings.module.scss'
|
import css from './RepositorySettings.module.scss'
|
||||||
@ -74,7 +74,7 @@ export default function RepositorySettings() {
|
|||||||
noData={{
|
noData={{
|
||||||
when: () => repoMetadata !== null,
|
when: () => repoMetadata !== null,
|
||||||
message: getString('noWebHooks'),
|
message: getString('noWebHooks'),
|
||||||
image: emptyStateImage,
|
image: Images.EmptyState,
|
||||||
button: NewWebHookButton
|
button: NewWebHookButton
|
||||||
}}>
|
}}>
|
||||||
<Container className={css.contentContainer}>
|
<Container className={css.contentContainer}>
|
||||||
|
@ -346,6 +346,7 @@ export interface TypesPullReq {
|
|||||||
source_branch?: string
|
source_branch?: string
|
||||||
source_repo_id?: number
|
source_repo_id?: number
|
||||||
state?: EnumPullReqState
|
state?: EnumPullReqState
|
||||||
|
stats?: TypesPullReqStats
|
||||||
target_branch?: string
|
target_branch?: string
|
||||||
target_repo_id?: number
|
target_repo_id?: number
|
||||||
title?: string
|
title?: string
|
||||||
@ -371,6 +372,12 @@ export interface TypesPullReqActivity {
|
|||||||
type?: EnumPullReqActivityType
|
type?: EnumPullReqActivityType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TypesPullReqStats {
|
||||||
|
commits?: number
|
||||||
|
conversations?: number
|
||||||
|
files_changed?: number
|
||||||
|
}
|
||||||
|
|
||||||
export type TypesPullRequestActivityPayloadComment = { [key: string]: any } | null
|
export type TypesPullRequestActivityPayloadComment = { [key: string]: any } | null
|
||||||
|
|
||||||
export interface TypesRepository {
|
export interface TypesRepository {
|
||||||
@ -384,6 +391,7 @@ export interface TypesRepository {
|
|||||||
is_public?: boolean
|
is_public?: boolean
|
||||||
num_closed_pulls?: number
|
num_closed_pulls?: number
|
||||||
num_forks?: number
|
num_forks?: number
|
||||||
|
num_merged_pulls?: number
|
||||||
num_open_pulls?: number
|
num_open_pulls?: number
|
||||||
num_pulls?: number
|
num_pulls?: number
|
||||||
parent_id?: number
|
parent_id?: number
|
||||||
@ -1599,6 +1607,32 @@ export const useMergePullReqOp = ({ repo_ref, pullreq_number, ...props }: UseMer
|
|||||||
{ base: getConfig('code'), pathParams: { repo_ref, pullreq_number }, ...props }
|
{ base: getConfig('code'), pathParams: { repo_ref, pullreq_number }, ...props }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export interface PullReqMetaDataPathParams {
|
||||||
|
repo_ref: string
|
||||||
|
pullreq_number: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PullReqMetaDataProps = Omit<GetProps<void, UsererrorError, void, PullReqMetaDataPathParams>, 'path'> &
|
||||||
|
PullReqMetaDataPathParams
|
||||||
|
|
||||||
|
export const PullReqMetaData = ({ repo_ref, pullreq_number, ...props }: PullReqMetaDataProps) => (
|
||||||
|
<Get<void, UsererrorError, void, PullReqMetaDataPathParams>
|
||||||
|
path={`/repos/${repo_ref}/pullreq/${pullreq_number}/metadata`}
|
||||||
|
base={getConfig('code')}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
export type UsePullReqMetaDataProps = Omit<UseGetProps<void, UsererrorError, void, PullReqMetaDataPathParams>, 'path'> &
|
||||||
|
PullReqMetaDataPathParams
|
||||||
|
|
||||||
|
export const usePullReqMetaData = ({ repo_ref, pullreq_number, ...props }: UsePullReqMetaDataProps) =>
|
||||||
|
useGet<void, UsererrorError, void, PullReqMetaDataPathParams>(
|
||||||
|
(paramsInPath: PullReqMetaDataPathParams) =>
|
||||||
|
`/repos/${paramsInPath.repo_ref}/pullreq/${paramsInPath.pullreq_number}/metadata`,
|
||||||
|
{ base: getConfig('code'), pathParams: { repo_ref, pullreq_number }, ...props }
|
||||||
|
)
|
||||||
|
|
||||||
export interface ReviewerListPullReqPathParams {
|
export interface ReviewerListPullReqPathParams {
|
||||||
repo_ref: string
|
repo_ref: string
|
||||||
pullreq_number: number
|
pullreq_number: number
|
||||||
|
@ -51,13 +51,13 @@ export enum GitCommitAction {
|
|||||||
export enum PullRequestState {
|
export enum PullRequestState {
|
||||||
OPEN = 'open',
|
OPEN = 'open',
|
||||||
MERGED = 'merged',
|
MERGED = 'merged',
|
||||||
CLOSED = 'closed',
|
CLOSED = 'closed'
|
||||||
REJECTED = 'rejected',
|
|
||||||
DRAFT = 'draft'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PullRequestFilterOption = {
|
export const PullRequestFilterOption = {
|
||||||
...PullRequestState,
|
...PullRequestState,
|
||||||
|
// REJECTED: 'rejected',
|
||||||
|
DRAFT: 'draft',
|
||||||
YOURS: 'yours',
|
YOURS: 'yours',
|
||||||
ALL: 'all'
|
ALL: 'all'
|
||||||
}
|
}
|
||||||
@ -65,6 +65,8 @@ export const PullRequestFilterOption = {
|
|||||||
export const CodeIcon = {
|
export const CodeIcon = {
|
||||||
Logo: 'code' as IconName,
|
Logo: 'code' as IconName,
|
||||||
PullRequest: 'git-pull' as IconName,
|
PullRequest: 'git-pull' as IconName,
|
||||||
|
Merged: 'code-merged' as IconName,
|
||||||
|
Draft: 'code-draft' as IconName,
|
||||||
PullRequestRejected: 'main-close' as IconName,
|
PullRequestRejected: 'main-close' as IconName,
|
||||||
Add: 'plus' as IconName,
|
Add: 'plus' as IconName,
|
||||||
BranchSmall: 'code-branch-small' as IconName,
|
BranchSmall: 'code-branch-small' as IconName,
|
||||||
|
@ -661,10 +661,10 @@
|
|||||||
resolved "https://npm.pkg.github.com/download/@harness/design-system/1.4.0/b2a77f73696d71a53765c71efd0a5b28039fa1cf#b2a77f73696d71a53765c71efd0a5b28039fa1cf"
|
resolved "https://npm.pkg.github.com/download/@harness/design-system/1.4.0/b2a77f73696d71a53765c71efd0a5b28039fa1cf#b2a77f73696d71a53765c71efd0a5b28039fa1cf"
|
||||||
integrity sha512-LuzuPEHPkE6xgIuXxn16RCCvPY1NDXF3o1JWlIjxmepoDTkgFuwnV1OhBdQftvAVBawJ5wJP10IIKUL161LdYg==
|
integrity sha512-LuzuPEHPkE6xgIuXxn16RCCvPY1NDXF3o1JWlIjxmepoDTkgFuwnV1OhBdQftvAVBawJ5wJP10IIKUL161LdYg==
|
||||||
|
|
||||||
"@harness/icons@1.95.1":
|
"@harness/icons@1.101.1":
|
||||||
version "1.95.1"
|
version "1.101.1"
|
||||||
resolved "https://npm.pkg.github.com/download/@harness/icons/1.95.1/53de6061dfaa8c8b1ef8aa6271f65456a84bfe46#53de6061dfaa8c8b1ef8aa6271f65456a84bfe46"
|
resolved "https://npm.pkg.github.com/download/@harness/icons/1.101.1/b15024459bc229e20ca6ba48f86d038a576173c9#b15024459bc229e20ca6ba48f86d038a576173c9"
|
||||||
integrity sha512-RZ9OdWLUcVrKR4FJvrJa2ewgZPsK8vk550ZPYC4F3ZRxkN5M8B1HNtrDmmAgJzgO2EeP6+9Xzj8LcbeOWQPlMA==
|
integrity sha512-IMYSDpWT/Hi8XlkM+XjpquJlaCLAVH7aWzbfxkJl/rraD0btym72/08lVM5xTLRlwLgQt9Y3TAL2f0JvWGU0NA==
|
||||||
|
|
||||||
"@harness/jarvis@^0.12.0":
|
"@harness/jarvis@^0.12.0":
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
@ -695,10 +695,10 @@
|
|||||||
resolved "https://npm.pkg.github.com/download/@harness/telemetry/1.0.44/55e75d8caccbcdcb0a11226c813edd631578d9af#55e75d8caccbcdcb0a11226c813edd631578d9af"
|
resolved "https://npm.pkg.github.com/download/@harness/telemetry/1.0.44/55e75d8caccbcdcb0a11226c813edd631578d9af#55e75d8caccbcdcb0a11226c813edd631578d9af"
|
||||||
integrity sha512-t6N3Ie/F9Nw/tANAmptsunebGYBkC3j865bc75MZVL2ZqFM0CBRxFR1MG8zC+hU6uDpr8Drqsn81NmdlVlBSmA==
|
integrity sha512-t6N3Ie/F9Nw/tANAmptsunebGYBkC3j865bc75MZVL2ZqFM0CBRxFR1MG8zC+hU6uDpr8Drqsn81NmdlVlBSmA==
|
||||||
|
|
||||||
"@harness/uicore@3.95.1":
|
"@harness/uicore@3.106.3":
|
||||||
version "3.95.1"
|
version "3.106.3"
|
||||||
resolved "https://npm.pkg.github.com/download/@harness/uicore/3.95.1/4b9551e0299ed56e6f4291e7e75cb48562aa7dd8#4b9551e0299ed56e6f4291e7e75cb48562aa7dd8"
|
resolved "https://npm.pkg.github.com/download/@harness/uicore/3.106.3/64b3c1cfb645a3eaaa2fe5ea481bd96a912de468#64b3c1cfb645a3eaaa2fe5ea481bd96a912de468"
|
||||||
integrity sha512-NSxVyQ5aGLF+3kysijjEeqFPQ4HHlWdYRq9HRKj8lRoT0YLYyvxSxZbGae/Ceq74SutnDk6h36qEK2L6lu8Cfw==
|
integrity sha512-LHDMbhdHHklJCKPj/n9EZBPNiWBPMHacQoInuTcSEswiXdQDThlozJ/3ccrsUk4Qbak16LV4xpDkDYCwolo5Pg==
|
||||||
|
|
||||||
"@harness/use-modal@1.3.0":
|
"@harness/use-modal@1.3.0":
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
|