mirror of
https://github.com/harness/drone.git
synced 2025-05-04 10:41:37 +08:00
573 lines
23 KiB
TypeScript
573 lines
23 KiB
TypeScript
/*
|
|
* Copyright 2023 Harness, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import React from 'react'
|
|
import { Avatar, Container, Layout, StringSubstitute, Text } from '@harnessio/uicore'
|
|
import { Icon, IconName } from '@harnessio/icons'
|
|
import { Color, FontVariation } from '@harnessio/design-system'
|
|
import { defaultTo } from 'lodash-es'
|
|
import { Case, Falsy, Match, Truthy } from 'react-jsx-match'
|
|
import { CodeIcon, GitInfoProps, MergeStrategy } from 'utils/GitUtils'
|
|
import { useStrings } from 'framework/strings'
|
|
import type { TypesPullReqActivity } from 'services/code'
|
|
import type { CommentItem } from 'components/CommentBox/CommentBox'
|
|
import { PullRequestSection } from 'utils/Utils'
|
|
import { CommentType, LabelActivity, ReviewerAddActivity } from 'components/DiffViewer/DiffViewerUtils'
|
|
import { useAppContext } from 'AppContext'
|
|
import { CommitActions } from 'components/CommitActions/CommitActions'
|
|
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
|
|
import { TimePopoverWithLocal } from 'utils/timePopoverLocal/TimePopoverWithLocal'
|
|
import { Label } from 'components/Label/Label'
|
|
import { GitRefLink } from 'components/GitRefLink/GitRefLink'
|
|
import css from './Conversation.module.scss'
|
|
|
|
interface SystemCommentProps extends Pick<GitInfoProps, 'pullReqMetadata'> {
|
|
commentItems: CommentItem<TypesPullReqActivity>[]
|
|
repoMetadataPath?: string
|
|
}
|
|
|
|
interface MergePayload {
|
|
merge_sha: string
|
|
merge_method: string
|
|
rules_bypassed: boolean
|
|
}
|
|
|
|
interface ReviewerAddActivityPayload {
|
|
reviewer_type: ReviewerAddActivity
|
|
}
|
|
//ToDo : update all comment options with the correct payload type and remove Unknown
|
|
export const SystemComment: React.FC<SystemCommentProps> = ({ pullReqMetadata, commentItems, repoMetadataPath }) => {
|
|
const { getString } = useStrings()
|
|
const payload = commentItems[0].payload
|
|
const type = payload?.type
|
|
const { routes } = useAppContext()
|
|
|
|
switch (type) {
|
|
case CommentType.MERGE: {
|
|
return (
|
|
<Container>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.mergedBox}>
|
|
<Container margin={{ left: 'xsmall' }} width={24} height={24} className={css.mergeContainer}>
|
|
<Icon name={CodeIcon.Merged} size={16} color={Color.PURPLE_700} />
|
|
</Container>
|
|
|
|
<Avatar name={pullReqMetadata.merger?.display_name} size="small" hoverCard={false} />
|
|
<Text flex tag="div" style={{ whiteSpace: 'nowrap' }}>
|
|
<StringSubstitute
|
|
str={
|
|
(payload?.payload as MergePayload)?.merge_method === MergeStrategy.REBASE
|
|
? getString('pr.prRebasedInfo')
|
|
: getString('pr.prMergedInfo')
|
|
}
|
|
vars={{
|
|
user: <strong className={css.rightTextPadding}>{pullReqMetadata.merger?.display_name}</strong>,
|
|
source: (
|
|
<Text lineClamp={1}>
|
|
<strong className={css.textPadding}>{pullReqMetadata.source_branch}</strong>
|
|
</Text>
|
|
),
|
|
target: (
|
|
<Text lineClamp={1}>
|
|
<strong className={css.textPadding}>{pullReqMetadata.target_branch}</strong>
|
|
</Text>
|
|
),
|
|
bypassed: (payload?.payload as MergePayload)?.rules_bypassed,
|
|
mergeSha: (
|
|
<Container className={css.commitContainer} padding={{ left: 'small', right: 'xsmall' }}>
|
|
<CommitActions
|
|
enableCopy
|
|
sha={(payload?.payload as MergePayload)?.merge_sha}
|
|
href={routes.toCODEPullRequest({
|
|
repoPath: repoMetadataPath as string,
|
|
pullRequestSection: PullRequestSection.FILES_CHANGED,
|
|
pullRequestId: String(pullReqMetadata.number),
|
|
commitSHA: (payload?.payload as MergePayload)?.merge_sha as string
|
|
})}
|
|
/>
|
|
</Container>
|
|
),
|
|
time: (
|
|
<Text inline margin={{ left: 'xsmall' }}>
|
|
<PipeSeparator height={9} />
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(pullReqMetadata.merged as number, 0)}
|
|
inline={false}
|
|
className={css.timeText}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Text>
|
|
)
|
|
}}
|
|
/>
|
|
</Text>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.REVIEW_SUBMIT: {
|
|
return (
|
|
<Container>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.mergedBox}>
|
|
<Icon
|
|
margin={{ left: 'small' }}
|
|
padding={{ right: 'small' }}
|
|
{...generateReviewDecisionIcon((payload?.payload as Unknown)?.decision)}
|
|
/>
|
|
|
|
<Avatar name={payload?.author?.display_name as string} size="small" hoverCard={false} />
|
|
<Text color={Color.GREY_500}>
|
|
<StringSubstitute
|
|
str={getString('pr.prReviewSubmit')}
|
|
vars={{
|
|
user: <strong>{payload?.author?.display_name}</strong>,
|
|
state: (payload?.payload as Unknown)?.decision,
|
|
time: (
|
|
<Text inline margin={{ left: 'xsmall' }}>
|
|
<PipeSeparator height={9} />
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
inline={false}
|
|
className={css.timeText}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Text>
|
|
)
|
|
}}
|
|
/>
|
|
</Text>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.BRANCH_UPDATE: {
|
|
return (
|
|
<Container>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.mergedBox}>
|
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
|
<Text flex tag="div">
|
|
{(payload?.payload as Unknown)?.forced ? (
|
|
<StringSubstitute
|
|
str={getString('pr.prBranchForcePushInfo')}
|
|
vars={{
|
|
user: (
|
|
<Text padding={{ right: 'small' }} inline>
|
|
<strong>{payload?.author?.display_name}</strong>
|
|
</Text>
|
|
),
|
|
gitRef: (
|
|
<Container padding={{ left: 'small', right: 'small' }}>
|
|
<GitRefLink
|
|
text={pullReqMetadata.source_branch as string}
|
|
url={routes.toCODERepository({
|
|
repoPath: repoMetadataPath as string,
|
|
gitRef: pullReqMetadata.source_branch
|
|
})}
|
|
showCopy
|
|
/>
|
|
</Container>
|
|
),
|
|
oldCommit: (
|
|
<Container className={css.commitContainer} padding={{ left: 'small', right: 'small' }}>
|
|
<CommitActions
|
|
enableCopy
|
|
sha={(payload?.payload as Unknown)?.old}
|
|
href={routes.toCODEPullRequest({
|
|
repoPath: repoMetadataPath as string,
|
|
pullRequestSection: PullRequestSection.FILES_CHANGED,
|
|
pullRequestId: String(pullReqMetadata.number),
|
|
commitSHA: (payload?.payload as Unknown)?.old as string
|
|
})}
|
|
/>
|
|
</Container>
|
|
),
|
|
newCommit: (
|
|
<Container className={css.commitContainer} padding={{ left: 'small' }}>
|
|
<CommitActions
|
|
enableCopy
|
|
sha={(payload?.payload as Unknown)?.new}
|
|
href={routes.toCODEPullRequest({
|
|
repoPath: repoMetadataPath as string,
|
|
pullRequestSection: PullRequestSection.FILES_CHANGED,
|
|
pullRequestId: String(pullReqMetadata.number),
|
|
commitSHA: (payload?.payload as Unknown)?.new as string
|
|
})}
|
|
/>
|
|
</Container>
|
|
)
|
|
}}
|
|
/>
|
|
) : (
|
|
<StringSubstitute
|
|
str={getString('pr.prBranchPushInfo')}
|
|
vars={{
|
|
user: (
|
|
<Text padding={{ right: 'small' }} inline>
|
|
<strong>{payload?.author?.display_name}</strong>
|
|
</Text>
|
|
),
|
|
commit: (
|
|
<Container className={css.commitContainer} padding={{ left: 'small' }}>
|
|
<CommitActions
|
|
enableCopy
|
|
sha={(payload?.payload as Unknown)?.new}
|
|
href={routes.toCODEPullRequest({
|
|
repoPath: repoMetadataPath as string,
|
|
pullRequestSection: PullRequestSection.FILES_CHANGED,
|
|
pullRequestId: String(pullReqMetadata.number),
|
|
commitSHA: (payload?.payload as Unknown)?.new as string
|
|
})}
|
|
/>
|
|
</Container>
|
|
)
|
|
}}
|
|
/>
|
|
)}
|
|
</Text>
|
|
<PipeSeparator height={9} />
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
inline={true}
|
|
width={100}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.BRANCH_DELETE:
|
|
case CommentType.BRANCH_RESTORE: {
|
|
const isSourceBranchDeleted = type === CommentType.BRANCH_DELETE
|
|
return (
|
|
<Container>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.mergedBox}>
|
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
|
<Text flex tag="div">
|
|
<StringSubstitute
|
|
str={isSourceBranchDeleted ? getString('pr.prBranchDeleteInfo') : getString('pr.prBranchRestoreInfo')}
|
|
vars={{
|
|
user: (
|
|
<Text padding={{ right: 'small' }} inline>
|
|
<strong>{payload?.author?.display_name}</strong>
|
|
</Text>
|
|
),
|
|
commit: (
|
|
<Container className={css.commitContainer} padding={{ left: 'small' }}>
|
|
<CommitActions
|
|
enableCopy
|
|
sha={(payload?.payload as Unknown)?.sha}
|
|
href={routes.toCODEPullRequest({
|
|
repoPath: repoMetadataPath as string,
|
|
pullRequestSection: PullRequestSection.FILES_CHANGED,
|
|
pullRequestId: String(pullReqMetadata.number),
|
|
commitSHA: (payload?.payload as Unknown)?.sha as string
|
|
})}
|
|
/>
|
|
</Container>
|
|
)
|
|
}}
|
|
/>
|
|
</Text>
|
|
<PipeSeparator height={9} />
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
width={100}
|
|
inline={true}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.STATE_CHANGE: {
|
|
const openFromDraft =
|
|
(payload?.payload as Unknown)?.old_draft === true && (payload?.payload as Unknown)?.new_draft === false
|
|
const changedToDraft =
|
|
(payload?.payload as Unknown)?.old_draft === false && (payload?.payload as Unknown)?.new_draft === true
|
|
return (
|
|
<Container>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }} className={css.mergedBox}>
|
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
|
<Text>
|
|
<StringSubstitute
|
|
str={getString(openFromDraft || changedToDraft ? 'pr.prStateChangedDraft' : 'pr.prStateChanged')}
|
|
vars={{
|
|
changedToDraft,
|
|
user: <strong>{payload?.author?.display_name}</strong>,
|
|
old: <strong>{(payload?.payload as Unknown)?.old}</strong>,
|
|
new: <strong>{(payload?.payload as Unknown)?.new}</strong>
|
|
}}
|
|
/>
|
|
</Text>
|
|
<PipeSeparator height={9} />
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
inline={true}
|
|
width={100}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.TITLE_CHANGE: {
|
|
return (
|
|
<Container className={css.mergedBox}>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
|
<Text tag="div">
|
|
<StringSubstitute
|
|
str={getString('pr.titleChanged')}
|
|
vars={{
|
|
user: <strong>{payload?.author?.display_name}</strong>,
|
|
old: (
|
|
<strong>
|
|
<s>{(payload?.payload as Unknown)?.old}</s>
|
|
</strong>
|
|
),
|
|
new: <strong>{(payload?.payload as Unknown)?.new}</strong>
|
|
}}
|
|
/>
|
|
</Text>
|
|
<PipeSeparator height={9} />
|
|
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
inline={true}
|
|
width={100}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.LABEL_MODIFY: {
|
|
return (
|
|
<Container className={css.mergedBox}>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
|
<Text tag="div">
|
|
<Match expr={(payload?.payload as Unknown).type}>
|
|
<Case val={LabelActivity.ASSIGN}>
|
|
<strong>{payload?.author?.display_name}</strong> {getString('labels.applied')}
|
|
<Label
|
|
name={(payload?.payload as Unknown).label}
|
|
label_color={(payload?.payload as Unknown).label_color}
|
|
label_value={{
|
|
name: (payload?.payload as Unknown).value,
|
|
color: (payload?.payload as Unknown).value_color
|
|
}}
|
|
scope={(payload?.payload as Unknown).label_scope}
|
|
/>
|
|
<span>{getString('labels.label')}</span>
|
|
</Case>
|
|
<Case val={LabelActivity.RE_ASSIGN}>
|
|
<strong>{payload?.author?.display_name}</strong> <span>{getString('labels.updated')}</span>
|
|
<Label
|
|
name={(payload?.payload as Unknown).label}
|
|
label_color={(payload?.payload as Unknown).label_color}
|
|
label_value={{
|
|
name: (payload?.payload as Unknown).old_value,
|
|
color: (payload?.payload as Unknown).old_value_color
|
|
}}
|
|
scope={(payload?.payload as Unknown).label_scope}
|
|
/>
|
|
<span>{getString('labels.labelTo')}</span>
|
|
<Label
|
|
name={(payload?.payload as Unknown).label}
|
|
label_color={(payload?.payload as Unknown).label_color}
|
|
label_value={{
|
|
name: (payload?.payload as Unknown).value,
|
|
color: (payload?.payload as Unknown).value_color
|
|
}}
|
|
scope={(payload?.payload as Unknown).label_scope}
|
|
/>
|
|
</Case>
|
|
<Case val={LabelActivity.UN_ASSIGN}>
|
|
<strong>{payload?.author?.display_name}</strong> <span>{getString('labels.removed')}</span>
|
|
<Label
|
|
name={(payload?.payload as Unknown).label}
|
|
label_color={(payload?.payload as Unknown).label_color}
|
|
label_value={{
|
|
name: (payload?.payload as Unknown).value,
|
|
color: (payload?.payload as Unknown).value_color
|
|
}}
|
|
scope={(payload?.payload as Unknown).label_scope}
|
|
/>
|
|
<span>{getString('labels.label')}</span>
|
|
</Case>
|
|
</Match>
|
|
</Text>
|
|
<PipeSeparator height={9} />
|
|
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
inline={true}
|
|
width={100}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.REVIEWER_ADD: {
|
|
const mentionId = payload?.metadata?.mentions?.ids?.[0] ?? 0
|
|
const mentionDisplayName = payload?.mentions?.[mentionId]?.display_name ?? ''
|
|
return (
|
|
<Container className={css.mergedBox}>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
|
<Text tag="div">
|
|
<Match expr={(payload?.payload as ReviewerAddActivityPayload).reviewer_type}>
|
|
<Case val={ReviewerAddActivity.ASSIGNED}>
|
|
<StringSubstitute
|
|
str={getString('prReview.assigned')}
|
|
vars={{
|
|
author: <strong>{payload?.author?.display_name}</strong>,
|
|
reviewer: <strong>{mentionDisplayName}</strong>
|
|
}}
|
|
/>
|
|
</Case>
|
|
<Case val={ReviewerAddActivity.REQUESTED}>
|
|
<StringSubstitute
|
|
str={getString('prReview.requested')}
|
|
vars={{
|
|
author: <strong>{payload?.author?.display_name}</strong>,
|
|
reviewer: <strong>{mentionDisplayName}</strong>
|
|
}}
|
|
/>
|
|
</Case>
|
|
<Case val={ReviewerAddActivity.SELF_ASSIGNED}>
|
|
<StringSubstitute
|
|
str={getString('prReview.selfAssigned')}
|
|
vars={{
|
|
reviewer: <strong>{mentionDisplayName}</strong>
|
|
}}
|
|
/>
|
|
</Case>
|
|
</Match>
|
|
</Text>
|
|
<PipeSeparator height={9} />
|
|
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
inline={true}
|
|
width={100}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
case CommentType.REVIEWER_DELETE: {
|
|
const mentionId = payload?.metadata?.mentions?.ids?.[0] ?? 0
|
|
const mentionDisplayName = payload?.mentions?.[mentionId]?.display_name ?? ''
|
|
return (
|
|
<Container className={css.mergedBox}>
|
|
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
|
<Avatar name={payload?.author?.display_name} size="small" hoverCard={false} />
|
|
<Text tag="div">
|
|
<Match expr={payload?.author?.id === mentionId}>
|
|
<Truthy>
|
|
<StringSubstitute
|
|
str={getString('prReview.selfRemoved')}
|
|
vars={{
|
|
author: <strong>{payload?.author?.display_name}</strong>
|
|
}}
|
|
/>
|
|
</Truthy>
|
|
<Falsy>
|
|
<StringSubstitute
|
|
str={getString('prReview.removed')}
|
|
vars={{
|
|
author: <strong>{payload?.author?.display_name}</strong>,
|
|
reviewer: <strong>{mentionDisplayName}</strong>
|
|
}}
|
|
/>
|
|
</Falsy>
|
|
</Match>
|
|
</Text>
|
|
<PipeSeparator height={9} />
|
|
|
|
<TimePopoverWithLocal
|
|
time={defaultTo(payload?.created as number, 0)}
|
|
inline={true}
|
|
width={100}
|
|
font={{ variation: FontVariation.SMALL }}
|
|
color={Color.GREY_400}
|
|
/>
|
|
</Layout.Horizontal>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
default: {
|
|
// eslint-disable-next-line no-console
|
|
console.warn('Unable to render system type activity', commentItems)
|
|
return (
|
|
<Text className={css.mergedBox}>
|
|
<Icon name={CodeIcon.Commit} padding={{ right: 'small' }} />
|
|
{type}
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
const generateReviewDecisionIcon = (
|
|
reviewDecision: string
|
|
): {
|
|
name: IconName
|
|
color: string | undefined
|
|
size: number | undefined
|
|
icon: IconName
|
|
iconProps?: { color?: Color }
|
|
} => {
|
|
let icon: IconName = 'dot'
|
|
let color: Color | undefined = undefined
|
|
let size: number | undefined = undefined
|
|
|
|
switch (reviewDecision) {
|
|
case 'changereq':
|
|
icon = 'main-issue-filled'
|
|
color = Color.ORANGE_700
|
|
size = 18
|
|
break
|
|
case 'approved':
|
|
icon = 'execution-success'
|
|
size = 18
|
|
color = Color.GREEN_700
|
|
break
|
|
}
|
|
const name = icon
|
|
return { name, color, size, icon, ...(color ? { iconProps: { color } } : undefined) }
|
|
}
|