feat: Allow empty PR description

This commit is contained in:
“tan-nhu” 2023-06-02 17:21:32 -07:00
parent 674dcc11d2
commit 404631a22d
12 changed files with 249 additions and 154 deletions

View File

@ -27,7 +27,7 @@ import type { CODERoutes } from 'RouteDefinitions'
import css from './CommitsView.module.scss' import css from './CommitsView.module.scss'
interface CommitsViewProps extends Pick<GitInfoProps, 'repoMetadata'> { interface CommitsViewProps extends Pick<GitInfoProps, 'repoMetadata'> {
commits: TypesCommit[] commits: TypesCommit[] | null
emptyTitle: string emptyTitle: string
emptyMessage: string emptyMessage: string
prHasChanged?: boolean prHasChanged?: boolean
@ -116,7 +116,7 @@ export function CommitsView({
/> />
)} )}
</Layout.Horizontal> </Layout.Horizontal>
{!!commits.length && {!!commits?.length &&
Object.entries(commitsGroupedByDate).map(([date, commitsByDate]) => { Object.entries(commitsGroupedByDate).map(([date, commitsByDate]) => {
return ( return (
<ThreadSection <ThreadSection

View File

@ -8,7 +8,11 @@ export enum UserPreference {
PULL_REQUEST_CREATION_OPTION = 'PULL_REQUEST_CREATION_OPTION' PULL_REQUEST_CREATION_OPTION = 'PULL_REQUEST_CREATION_OPTION'
} }
export function useUserPreference<T = string>(key: UserPreference, defaultValue: T): [T, (val: T) => void] { export function useUserPreference<T = string>(
key: UserPreference,
defaultValue: T,
filter: (val: T) => boolean = () => true
): [T, (val: T) => void] {
const prefKey = `CODE_MOD_USER_PREF__${key}` const prefKey = `CODE_MOD_USER_PREF__${key}`
const convert = useCallback( const convert = useCallback(
val => { val => {
@ -40,14 +44,16 @@ export function useUserPreference<T = string>(key: UserPreference, defaultValue:
const savePreference = useCallback( const savePreference = useCallback(
(val: T) => { (val: T) => {
try { try {
if (filter(val)) {
localStorage[prefKey] = Array.isArray(val) || typeof val === 'object' ? JSON.stringify(val) : val localStorage[prefKey] = Array.isArray(val) || typeof val === 'object' ? JSON.stringify(val) : val
}
} catch (exception) { } catch (exception) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('useUserPreference: Failed to stringify object', val) console.error('useUserPreference: Failed to stringify object', val)
} }
setPreference(val) setPreference(val)
}, },
[prefKey] [prefKey, filter]
) )
return [preference, savePreference] return [preference, savePreference]

View File

@ -85,16 +85,12 @@ export default function Compare() {
return showToaster(getString('pr.titleIsRequired')) return showToaster(getString('pr.titleIsRequired'))
} }
if (!description) {
return showToaster(getString('pr.descIsRequired'))
}
const pullReqUrl = window.location.href.split('compare')?.[0] const pullReqUrl = window.location.href.split('compare')?.[0]
const payload: OpenapiCreatePullReqRequest = { const payload: OpenapiCreatePullReqRequest = {
target_branch: targetGitRef, target_branch: targetGitRef,
source_branch: sourceGitRef, source_branch: sourceGitRef,
title: title, title: title,
description: description, description: description || '',
is_draft: creationType === PRCreationType.DRAFT is_draft: creationType === PRCreationType.DRAFT
} }
@ -243,7 +239,7 @@ export default function Compare() {
</Container> </Container>
<Container className={css.markdownContainer}> <Container className={css.markdownContainer}>
<Layout.Vertical spacing="small"> <Layout.Vertical spacing="small">
<Text font={{ variation: FontVariation.SMALL_BOLD }}>{getString('description')} *</Text> <Text font={{ variation: FontVariation.SMALL_BOLD }}>{getString('description')}</Text>
<MarkdownEditorWithPreview <MarkdownEditorWithPreview
value={description} value={description}
onChange={setDescription} onChange={setDescription}

View File

@ -27,13 +27,17 @@ import css from './Conversation.module.scss'
export interface ConversationProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> { export interface ConversationProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
onCommentUpdate: () => void onCommentUpdate: () => void
prHasChanged?: boolean prHasChanged?: boolean
showEditDescription?: boolean
onCancelEditDescription: () => void
} }
export const Conversation: React.FC<ConversationProps> = ({ export const Conversation: React.FC<ConversationProps> = ({
repoMetadata, repoMetadata,
pullRequestMetadata, pullRequestMetadata,
onCommentUpdate, onCommentUpdate,
prHasChanged prHasChanged,
showEditDescription,
onCancelEditDescription
}) => { }) => {
const { getString } = useStrings() const { getString } = useStrings()
const { currentUser } = useAppContext() const { currentUser } = useAppContext()
@ -116,6 +120,10 @@ export const Conversation: React.FC<ConversationProps> = ({
onCommentUpdate() onCommentUpdate()
refetchActivities() refetchActivities()
}, [onCommentUpdate, refetchActivities]) }, [onCommentUpdate, refetchActivities])
const hasDescription = useMemo(
() => !!pullRequestMetadata?.description?.length,
[pullRequestMetadata?.description?.length]
)
useEffect(() => { useEffect(() => {
if (prHasChanged) { if (prHasChanged) {
@ -136,12 +144,17 @@ export const Conversation: React.FC<ConversationProps> = ({
<Layout.Horizontal> <Layout.Horizontal>
<Container width={`70%`}> <Container width={`70%`}>
<Layout.Vertical spacing="xlarge"> <Layout.Vertical spacing="xlarge">
{(hasDescription || showEditDescription) && (
<DescriptionBox <DescriptionBox
repoMetadata={repoMetadata} repoMetadata={repoMetadata}
pullRequestMetadata={pullRequestMetadata} pullRequestMetadata={pullRequestMetadata}
onCommentUpdate={onCommentUpdate} onCommentUpdate={onCommentUpdate}
onCancelEditDescription={onCancelEditDescription}
/> />
<Layout.Horizontal className={css.sortContainer} padding={{ top: 'xxlarge', bottom: 'medium' }}> )}
<Layout.Horizontal
className={css.sortContainer}
padding={{ top: hasDescription || showEditDescription ? 'xxlarge' : undefined, bottom: 'medium' }}>
<Container> <Container>
<Select <Select
items={activityFilters} items={activityFilters}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import { Container, useToaster } from '@harness/uicore' import { Container, useToaster } from '@harness/uicore'
import cx from 'classnames' import cx from 'classnames'
import { useMutate } from 'restful-react' import { useMutate } from 'restful-react'
@ -12,10 +12,15 @@ import { getErrorMessage } from 'utils/Utils'
import type { ConversationProps } from './Conversation' import type { ConversationProps } from './Conversation'
import css from './Conversation.module.scss' import css from './Conversation.module.scss'
export const DescriptionBox: React.FC<ConversationProps> = ({ interface DescriptionBoxProps extends Omit<ConversationProps, 'onCancelEditDescription'> {
onCancelEditDescription: () => void
}
export const DescriptionBox: React.FC<DescriptionBoxProps> = ({
repoMetadata, repoMetadata,
pullRequestMetadata, pullRequestMetadata,
onCommentUpdate: refreshPullRequestMetadata onCommentUpdate: refreshPullRequestMetadata,
onCancelEditDescription
}) => { }) => {
const [edit, setEdit] = useState(false) const [edit, setEdit] = useState(false)
const [dirty, setDirty] = useState(false) const [dirty, setDirty] = useState(false)
@ -28,6 +33,10 @@ export const DescriptionBox: React.FC<ConversationProps> = ({
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}` path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}`
}) })
useEffect(() => {
setEdit(!pullRequestMetadata?.description?.length)
}, [pullRequestMetadata?.description?.length])
return ( return (
<Container className={cx({ [css.box]: !edit, [css.desc]: !edit })}> <Container className={cx({ [css.box]: !edit, [css.desc]: !edit })}>
<Container padding={!edit ? { left: 'small', bottom: 'small' } : undefined}> <Container padding={!edit ? { left: 'small', bottom: 'small' } : undefined}>
@ -37,14 +46,13 @@ export const DescriptionBox: React.FC<ConversationProps> = ({
onSave={value => { onSave={value => {
const payload: OpenapiUpdatePullReqRequest = { const payload: OpenapiUpdatePullReqRequest = {
title: pullRequestMetadata.title, title: pullRequestMetadata.title,
description: value description: value || ''
} }
mutate(payload) mutate(payload)
.then(() => { .then(() => {
setContent(value) setContent(value)
setOriginalContent(value) setOriginalContent(value)
setEdit(false) setEdit(false)
// setUpdated(Date.now())
refreshPullRequestMetadata() refreshPullRequestMetadata()
}) })
.catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate'))) .catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate')))
@ -52,6 +60,7 @@ export const DescriptionBox: React.FC<ConversationProps> = ({
onCancel={() => { onCancel={() => {
setContent(originalContent) setContent(originalContent)
setEdit(false) setEdit(false)
onCancelEditDescription()
}} }}
setDirty={setDirty} setDirty={setDirty}
i18n={{ i18n={{
@ -62,6 +71,7 @@ export const DescriptionBox: React.FC<ConversationProps> = ({
cancel: getString('cancel') cancel: getString('cancel')
}} }}
editorHeight="400px" editorHeight="400px"
autoFocusAndPositioning
/> />
)) || ( )) || (
<Container className={css.mdWrapper}> <Container className={css.mdWrapper}>

View File

@ -133,7 +133,8 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
const [mergeOption, setMergeOption] = useUserPreference<PRMergeOption>( const [mergeOption, setMergeOption] = useUserPreference<PRMergeOption>(
UserPreference.PULL_REQUEST_MERGE_STRATEGY, UserPreference.PULL_REQUEST_MERGE_STRATEGY,
mergeOptions[1] mergeOptions[1],
option => option.method !== 'close'
) )
const [draftOption, setDraftOption] = useState<PRDraftOption>(draftOptions[0]) const [draftOption, setDraftOption] = useState<PRDraftOption>(draftOptions[0])
const permPushResult = hooks?.usePermissionTranslate?.( const permPushResult = hooks?.usePermissionTranslate?.(

View File

@ -1,26 +1,14 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { import { Container, PageBody, Tabs } from '@harness/uicore'
Container, import { useGet } from 'restful-react'
PageBody, import { Render } from 'react-jsx-match'
Text,
FontVariation,
Tabs,
Layout,
Button,
ButtonVariation,
ButtonSize,
TextInput,
useToaster
} from '@harness/uicore'
import { useGet, useMutate } from 'restful-react'
import { Render, Match, Truthy, Else } from 'react-jsx-match'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata' 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 } from 'utils/Utils' import { voidFn, getErrorMessage } from 'utils/Utils'
import { CodeIcon, GitInfoProps } from 'utils/GitUtils' import { CodeIcon } from 'utils/GitUtils'
import type { TypesPullReq, TypesPullReqStats, TypesRepository } from 'services/code' import type { TypesPullReq, TypesPullReqStats, TypesRepository } from 'services/code'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { TabTitleWithCount, tabContainerCSS } from 'components/TabTitleWithCount/TabTitleWithCount' import { TabTitleWithCount, tabContainerCSS } from 'components/TabTitleWithCount/TabTitleWithCount'
@ -29,6 +17,7 @@ import { Conversation } from './Conversation/Conversation'
import { Checks } from './Checks/Checks' import { Checks } from './Checks/Checks'
import { Changes } from '../../components/Changes/Changes' import { Changes } from '../../components/Changes/Changes'
import { PullRequestCommits } from './PullRequestCommits/PullRequestCommits' import { PullRequestCommits } from './PullRequestCommits/PullRequestCommits'
import { PullRequestTitle } from './PullRequestTitle'
import css from './PullRequest.module.scss' import css from './PullRequest.module.scss'
export default function PullRequest() { export default function PullRequest() {
@ -61,6 +50,7 @@ export default function PullRequest() {
return loading || (prLoading && !prData) return loading || (prLoading && !prData)
}, [loading, prLoading, prData]) }, [loading, prLoading, prData])
const [stats, setStats] = useState<TypesPullReqStats>() const [stats, setStats] = useState<TypesPullReqStats>()
const [showEditDescription, setShowEditDescription] = useState(false)
const prHasChanged = useMemo(() => { const prHasChanged = useMemo(() => {
if (stats && prData?.stats) { if (stats && prData?.stats) {
if ( if (
@ -74,6 +64,9 @@ export default function PullRequest() {
} }
return false return false
}, [prData?.stats, stats]) }, [prData?.stats, stats])
const onAddDescriptionClick = useCallback(() => {
setShowEditDescription(true)
}, [])
useEffect( useEffect(
function setStatsIfNotSet() { function setStatsIfNotSet() {
@ -120,7 +113,13 @@ export default function PullRequest() {
<Container className={css.main}> <Container className={css.main}>
<RepositoryPageHeader <RepositoryPageHeader
repoMetadata={repoMetadata} repoMetadata={repoMetadata}
title={repoMetadata && prData ? <PullRequestTitle repoMetadata={repoMetadata} {...prData} /> : ''} title={
repoMetadata && prData ? (
<PullRequestTitle repoMetadata={repoMetadata} {...prData} onAddDescriptionClick={onAddDescriptionClick} />
) : (
''
)
}
dataTooltipId="repositoryPullRequests" dataTooltipId="repositoryPullRequests"
extraBreadcrumbLinks={ extraBreadcrumbLinks={
repoMetadata && [ repoMetadata && [
@ -165,8 +164,13 @@ export default function PullRequest() {
<Conversation <Conversation
repoMetadata={repoMetadata as TypesRepository} repoMetadata={repoMetadata as TypesRepository}
pullRequestMetadata={prData as TypesPullReq} pullRequestMetadata={prData as TypesPullReq}
onCommentUpdate={voidFn(refetchPullRequest)} onCommentUpdate={() => {
setShowEditDescription(false)
refetchPullRequest()
}}
prHasChanged={prHasChanged} prHasChanged={prHasChanged}
showEditDescription={showEditDescription}
onCancelEditDescription={() => setShowEditDescription(false)}
/> />
) )
}, },
@ -237,92 +241,6 @@ export default function PullRequest() {
) )
} }
interface PullRequestTitleProps extends TypesPullReq, Pick<GitInfoProps, 'repoMetadata'> {
onSaveDone?: (newTitle: string) => Promise<boolean>
}
const PullRequestTitle: React.FC<PullRequestTitleProps> = ({ repoMetadata, title, number, description }) => {
const [original, setOriginal] = useState(title)
const [val, setVal] = useState(title)
const [edit, setEdit] = useState(false)
const { getString } = useStrings()
const { showError } = useToaster()
const { mutate } = useMutate({
verb: 'PATCH',
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${number}`
})
const submitChange = useCallback(() => {
mutate({
title: val,
description
})
.then(() => {
setEdit(false)
setOriginal(val)
})
.catch(exception => showError(getErrorMessage(exception), 0))
}, [description, val, mutate, showError])
return (
<Layout.Horizontal spacing="xsmall" className={css.prTitle}>
<Match expr={edit}>
<Truthy>
<Container>
<Layout.Horizontal spacing="small">
<TextInput
wrapperClassName={css.input}
value={val}
onFocus={event => event.target.select()}
onInput={event => setVal(event.currentTarget.value)}
autoFocus
onKeyDown={event => {
switch (event.key) {
case 'Enter':
submitChange()
break
case 'Escape': // does not work, maybe TextInput cancels ESC?
setEdit(false)
break
}
}}
/>
<Button
variation={ButtonVariation.PRIMARY}
text={getString('save')}
size={ButtonSize.MEDIUM}
disabled={(val || '').trim().length === 0 || title === val}
onClick={submitChange}
/>
<Button
variation={ButtonVariation.TERTIARY}
text={getString('cancel')}
size={ButtonSize.MEDIUM}
onClick={() => setEdit(false)}
/>
</Layout.Horizontal>
</Container>
</Truthy>
<Else>
<>
<Text tag="h1" font={{ variation: FontVariation.H4 }}>
{original} <span className={css.prNumber}>#{number}</span>
</Text>
<Button
variation={ButtonVariation.ICON}
tooltip={getString('edit')}
tooltipProps={{ isDark: true, position: 'right' }}
size={ButtonSize.SMALL}
icon="code-edit"
className={css.btn}
onClick={() => setEdit(true)}
/>
</>
</Else>
</Match>
</Layout.Horizontal>
)
}
enum PullRequestSection { enum PullRequestSection {
CONVERSATION = 'conversation', CONVERSATION = 'conversation',
COMMITS = 'commits', COMMITS = 'commits',

View File

@ -43,7 +43,7 @@ export const PullRequestCommits: React.FC<CommitProps> = ({
return ( return (
<PullRequestTabContentWrapper loading={loading} error={error} onRetry={voidFn(refetch)}> <PullRequestTabContentWrapper loading={loading} error={error} onRetry={voidFn(refetch)}>
<CommitsView <CommitsView
commits={commits || []} commits={commits}
repoMetadata={repoMetadata} repoMetadata={repoMetadata}
emptyTitle={getString('noCommits')} emptyTitle={getString('noCommits')}
emptyMessage={getString('noCommitsPR')} emptyMessage={getString('noCommitsPR')}

View File

@ -0,0 +1,121 @@
import React, { useCallback, useState } from 'react'
import {
Container,
Text,
FontVariation,
Layout,
Button,
ButtonVariation,
ButtonSize,
TextInput,
useToaster
} from '@harness/uicore'
import { useMutate } from 'restful-react'
import { Match, Truthy, Else } from 'react-jsx-match'
import { useStrings } from 'framework/strings'
import { ButtonRoleProps, getErrorMessage } from 'utils/Utils'
import type { GitInfoProps } from 'utils/GitUtils'
import type { TypesPullReq } from 'services/code'
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
import css from './PullRequest.module.scss'
interface PullRequestTitleProps extends TypesPullReq, Pick<GitInfoProps, 'repoMetadata'> {
onSaveDone?: (newTitle: string) => Promise<boolean>
onAddDescriptionClick: () => void
}
export const PullRequestTitle: React.FC<PullRequestTitleProps> = ({
repoMetadata,
title,
number,
description,
onAddDescriptionClick
}) => {
const [original, setOriginal] = useState(title)
const [val, setVal] = useState(title)
const [edit, setEdit] = useState(false)
const { getString } = useStrings()
const { showError } = useToaster()
const { mutate } = useMutate({
verb: 'PATCH',
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${number}`
})
const submitChange = useCallback(() => {
mutate({
title: val,
description
})
.then(() => {
setEdit(false)
setOriginal(val)
})
.catch(exception => showError(getErrorMessage(exception), 0))
}, [description, val, mutate, showError])
return (
<Layout.Horizontal spacing="small" className={css.prTitle}>
<Match expr={edit}>
<Truthy>
<Container>
<Layout.Horizontal spacing="small">
<TextInput
wrapperClassName={css.input}
value={val}
onFocus={event => event.target.select()}
onInput={event => setVal(event.currentTarget.value)}
autoFocus
onKeyDown={event => {
switch (event.key) {
case 'Enter':
submitChange()
break
case 'Escape': // does not work, maybe TextInput cancels ESC?
setEdit(false)
break
}
}}
/>
<Button
variation={ButtonVariation.PRIMARY}
text={getString('save')}
size={ButtonSize.MEDIUM}
disabled={(val || '').trim().length === 0 || title === val}
onClick={submitChange}
/>
<Button
variation={ButtonVariation.TERTIARY}
text={getString('cancel')}
size={ButtonSize.MEDIUM}
onClick={() => setEdit(false)}
/>
</Layout.Horizontal>
</Container>
</Truthy>
<Else>
<>
<Text tag="h1" font={{ variation: FontVariation.H4 }}>
{original} <span className={css.prNumber}>#{number}</span>
</Text>
<Button
variation={ButtonVariation.ICON}
tooltip={getString('edit')}
tooltipProps={{ isDark: true, position: 'right' }}
size={ButtonSize.SMALL}
icon="code-edit"
className={css.btn}
onClick={() => setEdit(true)}
/>
{!(description || '').trim().length && (
<>
<PipeSeparator height={10} />
<a {...ButtonRoleProps} onClick={onAddDescriptionClick}>
&nbsp;Add Description
</a>
</>
)}
</>
</Else>
</Match>
</Layout.Horizontal>
)
}

View File

@ -11,6 +11,7 @@
.title { .title {
font-weight: 600; font-weight: 600;
display: flex; display: flex;
.convoIcon { .convoIcon {
padding-top: 1px !important; padding-top: 1px !important;
} }
@ -20,7 +21,7 @@
.titleRow { .titleRow {
padding-left: var(--spacing-small); padding-left: var(--spacing-small);
align-items: start; align-items: center;
} }
} }

View File

@ -9,7 +9,8 @@ import {
StringSubstitute, StringSubstitute,
Icon, Icon,
FontVariation, FontVariation,
FlexExpander FlexExpander,
Utils
} from '@harness/uicore' } from '@harness/uicore'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import { useGet } from 'restful-react' import { useGet } from 'restful-react'
@ -30,6 +31,8 @@ import type { TypesPullReq, TypesRepository } from 'services/code'
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination' import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { UserPreference, useUserPreference } from 'hooks/useUserPreference' import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
import { NoResultCard } from 'components/NoResultCard/NoResultCard' import { NoResultCard } from 'components/NoResultCard/NoResultCard'
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
import { GitRefLink } from 'components/GitRefLink/GitRefLink'
import { PullRequestStateLabel } from 'components/PullRequestStateLabel/PullRequestStateLabel' import { PullRequestStateLabel } from 'components/PullRequestStateLabel/PullRequestStateLabel'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { ExecutionStatusLabel } from 'components/ExecutionStatusLabel/ExecutionStatusLabel' import { ExecutionStatusLabel } from 'components/ExecutionStatusLabel/ExecutionStatusLabel'
@ -97,7 +100,7 @@ export default function PullRequests() {
<Layout.Horizontal className={css.titleRow} spacing="medium"> <Layout.Horizontal className={css.titleRow} spacing="medium">
<PullRequestStateLabel iconSize={22} data={row.original} iconOnly /> <PullRequestStateLabel iconSize={22} data={row.original} iconOnly />
<Container padding={{ left: 'small' }}> <Container padding={{ left: 'small' }}>
<Layout.Vertical spacing="small"> <Layout.Vertical spacing="xsmall">
<Text color={Color.GREY_800} className={css.title}> <Text color={Color.GREY_800} className={css.title}>
{row.original.title} {row.original.title}
<Icon <Icon
@ -110,6 +113,8 @@ export default function PullRequests() {
{row.original.stats?.conversations} {row.original.stats?.conversations}
</Text> </Text>
</Text> </Text>
<Container>
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
<Text color={Color.GREY_500} font={{ size: 'small' }}> <Text color={Color.GREY_500} font={{ size: 'small' }}>
<StringSubstitute <StringSubstitute
str={getString('pr.statusLine')} str={getString('pr.statusLine')}
@ -120,7 +125,9 @@ export default function PullRequests() {
<strong> <strong>
<ReactTimeago <ReactTimeago
date={ date={
(row.original.state == 'merged' ? row.original.merged : row.original.created) as number (row.original.state == 'merged'
? row.original.merged
: row.original.created) as number
} }
/> />
</strong> </strong>
@ -129,10 +136,32 @@ export default function PullRequests() {
}} }}
/> />
</Text> </Text>
<PipeSeparator height={10} />
<Container>
<Layout.Horizontal spacing="xsmall" style={{ alignItems: 'center' }} onClick={Utils.stopEvent}>
<GitRefLink
text={row.original.target_branch as string}
url={routes.toCODERepository({
repoPath: repoMetadata?.path as string,
gitRef: row.original.target_branch
})}
/>
<Text color={Color.GREY_500}></Text>
<GitRefLink
text={row.original.source_branch as string}
url={routes.toCODERepository({
repoPath: repoMetadata?.path as string,
gitRef: row.original.source_branch
})}
/>
</Layout.Horizontal>
</Container>
</Layout.Horizontal>
</Container>
</Layout.Vertical> </Layout.Vertical>
</Container> </Container>
<FlexExpander /> <FlexExpander />
{/* fix state when api is fully implemented */} {/* TODO: Pass proper state when check api is fully implemented */}
<ExecutionStatusLabel data={{ state: 'success' }} /> <ExecutionStatusLabel data={{ state: 'success' }} />
</Layout.Horizontal> </Layout.Horizontal>
) )

View File

@ -115,7 +115,7 @@ export function formatNumber(num: number | bigint): string {
* support (hit Enter/Space will trigger click event) * support (hit Enter/Space will trigger click event)
*/ */
export const ButtonRoleProps = { export const ButtonRoleProps = {
onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => { onKeyDown: (e: React.KeyboardEvent<HTMLElement>) => {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar' || e.which === 13 || e.which === 32) { if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar' || e.which === 13 || e.which === 32) {
;(e.target as unknown as { click: () => void })?.click?.() ;(e.target as unknown as { click: () => void })?.click?.()
} }