API integration for different type of merges (#268)
Co-authored-by: Tan Nhu <tnhu@users.noreply.github.com>jobatzil/rename
|
@ -16,7 +16,7 @@ const ExactSharedPackages = [
|
|||
'@harness/monaco-yaml',
|
||||
'monaco-editor',
|
||||
'monaco-editor-core',
|
||||
'monaco-languages',
|
||||
// 'monaco-languages',
|
||||
'monaco-plugin-helpers',
|
||||
'react-monaco-editor'
|
||||
]
|
||||
|
|
|
@ -37,10 +37,10 @@
|
|||
"@blueprintjs/datetime": "3.13.0",
|
||||
"@blueprintjs/select": "3.12.3",
|
||||
"@harness/design-system": "1.4.0",
|
||||
"@harness/icons": "1.95.1",
|
||||
"@harness/icons": "1.101.1",
|
||||
"@harness/ng-tooltip": ">=1.31.25",
|
||||
"@harness/telemetry": ">=1.0.42",
|
||||
"@harness/uicore": "3.95.1",
|
||||
"@harness/uicore": "3.106.3",
|
||||
"@harness/use-modal": "1.3.0",
|
||||
"@popperjs/core": "^2.4.2",
|
||||
"@uiw/react-markdown-editor": "^5.10.1",
|
||||
|
|
|
@ -49,6 +49,7 @@ interface ChangesProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
|||
emptyMessage: string
|
||||
pullRequestMetadata?: TypesPullReq
|
||||
className?: string
|
||||
onCommentUpdate: () => void
|
||||
}
|
||||
|
||||
export const Changes: React.FC<ChangesProps> = ({
|
||||
|
@ -59,6 +60,7 @@ export const Changes: React.FC<ChangesProps> = ({
|
|||
emptyTitle,
|
||||
emptyMessage,
|
||||
pullRequestMetadata,
|
||||
onCommentUpdate,
|
||||
className
|
||||
}) => {
|
||||
const { getString } = useStrings()
|
||||
|
@ -72,7 +74,9 @@ export const Changes: React.FC<ChangesProps> = ({
|
|||
loading,
|
||||
refetch
|
||||
} = 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
|
||||
})
|
||||
const {
|
||||
|
@ -214,6 +218,7 @@ export const Changes: React.FC<ChangesProps> = ({
|
|||
stickyTopPosition={STICKY_TOP_POSITION}
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
onCommentUpdate={onCommentUpdate}
|
||||
/>
|
||||
))}
|
||||
</Layout.Vertical>
|
||||
|
|
|
@ -225,20 +225,20 @@ const CommentsThread = <T = unknown,>({
|
|||
title={
|
||||
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
||||
<Text inline icon="code-chat"></Text>
|
||||
<Avatar name={commentItem.author} size="small" hoverCard={false} />
|
||||
<Avatar name={commentItem?.author} size="small" hoverCard={false} />
|
||||
<Text inline>
|
||||
<strong>{commentItem.author}</strong>
|
||||
<strong>{commentItem?.author}</strong>
|
||||
</Text>
|
||||
<PipeSeparator height={8} />
|
||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
||||
<ReactTimeago date={new Date(commentItem.updated)} />
|
||||
<ReactTimeago date={new Date(commentItem?.updated)} />
|
||||
</Text>
|
||||
|
||||
<Render when={commentItem.updated !== commentItem.created || !!commentItem.deleted}>
|
||||
<Render when={commentItem?.updated !== commentItem?.created || !!commentItem?.deleted}>
|
||||
<>
|
||||
<PipeSeparator height={8} />
|
||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
||||
{getString(commentItem.deleted ? 'deleted' : 'edited')}
|
||||
{getString(commentItem?.deleted ? 'deleted' : 'edited')}
|
||||
</Text>
|
||||
</>
|
||||
</Render>
|
||||
|
@ -249,7 +249,7 @@ const CommentsThread = <T = unknown,>({
|
|||
icon="Options"
|
||||
iconProps={{ size: 14 }}
|
||||
style={{ padding: '5px' }}
|
||||
disabled={!!commentItem.deleted}
|
||||
disabled={!!commentItem?.deleted}
|
||||
width="100px"
|
||||
items={[
|
||||
{
|
||||
|
@ -258,7 +258,7 @@ const CommentsThread = <T = unknown,>({
|
|||
},
|
||||
{
|
||||
text: getString('quote'),
|
||||
onClick: () => onQuote(commentItem.content)
|
||||
onClick: () => onQuote(commentItem?.content)
|
||||
},
|
||||
'-',
|
||||
{
|
||||
|
@ -309,12 +309,12 @@ const CommentsThread = <T = unknown,>({
|
|||
</Container>
|
||||
</Truthy>
|
||||
<Else>
|
||||
<Match expr={commentItem.deleted}>
|
||||
<Match expr={commentItem?.deleted}>
|
||||
<Truthy>
|
||||
<Text className={css.deleted}>{getString('commentDeleted')}</Text>
|
||||
</Truthy>
|
||||
<Else>
|
||||
<MarkdownEditor.Markdown source={commentItem.content} />
|
||||
<MarkdownEditor.Markdown source={commentItem?.content} />
|
||||
</Else>
|
||||
</Match>
|
||||
</Else>
|
||||
|
|
|
@ -6,4 +6,11 @@
|
|||
.description textarea {
|
||||
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 title: string
|
||||
readonly description: string
|
||||
readonly checkbox: string
|
||||
}
|
||||
export default styles
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React from 'react'
|
||||
import { Dialog, Intent } from '@blueprintjs/core'
|
||||
import * as yup from 'yup'
|
||||
import { Render } from 'react-jsx-match'
|
||||
import {
|
||||
Button,
|
||||
ButtonProps,
|
||||
|
@ -35,6 +36,7 @@ import css from './CreatePullRequestModal.module.scss'
|
|||
interface FormData {
|
||||
title: string
|
||||
description: string
|
||||
draft: boolean
|
||||
}
|
||||
|
||||
interface CreatePullRequestModalProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||
|
@ -65,7 +67,8 @@ export function useCreatePullRequestModal({
|
|||
target_branch: targetGitRef,
|
||||
source_branch: sourceGitRef,
|
||||
title: title,
|
||||
description: description
|
||||
description: description,
|
||||
is_draft: formData.draft
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -97,7 +100,8 @@ export function useCreatePullRequestModal({
|
|||
<Formik<FormData>
|
||||
initialValues={{
|
||||
title: '',
|
||||
description: ''
|
||||
description: '',
|
||||
draft: false
|
||||
}}
|
||||
formName="createPullRequest"
|
||||
enableReinitialize={true}
|
||||
|
@ -129,6 +133,7 @@ export function useCreatePullRequestModal({
|
|||
className={css.description}
|
||||
maxLength={1024 * 50}
|
||||
/>
|
||||
<FormInput.CheckBox label={getString('pr.createDraftPR')} name="draft" className={css.checkbox} />
|
||||
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
|
@ -143,7 +148,9 @@ export function useCreatePullRequestModal({
|
|||
<Button text={getString('cancel')} variation={ButtonVariation.LINK} onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="spinner" size={16} />}
|
||||
<Render when={loading}>
|
||||
<Icon intent={Intent.PRIMARY} name="spinner" size={16} />
|
||||
</Render>
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
|
|
|
@ -49,6 +49,7 @@ interface DiffViewerProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
|||
stickyTopPosition?: number
|
||||
readOnly?: boolean
|
||||
pullRequestMetadata?: TypesPullReq
|
||||
onCommentUpdate: () => void
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -62,7 +63,8 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
|||
stickyTopPosition = 0,
|
||||
readOnly,
|
||||
repoMetadata,
|
||||
pullRequestMetadata
|
||||
pullRequestMetadata,
|
||||
onCommentUpdate
|
||||
}) => {
|
||||
const { getString } = useStrings()
|
||||
const [viewed, setViewed] = useState(false)
|
||||
|
@ -379,6 +381,10 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
onCommentUpdate()
|
||||
}
|
||||
|
||||
return [result, updatedItem]
|
||||
}}
|
||||
/>,
|
||||
|
|
|
@ -16,7 +16,9 @@ export enum CommentType {
|
|||
CODE_COMMENT = 'code-comment',
|
||||
TITLE_CHANGE = 'title-change',
|
||||
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'
|
||||
|
@ -194,7 +196,7 @@ export const activityToCommentItem = (activity: TypesPullReqActivity): CommentIt
|
|||
created: activity.created as number,
|
||||
updated: activity.edited 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
|
||||
})
|
||||
|
||||
|
|
|
@ -85,9 +85,15 @@
|
|||
overflow: auto;
|
||||
|
||||
:global {
|
||||
.wmde-markdown .anchor {
|
||||
.wmde-markdown {
|
||||
.anchor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ import { Button, Container, ButtonVariation, NoDataCard, IconName } from '@harne
|
|||
import { noop } from 'lodash-es'
|
||||
import { CodeIcon } from 'utils/GitUtils'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import emptyStateImage from 'images/empty-state.svg'
|
||||
import { Images } from 'images'
|
||||
import css from './NoResultCard.module.scss'
|
||||
|
||||
interface NoResultCardProps {
|
||||
showWhen: () => boolean
|
||||
showWhen?: () => boolean
|
||||
forSearch: boolean
|
||||
title?: string
|
||||
message?: string
|
||||
|
@ -18,7 +18,7 @@ interface NoResultCardProps {
|
|||
}
|
||||
|
||||
export const NoResultCard: React.FC<NoResultCardProps> = ({
|
||||
showWhen,
|
||||
showWhen = () => true,
|
||||
forSearch,
|
||||
title,
|
||||
message,
|
||||
|
@ -36,7 +36,7 @@ export const NoResultCard: React.FC<NoResultCardProps> = ({
|
|||
return (
|
||||
<Container className={css.main}>
|
||||
<NoDataCard
|
||||
image={emptyStateImage}
|
||||
image={Images.EmptyState}
|
||||
messageTitle={forSearch ? title || getString('noResultTitle') : undefined}
|
||||
message={
|
||||
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 */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly state: string
|
||||
readonly prStatus: string
|
||||
readonly iconOnly: string
|
||||
readonly open: string
|
||||
readonly merged: string
|
||||
readonly closed: string
|
||||
readonly rejected: string
|
||||
readonly draft: string
|
||||
}
|
||||
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'
|
||||
|
||||
useEffect(() => {
|
||||
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions)
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions)
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions)
|
||||
monaco.languages.typescript?.typescriptDefaults?.setDiagnosticsOptions?.(diagnosticsOptions)
|
||||
monaco.languages.typescript?.javascriptDefaults?.setDiagnosticsOptions?.(diagnosticsOptions)
|
||||
monaco.languages.typescript?.typescriptDefaults?.setCompilerOptions?.(compilerOptions)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.main {
|
||||
:global {
|
||||
.wmde-markdown {
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor'
|
||||
import type { SourceCodeEditorProps } from 'utils/Utils'
|
||||
import css from './SourceCodeViewer.module.scss'
|
||||
|
||||
interface MarkdownViewerProps {
|
||||
source: string
|
||||
|
@ -13,7 +14,7 @@ export function MarkdownViewer({ source }: MarkdownViewerProps) {
|
|||
const { getString } = useStrings()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container className={css.main}>
|
||||
<Suspense fallback={<Text>{getString('loading')}</Text>}>
|
||||
<MarkdownEditor.Markdown
|
||||
source={source}
|
||||
|
|
|
@ -80,6 +80,7 @@ export interface StringsMap {
|
|||
deployKeys: string
|
||||
description: string
|
||||
diff: string
|
||||
draft: string
|
||||
edit: string
|
||||
editFile: string
|
||||
editNotAllowed: string
|
||||
|
@ -149,9 +150,11 @@ export interface StringsMap {
|
|||
payloadUrl: string
|
||||
payloadUrlLabel: string
|
||||
'pr.ableToMerge': string
|
||||
'pr.authorCommentedPR': string
|
||||
'pr.branchHasNoConflicts': string
|
||||
'pr.buttonText': string
|
||||
'pr.cantMerge': string
|
||||
'pr.createDraftPR': string
|
||||
'pr.descriptionPlaceHolder': string
|
||||
'pr.diffStatsLabel': string
|
||||
'pr.diffStatus': string
|
||||
|
@ -164,12 +167,25 @@ export interface StringsMap {
|
|||
'pr.failedToUpdateTitle': string
|
||||
'pr.fileDeleted': 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.metaLine': string
|
||||
'pr.modalTitle': string
|
||||
'pr.openForReview': string
|
||||
'pr.prBranchPushInfo': string
|
||||
'pr.prCanBeMerged': string
|
||||
'pr.prMerged': string
|
||||
'pr.prMergedInfo': string
|
||||
'pr.prStateChanged': string
|
||||
'pr.prStateChangedDraft': string
|
||||
'pr.readyForReview': string
|
||||
'pr.reviewChanges': string
|
||||
'pr.reviewSubmitted': string
|
||||
'pr.showDiff': string
|
||||
|
@ -181,6 +197,8 @@ export interface StringsMap {
|
|||
'pr.titleChangedTable': string
|
||||
'pr.titlePlaceHolder': string
|
||||
'pr.unified': string
|
||||
'prState.draftDesc': string
|
||||
'prState.draftHeading': string
|
||||
prefixBase: string
|
||||
prefixCompare: string
|
||||
prev: string
|
||||
|
|
|
@ -156,6 +156,7 @@ enableSSLVerification: 'Enable SSL verification'
|
|||
createWebhook: Create Webhook
|
||||
webhook: Webhook
|
||||
diff: Diff
|
||||
draft: Draft
|
||||
conversation: Conversation
|
||||
pr:
|
||||
ableToMerge: Able to merge.
|
||||
|
@ -164,8 +165,9 @@ pr:
|
|||
titlePlaceHolder: Enter the pull request title
|
||||
descriptionPlaceHolder: Leave pull request comment here
|
||||
modalTitle: Open a pull request
|
||||
buttonText: Open pull request
|
||||
metaLine: '{user} wants to merge {number} {number|1:commit,commits} into {target} from {source}'
|
||||
buttonText: Create pull request
|
||||
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}'
|
||||
statusLine: '#{number} {state|merged:merged,closed:closed,rejected:rejected,opened} {time} by {user}'
|
||||
diffStatus: '{status|deleted:Deleted,new:Added,renamed:Renamed,copied:Copied,Changed}'
|
||||
|
@ -189,11 +191,29 @@ pr:
|
|||
prMerged: This Pull Request was merged
|
||||
reviewSubmitted: Review submitted.
|
||||
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}.'
|
||||
titleChangedTable: |
|
||||
### Other title changes in history
|
||||
| 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 ...'
|
||||
general: 'General'
|
||||
webhooks: 'Webhooks'
|
||||
|
|
|
@ -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 { Container, PageBody, NoDataCard, Tabs } from '@harness/uicore'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
@ -7,7 +8,7 @@ import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
|||
import { useStrings } from 'framework/strings'
|
||||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||
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 { CommitsView } from 'components/CommitsView/CommitsView'
|
||||
import { Changes } from 'components/Changes/Changes'
|
||||
|
@ -81,7 +82,7 @@ export default function Compare() {
|
|||
|
||||
{(!targetGitRef || !sourceGitRef) && (
|
||||
<Container className={css.noDataContainer}>
|
||||
<NoDataCard image={emptyStateImage} message={getString('selectToViewMore')} />
|
||||
<NoDataCard image={Images.EmptyState} message={getString('selectToViewMore')} />
|
||||
</Container>
|
||||
)}
|
||||
|
||||
|
@ -120,6 +121,7 @@ export default function Compare() {
|
|||
sourceBranch={sourceGitRef}
|
||||
emptyTitle={getString('noChanges')}
|
||||
emptyMessage={getString('noChangesCompare')}
|
||||
onCommentUpdate={noop} // TODO: Update tab stats
|
||||
/>
|
||||
</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),
|
||||
0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
||||
|
||||
border-radius: 5px;
|
||||
padding: var(--spacing-xlarge) !important;
|
||||
border-radius: 4px;
|
||||
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 {
|
||||
|
@ -70,7 +77,7 @@
|
|||
.newCommentCreated {
|
||||
box-shadow: 0px 0px 5px rgb(37 41 192);
|
||||
border-radius: 4px;
|
||||
transition: box-shadow 1s ease-in-out;
|
||||
transition: box-shadow 0.5s ease-in-out;
|
||||
|
||||
&.clear {
|
||||
box-shadow: none;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly box: string
|
||||
readonly descBox: string
|
||||
readonly snapshot: string
|
||||
readonly title: string
|
||||
readonly fname: string
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Avatar,
|
||||
Color,
|
||||
|
@ -20,9 +20,8 @@ import { CodeIcon, GitInfoProps } from 'utils/GitUtils'
|
|||
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
||||
import { useStrings } from 'framework/strings'
|
||||
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 { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
|
||||
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||
import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
|
||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||
|
@ -33,18 +32,14 @@ import {
|
|||
PullRequestCodeCommentPayload
|
||||
} from 'components/DiffViewer/DiffViewerUtils'
|
||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||
import { PullRequestStatusInfo } from './PullRequestStatusInfo/PullRequestStatusInfo'
|
||||
import { PullRequestActionsBox } from './PullRequestActionsBox/PullRequestActionsBox'
|
||||
import css from './Conversation.module.scss'
|
||||
|
||||
interface ConversationProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
|
||||
refreshPullRequestMetadata: () => void
|
||||
onCommentUpdate: () => void
|
||||
}
|
||||
|
||||
export const Conversation: React.FC<ConversationProps> = ({
|
||||
repoMetadata,
|
||||
pullRequestMetadata,
|
||||
refreshPullRequestMetadata
|
||||
}) => {
|
||||
export const Conversation: React.FC<ConversationProps> = ({ repoMetadata, pullRequestMetadata, onCommentUpdate }) => {
|
||||
const { getString } = useStrings()
|
||||
const { currentUser } = useAppContext()
|
||||
const {
|
||||
|
@ -62,12 +57,8 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
// contains a parent comment and multiple replied comments
|
||||
const blocks: CommentItem<TypesPullReqActivity>[][] = []
|
||||
|
||||
if (newComments.length) {
|
||||
blocks.push(orderBy(newComments, 'edited', 'desc').map(activityToCommentItem))
|
||||
}
|
||||
|
||||
// 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]
|
||||
)
|
||||
|
||||
|
@ -84,20 +75,26 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
blocks.push(parentActivity.map(activityToCommentItem))
|
||||
})
|
||||
|
||||
// Group title-change events into one single block
|
||||
const titleChangeItems =
|
||||
blocks.filter(
|
||||
_activities => isSystemComment(_activities) && _activities[0].payload?.type === CommentType.TITLE_CHANGE
|
||||
) || []
|
||||
|
||||
titleChangeItems.forEach((value, index) => {
|
||||
if (index > 0) {
|
||||
titleChangeItems[0].push(...value)
|
||||
if (newComments.length) {
|
||||
blocks.push(orderBy(newComments, 'edited', 'asc').map(activityToCommentItem))
|
||||
}
|
||||
})
|
||||
titleChangeItems.shift()
|
||||
|
||||
return blocks.filter(_activities => !titleChangeItems.includes(_activities))
|
||||
// Group title-change events into one single block
|
||||
// Disabled for now, @see https://harness.atlassian.net/browse/SCM-79
|
||||
// const titleChangeItems =
|
||||
// blocks.filter(
|
||||
// _activities => isSystemComment(_activities) && _activities[0].payload?.type === CommentType.TITLE_CHANGE
|
||||
// ) || []
|
||||
|
||||
// titleChangeItems.forEach((value, index) => {
|
||||
// if (index > 0) {
|
||||
// titleChangeItems[0].push(...value)
|
||||
// }
|
||||
// })
|
||||
// titleChangeItems.shift()
|
||||
// return blocks.filter(_activities => !titleChangeItems.includes(_activities))
|
||||
|
||||
return blocks
|
||||
}, [activities, newComments])
|
||||
const path = useMemo(
|
||||
() => `/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 confirmAct = useConfirmAct()
|
||||
const [commentCreated, setCommentCreated] = useState(false)
|
||||
const refreshPR = useCallback(() => {
|
||||
onCommentUpdate()
|
||||
refetchActivities()
|
||||
}, [onCommentUpdate, refetchActivities])
|
||||
|
||||
useAnimateNewCommentBox(commentCreated, setCommentCreated)
|
||||
|
||||
|
@ -115,20 +116,17 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
<PullRequestTabContentWrapper loading={loading} error={error} onRetry={refetchActivities}>
|
||||
<Container>
|
||||
<Layout.Vertical spacing="xlarge">
|
||||
<PullRequestStatusInfo
|
||||
<PullRequestActionsBox
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
onMerge={() => {
|
||||
refreshPullRequestMetadata()
|
||||
refetchActivities()
|
||||
}}
|
||||
onPRStateChanged={refreshPR}
|
||||
/>
|
||||
<Container>
|
||||
<Layout.Vertical spacing="xlarge">
|
||||
<DescriptionBox
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
refreshPullRequestMetadata={refreshPullRequestMetadata}
|
||||
onCommentUpdate={onCommentUpdate}
|
||||
/>
|
||||
|
||||
{activityBlocks?.map((blocks, index) => {
|
||||
|
@ -145,7 +143,7 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
<CommentBox
|
||||
key={threadId}
|
||||
fluid
|
||||
className={cx({ [css.newCommentCreated]: commentCreated && !index })}
|
||||
className={cx({ [css.newCommentCreated]: commentCreated && index === activityBlocks.length - 1 })}
|
||||
getString={getString}
|
||||
commentItems={commentItems}
|
||||
currentUserName={currentUser.display_name}
|
||||
|
@ -195,6 +193,10 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
break
|
||||
}
|
||||
|
||||
if (result) {
|
||||
onCommentUpdate()
|
||||
}
|
||||
|
||||
return [result, updatedItem]
|
||||
}}
|
||||
outlets={{
|
||||
|
@ -227,6 +229,11 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
result = false
|
||||
showError(getErrorMessage(exception), 0)
|
||||
})
|
||||
|
||||
if (result) {
|
||||
onCommentUpdate()
|
||||
}
|
||||
|
||||
return [result, updatedItem]
|
||||
}}
|
||||
/>
|
||||
|
@ -241,10 +248,10 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
const DescriptionBox: React.FC<ConversationProps> = ({
|
||||
repoMetadata,
|
||||
pullRequestMetadata,
|
||||
refreshPullRequestMetadata
|
||||
onCommentUpdate: refreshPullRequestMetadata
|
||||
}) => {
|
||||
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 [content, setContent] = useState(originalContent)
|
||||
const { getString } = useStrings()
|
||||
|
@ -259,15 +266,29 @@ const DescriptionBox: React.FC<ConversationProps> = ({
|
|||
<Container className={css.box}>
|
||||
<Layout.Vertical spacing="medium">
|
||||
<Container>
|
||||
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
||||
<Layout.Horizontal spacing="xsmall" style={{ alignItems: 'center' }}>
|
||||
<StringSubstitute
|
||||
str={getString('pr.authorCommentedPR')}
|
||||
vars={{
|
||||
author: (
|
||||
<>
|
||||
<Avatar name={name} size="small" hoverCard={false} />
|
||||
<Text inline>
|
||||
<Text inline margin={{ right: 'xsmall' }}>
|
||||
<strong>{name}</strong>
|
||||
</Text>
|
||||
<PipeSeparator height={8} />
|
||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
||||
<ReactTimeago date={updated} />
|
||||
</>
|
||||
),
|
||||
time: (
|
||||
<Text inline>
|
||||
<ReactTimeago date={pullRequestMetadata.created as number} />
|
||||
</Text>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* <PipeSeparator height={8} />
|
||||
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
|
||||
</Text> */}
|
||||
<FlexExpander />
|
||||
<OptionsMenuButton
|
||||
isDark={false}
|
||||
|
@ -288,15 +309,16 @@ const DescriptionBox: React.FC<ConversationProps> = ({
|
|||
<MarkdownEditorWithPreview
|
||||
value={content}
|
||||
onSave={value => {
|
||||
mutate({
|
||||
const payload: OpenapiUpdatePullReqRequest = {
|
||||
title: pullRequestMetadata.title,
|
||||
description: value
|
||||
})
|
||||
}
|
||||
mutate(payload)
|
||||
.then(() => {
|
||||
setContent(value)
|
||||
setOriginalContent(value)
|
||||
setEdit(false)
|
||||
setUpdated(Date.now())
|
||||
// setUpdated(Date.now())
|
||||
refreshPullRequestMetadata()
|
||||
})
|
||||
.catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate')))
|
||||
|
@ -322,7 +344,7 @@ const DescriptionBox: React.FC<ConversationProps> = ({
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -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: {
|
||||
return (
|
||||
<Container className={css.box}>
|
||||
|
@ -412,15 +496,20 @@ const SystemBox: React.FC<SystemBoxProps> = ({ pullRequestMetadata, commentItems
|
|||
user: <strong>{payload?.author?.display_name}</strong>,
|
||||
old: (
|
||||
<strong>
|
||||
<s>{payload?.payload?.old}</s>
|
||||
<s>{(payload?.payload as Unknown)?.old}</s>
|
||||
</strong>
|
||||
),
|
||||
new: <strong>{payload?.payload?.new}</strong>
|
||||
new: <strong>{(payload?.payload as Unknown)?.new}</strong>
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
<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} />
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
|
@ -435,8 +524,8 @@ const SystemBox: React.FC<SystemBoxProps> = ({ pullRequestMetadata, commentItems
|
|||
.filter((_, index) => index > 0)
|
||||
.map(
|
||||
item =>
|
||||
`|${item.author}|<s>${item.payload?.payload?.old}</s>|${
|
||||
item.payload?.payload?.new
|
||||
`|${item.author}|<s>${(item.payload?.payload as Unknown)?.old}</s>|${
|
||||
(item.payload?.payload as Unknown)?.new
|
||||
}|${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: {
|
||||
readonly main: string
|
||||
readonly merged: string
|
||||
readonly layout: string
|
||||
readonly secondaryButton: string
|
||||
readonly btn: string
|
||||
readonly heading: string
|
||||
readonly sub: string
|
||||
readonly popover: string
|
||||
readonly menuItem: string
|
||||
readonly btnWrapper: string
|
||||
}
|
||||
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={[
|
||||
{
|
||||
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: (
|
||||
<Conversation
|
||||
repoMetadata={repoMetadata as TypesRepository}
|
||||
pullRequestMetadata={prData as TypesPullReq}
|
||||
refreshPullRequestMetadata={voidFn(refetchPullRequest)}
|
||||
onCommentUpdate={voidFn(refetchPullRequest)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
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: (
|
||||
<PullRequestCommits
|
||||
repoMetadata={repoMetadata as TypesRepository}
|
||||
|
@ -125,7 +137,13 @@ export default function PullRequest() {
|
|||
},
|
||||
{
|
||||
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: (
|
||||
<Container className={css.changes}>
|
||||
<Changes
|
||||
|
@ -135,6 +153,7 @@ export default function PullRequest() {
|
|||
sourceBranch={prData?.source_branch}
|
||||
emptyTitle={getString('noChanges')}
|
||||
emptyMessage={getString('noChangesPR')}
|
||||
onCommentUpdate={voidFn(refetchPullRequest)}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@ export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'p
|
|||
refetch,
|
||||
response
|
||||
} = useGet<TypesCommit[]>({
|
||||
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
||||
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullRequestMetadata.number}/commits`,
|
||||
queryParams: {
|
||||
limit,
|
||||
page,
|
||||
|
|
|
@ -2,11 +2,11 @@ import React from 'react'
|
|||
import { Container, Text, Layout, StringSubstitute } from '@harness/uicore'
|
||||
import cx from 'classnames'
|
||||
import ReactTimeago from 'react-timeago'
|
||||
import { GitInfoProps, PullRequestState } from 'utils/GitUtils'
|
||||
import type { GitInfoProps } from 'utils/GitUtils'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { useStrings } from 'framework/strings'
|
||||
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 { GitRefLink } from 'components/GitRefLink/GitRefLink'
|
||||
import css from './PullRequestMetaLine.module.scss'
|
||||
|
@ -17,14 +17,16 @@ export const PullRequestMetaLine: React.FC<TypesPullReq & Pick<GitInfoProps, 're
|
|||
source_branch,
|
||||
author,
|
||||
edited,
|
||||
merged,
|
||||
state
|
||||
state,
|
||||
is_draft,
|
||||
stats
|
||||
}) => {
|
||||
const { getString } = useStrings()
|
||||
const { routes } = useAppContext()
|
||||
const vars = {
|
||||
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: (
|
||||
<GitRefLink
|
||||
text={target_branch as string}
|
||||
|
@ -42,7 +44,7 @@ export const PullRequestMetaLine: React.FC<TypesPullReq & Pick<GitInfoProps, 're
|
|||
return (
|
||||
<Container padding={{ left: 'xlarge' }} className={css.main}>
|
||||
<Layout.Horizontal spacing="small" className={css.layout}>
|
||||
<PRStateLabel state={merged ? PullRequestState.MERGED : (state as PullRequestState)} />
|
||||
<PullRequestStateLabel data={{ is_draft, state }} />
|
||||
<Text className={css.metaline}>
|
||||
<StringSubstitute str={getString('pr.metaLine')} vars={vars} />
|
||||
</Text>
|
||||
|
|
|
@ -14,31 +14,8 @@
|
|||
}
|
||||
|
||||
.titleRow {
|
||||
padding-left: var(--spacing-medium);
|
||||
padding-left: var(--spacing-small);
|
||||
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 title: string
|
||||
readonly titleRow: string
|
||||
readonly rowImg: string
|
||||
readonly open: string
|
||||
readonly merged: string
|
||||
readonly closed: string
|
||||
readonly draft: string
|
||||
}
|
||||
export default styles
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useMemo, useState } from 'react'
|
||||
import { Container, PageBody, Text, Color, TableV2, Layout, StringSubstitute } from '@harness/uicore'
|
||||
import cx from 'classnames'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { useGet } from 'restful-react'
|
||||
import type { CellProps, Column } from 'react-table'
|
||||
import { Case, Match, Render, Truthy } from 'react-jsx-match'
|
||||
import ReactTimeago from 'react-timeago'
|
||||
import { makeDiffRefs, PullRequestFilterOption } from 'utils/GitUtils'
|
||||
import { useAppContext } from 'AppContext'
|
||||
|
@ -12,15 +12,12 @@ import { useStrings } from 'framework/strings'
|
|||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
||||
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 { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
||||
import { PullRequestStateLabel } from 'components/PullRequestStateLabel/PullRequestStateLabel'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||
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'
|
||||
|
||||
export default function PullRequests() {
|
||||
|
@ -56,7 +53,7 @@ export default function PullRequests() {
|
|||
Cell: ({ row }: CellProps<TypesPullReq>) => {
|
||||
return (
|
||||
<Layout.Horizontal className={css.titleRow} spacing="medium">
|
||||
<img {...stateToImageProps(row.original)} />
|
||||
<PullRequestStateLabel data={row.original} iconOnly />
|
||||
<Container padding={{ left: 'small' }}>
|
||||
<Layout.Vertical spacing="small">
|
||||
<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)}>
|
||||
<LoadingSpinner visible={loading} />
|
||||
|
||||
{repoMetadata && (
|
||||
<Render when={repoMetadata}>
|
||||
<Layout.Vertical>
|
||||
<PullRequestsContentHeader
|
||||
loading={prLoading}
|
||||
repoMetadata={repoMetadata}
|
||||
repoMetadata={repoMetadata as TypesRepository}
|
||||
onPullRequestFilterChanged={_filter => {
|
||||
setFilter(_filter)
|
||||
setPage(1)
|
||||
|
@ -114,18 +111,19 @@ export default function PullRequests() {
|
|||
}}
|
||||
/>
|
||||
<Container padding="xlarge">
|
||||
{!!data?.length && (
|
||||
<Match expr={data?.length}>
|
||||
<Truthy>
|
||||
<>
|
||||
<TableV2<TypesPullReq>
|
||||
className={css.table}
|
||||
hideHeaders
|
||||
columns={columns}
|
||||
data={data}
|
||||
data={data || []}
|
||||
getRowClassName={() => css.row}
|
||||
onRowClick={row => {
|
||||
history.push(
|
||||
routes.toCODEPullRequest({
|
||||
repoPath: repoMetadata.path as string,
|
||||
repoPath: repoMetadata?.path as string,
|
||||
pullRequestId: String(row.number)
|
||||
})
|
||||
)
|
||||
|
@ -133,9 +131,9 @@ export default function PullRequests() {
|
|||
/>
|
||||
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||
</>
|
||||
)}
|
||||
</Truthy>
|
||||
<Case val={0}>
|
||||
<NoResultCard
|
||||
showWhen={() => data?.length === 0}
|
||||
forSearch={!!searchTerm}
|
||||
message={getString('pullRequestEmpty')}
|
||||
buttonText={getString('newPullRequest')}
|
||||
|
@ -148,37 +146,12 @@ export default function PullRequests() {
|
|||
)
|
||||
}
|
||||
/>
|
||||
</Case>
|
||||
</Match>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
)}
|
||||
</Render>
|
||||
</PageBody>
|
||||
</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('merged'), value: PullRequestFilterOption.MERGED },
|
||||
{ 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('all'), value: PullRequestFilterOption.ALL }
|
||||
],
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useAppContext } from 'AppContext'
|
|||
import { CodeIcon } from 'utils/GitUtils'
|
||||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import emptyStateImage from 'images/empty-state.svg'
|
||||
import { Images } from 'images'
|
||||
import hooks from './mockWebhooks.json'
|
||||
import { SettingsContent } from './SettingsContent'
|
||||
import css from './RepositorySettings.module.scss'
|
||||
|
@ -74,7 +74,7 @@ export default function RepositorySettings() {
|
|||
noData={{
|
||||
when: () => repoMetadata !== null,
|
||||
message: getString('noWebHooks'),
|
||||
image: emptyStateImage,
|
||||
image: Images.EmptyState,
|
||||
button: NewWebHookButton
|
||||
}}>
|
||||
<Container className={css.contentContainer}>
|
||||
|
|
|
@ -346,6 +346,7 @@ export interface TypesPullReq {
|
|||
source_branch?: string
|
||||
source_repo_id?: number
|
||||
state?: EnumPullReqState
|
||||
stats?: TypesPullReqStats
|
||||
target_branch?: string
|
||||
target_repo_id?: number
|
||||
title?: string
|
||||
|
@ -371,6 +372,12 @@ export interface TypesPullReqActivity {
|
|||
type?: EnumPullReqActivityType
|
||||
}
|
||||
|
||||
export interface TypesPullReqStats {
|
||||
commits?: number
|
||||
conversations?: number
|
||||
files_changed?: number
|
||||
}
|
||||
|
||||
export type TypesPullRequestActivityPayloadComment = { [key: string]: any } | null
|
||||
|
||||
export interface TypesRepository {
|
||||
|
@ -384,6 +391,7 @@ export interface TypesRepository {
|
|||
is_public?: boolean
|
||||
num_closed_pulls?: number
|
||||
num_forks?: number
|
||||
num_merged_pulls?: number
|
||||
num_open_pulls?: number
|
||||
num_pulls?: 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 }
|
||||
)
|
||||
|
||||
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 {
|
||||
repo_ref: string
|
||||
pullreq_number: number
|
||||
|
|
|
@ -1624,6 +1624,53 @@ paths:
|
|||
description: Unprocessable Entity
|
||||
tags:
|
||||
- pullreq
|
||||
/repos/{repo_ref}/pullreq/{pullreq_number}/metadata:
|
||||
get:
|
||||
operationId: pullReqMetaData
|
||||
parameters:
|
||||
- in: path
|
||||
name: repo_ref
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- in: path
|
||||
name: pullreq_number
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
description: OK
|
||||
'401':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UsererrorError'
|
||||
description: Unauthorized
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UsererrorError'
|
||||
description: Forbidden
|
||||
'404':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UsererrorError'
|
||||
description: Not Found
|
||||
'500':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UsererrorError'
|
||||
description: Internal Server Error
|
||||
tags:
|
||||
- pullreq
|
||||
/repos/{repo_ref}/pullreq/{pullreq_number}/reviewers:
|
||||
get:
|
||||
operationId: reviewerListPullReq
|
||||
|
@ -3834,6 +3881,8 @@ components:
|
|||
type: integer
|
||||
state:
|
||||
$ref: '#/components/schemas/EnumPullReqState'
|
||||
stats:
|
||||
$ref: '#/components/schemas/TypesPullReqStats'
|
||||
target_branch:
|
||||
type: string
|
||||
target_repo_id:
|
||||
|
@ -3882,6 +3931,15 @@ components:
|
|||
type:
|
||||
$ref: '#/components/schemas/EnumPullReqActivityType'
|
||||
type: object
|
||||
TypesPullReqStats:
|
||||
properties:
|
||||
commits:
|
||||
type: integer
|
||||
conversations:
|
||||
type: integer
|
||||
files_changed:
|
||||
type: integer
|
||||
type: object
|
||||
TypesPullRequestActivityPayloadComment:
|
||||
additionalProperties: {}
|
||||
nullable: true
|
||||
|
@ -3908,6 +3966,8 @@ components:
|
|||
type: integer
|
||||
num_forks:
|
||||
type: integer
|
||||
num_merged_pulls:
|
||||
type: integer
|
||||
num_open_pulls:
|
||||
type: integer
|
||||
num_pulls:
|
||||
|
|
|
@ -51,13 +51,13 @@ export enum GitCommitAction {
|
|||
export enum PullRequestState {
|
||||
OPEN = 'open',
|
||||
MERGED = 'merged',
|
||||
CLOSED = 'closed',
|
||||
REJECTED = 'rejected',
|
||||
DRAFT = 'draft'
|
||||
CLOSED = 'closed'
|
||||
}
|
||||
|
||||
export const PullRequestFilterOption = {
|
||||
...PullRequestState,
|
||||
// REJECTED: 'rejected',
|
||||
DRAFT: 'draft',
|
||||
YOURS: 'yours',
|
||||
ALL: 'all'
|
||||
}
|
||||
|
@ -65,6 +65,8 @@ export const PullRequestFilterOption = {
|
|||
export const CodeIcon = {
|
||||
Logo: 'code' as IconName,
|
||||
PullRequest: 'git-pull' as IconName,
|
||||
Merged: 'code-merged' as IconName,
|
||||
Draft: 'code-draft' as IconName,
|
||||
PullRequestRejected: 'main-close' as IconName,
|
||||
Add: 'plus' 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"
|
||||
integrity sha512-LuzuPEHPkE6xgIuXxn16RCCvPY1NDXF3o1JWlIjxmepoDTkgFuwnV1OhBdQftvAVBawJ5wJP10IIKUL161LdYg==
|
||||
|
||||
"@harness/icons@1.95.1":
|
||||
version "1.95.1"
|
||||
resolved "https://npm.pkg.github.com/download/@harness/icons/1.95.1/53de6061dfaa8c8b1ef8aa6271f65456a84bfe46#53de6061dfaa8c8b1ef8aa6271f65456a84bfe46"
|
||||
integrity sha512-RZ9OdWLUcVrKR4FJvrJa2ewgZPsK8vk550ZPYC4F3ZRxkN5M8B1HNtrDmmAgJzgO2EeP6+9Xzj8LcbeOWQPlMA==
|
||||
"@harness/icons@1.101.1":
|
||||
version "1.101.1"
|
||||
resolved "https://npm.pkg.github.com/download/@harness/icons/1.101.1/b15024459bc229e20ca6ba48f86d038a576173c9#b15024459bc229e20ca6ba48f86d038a576173c9"
|
||||
integrity sha512-IMYSDpWT/Hi8XlkM+XjpquJlaCLAVH7aWzbfxkJl/rraD0btym72/08lVM5xTLRlwLgQt9Y3TAL2f0JvWGU0NA==
|
||||
|
||||
"@harness/jarvis@^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"
|
||||
integrity sha512-t6N3Ie/F9Nw/tANAmptsunebGYBkC3j865bc75MZVL2ZqFM0CBRxFR1MG8zC+hU6uDpr8Drqsn81NmdlVlBSmA==
|
||||
|
||||
"@harness/uicore@3.95.1":
|
||||
version "3.95.1"
|
||||
resolved "https://npm.pkg.github.com/download/@harness/uicore/3.95.1/4b9551e0299ed56e6f4291e7e75cb48562aa7dd8#4b9551e0299ed56e6f4291e7e75cb48562aa7dd8"
|
||||
integrity sha512-NSxVyQ5aGLF+3kysijjEeqFPQ4HHlWdYRq9HRKj8lRoT0YLYyvxSxZbGae/Ceq74SutnDk6h36qEK2L6lu8Cfw==
|
||||
"@harness/uicore@3.106.3":
|
||||
version "3.106.3"
|
||||
resolved "https://npm.pkg.github.com/download/@harness/uicore/3.106.3/64b3c1cfb645a3eaaa2fe5ea481bd96a912de468#64b3c1cfb645a3eaaa2fe5ea481bd96a912de468"
|
||||
integrity sha512-LHDMbhdHHklJCKPj/n9EZBPNiWBPMHacQoInuTcSEswiXdQDThlozJ/3ccrsUk4Qbak16LV4xpDkDYCwolo5Pg==
|
||||
|
||||
"@harness/use-modal@1.3.0":
|
||||
version "1.3.0"
|
||||
|
|