feat: [code-120]: internal page refresh button (#316)

This commit is contained in:
Calvin Lee 2023-02-09 13:00:44 -07:00 committed by GitHub
parent f2358bd4ec
commit e179696678
15 changed files with 201 additions and 15 deletions

View File

@ -40,3 +40,16 @@
.container {
background-color: var(--primary-bg) !important;
}
.hideBtn {
display: none;
}
.refreshIcon {
padding-left: var(--spacing-xsmall) !important;
padding-right: var(--spacing-xsmall);
}
.repeatBtn {
margin-left: var(--spacing-xsmall) !important;
}

View File

@ -7,5 +7,8 @@ declare const styles: {
readonly main: string
readonly enableDiffLineBreaks: string
readonly container: string
readonly hideBtn: string
readonly refreshIcon: string
readonly repeatBtn: string
}
export default styles

View File

@ -7,7 +7,8 @@ import {
Text,
StringSubstitute,
Button,
PageError
PageError,
ButtonSize
} from '@harness/uicore'
import { Match, Case, Render } from 'react-jsx-match'
import * as Diff2Html from 'diff2html'
@ -50,6 +51,8 @@ interface ChangesProps extends Pick<GitInfoProps, 'repoMetadata'> {
pullRequestMetadata?: TypesPullReq
className?: string
onCommentUpdate: () => void
prHasChanged?: boolean
handleRefresh?: () => void
}
export const Changes: React.FC<ChangesProps> = ({
@ -61,7 +64,9 @@ export const Changes: React.FC<ChangesProps> = ({
emptyMessage,
pullRequestMetadata,
onCommentUpdate,
className
className,
prHasChanged,
handleRefresh
}) => {
const { getString } = useStrings()
const [viewStyle, setViewStyle] = useUserPreference(UserPreference.DIFF_VIEW_STYLE, ViewStyle.SIDE_BY_SIDE)
@ -174,6 +179,18 @@ export const Changes: React.FC<ChangesProps> = ({
}}
/>
</Text>
{!prHasChanged ? null : (
<Button
onClick={handleRefresh}
iconProps={{ className: css.refreshIcon, size: 12 }}
icon="repeat"
text={getString('refresh')}
variation={ButtonVariation.SECONDARY}
size={ButtonSize.SMALL}
padding={{ left: 'small' }}
className={css.repeatBtn}
/>
)}
{/* Show "Scroll to top" button */}
<Render when={isSticky}>

View File

@ -39,3 +39,8 @@
}
}
}
.refreshIcon {
padding-left: var(--spacing-xsmall) !important;
padding-right: var(--spacing-xsmall);
}

View File

@ -6,5 +6,6 @@ declare const styles: {
readonly row: string
readonly rowText: string
readonly label: string
readonly refreshIcon: string
}
export default styles

View File

@ -1,5 +1,16 @@
import React, { useMemo } from 'react'
import { Container, Color, TableV2 as Table, Text, Avatar, Layout } from '@harness/uicore'
import {
Container,
Color,
TableV2 as Table,
Text,
Avatar,
Layout,
ButtonVariation,
ButtonSize,
Button,
FlexExpander
} from '@harness/uicore'
import type { CellProps, Column } from 'react-table'
import { orderBy } from 'lodash-es'
import { useStrings } from 'framework/strings'
@ -16,9 +27,18 @@ interface CommitsViewProps extends Pick<GitInfoProps, 'repoMetadata'> {
commits: TypesCommit[]
emptyTitle: string
emptyMessage: string
prHasChanged: boolean
handleRefresh: () => void
}
export function CommitsView({ repoMetadata, commits, emptyTitle, emptyMessage }: CommitsViewProps) {
export function CommitsView({
repoMetadata,
commits,
emptyTitle,
emptyMessage,
handleRefresh,
prHasChanged
}: CommitsViewProps) {
const { getString } = useStrings()
const { routes } = useAppContext()
const columns: Column<TypesCommit>[] = useMemo(
@ -79,6 +99,20 @@ export function CommitsView({ repoMetadata, commits, emptyTitle, emptyMessage }:
return (
<Container className={css.container}>
<Layout.Horizontal>
<FlexExpander />
{!prHasChanged ? null : (
<Button
onClick={handleRefresh}
iconProps={{ className: css.refreshIcon, size: 12 }}
icon="repeat"
text={getString('refresh')}
variation={ButtonVariation.SECONDARY}
size={ButtonSize.SMALL}
margin={{ bottom: 'small' }}
/>
)}
</Layout.Horizontal>
{!!commits.length &&
Object.entries(commitsGroupedByDate).map(([date, commitsByDate]) => {
return (

View File

@ -220,6 +220,7 @@ export interface StringsMap {
pullRequests: string
quote: string
readMe: string
refresh: string
rejected: string
remove: string
renameFile: string

View File

@ -329,4 +329,5 @@ required: Required
noneYet: None Yet
noOptionalReviewers: No Optional Reviewers
noRequiredReviewers: No Required Reviewers
reviewers: Reviewers
reviewers: Reviewers
refresh: Refresh

View File

@ -82,3 +82,32 @@
box-shadow: none;
}
}
.refreshIcon {
padding-left: var(--spacing-xsmall) !important;
padding-right: var(--spacing-xsmall);
}
.repeatBtn:hover {
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.04), 0px 2px 4px rgba(96, 97, 112, 0.16) !important;
}
.repeatBtn {
margin-left: var(--spacing-xsmall) !important;
padding: 5px 1px !important;
background-color: var(--orange-100) !important;
color: #c05809 !important;
--background-color-hover: var(--grey-50) !important;
--background-color-active: var(--grey-100) !important;
--border: none !important;
--padding: 12px;
--icon-padding: 4px;
--font-size: 13px;
--font-weight: 500;
min-height: 24px !important;
height: 24px !important;
.bp3-button-text {
color: var(--orange-300);
}
}

View File

@ -9,5 +9,8 @@ declare const styles: {
readonly snapshotContent: string
readonly newCommentCreated: string
readonly clear: string
readonly refreshIcon: string
readonly repeatBtn: string
readonly bp3ButtonText: string
}
export default styles

View File

@ -1,6 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
Avatar,
Button,
ButtonSize,
ButtonVariation,
Color,
Container,
FlexExpander,
@ -38,9 +41,17 @@ import css from './Conversation.module.scss'
interface ConversationProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
onCommentUpdate: () => void
prHasChanged?: boolean
handleRefresh?: () => void
}
export const Conversation: React.FC<ConversationProps> = ({ repoMetadata, pullRequestMetadata, onCommentUpdate }) => {
export const Conversation: React.FC<ConversationProps> = ({
repoMetadata,
pullRequestMetadata,
onCommentUpdate,
prHasChanged,
handleRefresh
}) => {
const { getString } = useStrings()
const { currentUser } = useAppContext()
const {
@ -126,6 +137,21 @@ export const Conversation: React.FC<ConversationProps> = ({ repoMetadata, pullRe
onPRStateChanged={refreshPR}
/>
<Container>
<Layout.Horizontal width={`70%`}>
<FlexExpander />
{!prHasChanged ? null : (
<Button
onClick={handleRefresh}
iconProps={{ className: css.refreshIcon, size: 12 }}
icon="repeat"
text={getString('refresh')}
variation={ButtonVariation.SECONDARY}
size={ButtonSize.SMALL}
margin={{ bottom: 'small' }}
/>
)}
</Layout.Horizontal>
<Layout.Horizontal>
<Container width={`70%`}>
<Layout.Vertical spacing="xlarge">

View File

@ -74,9 +74,9 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
{getString('required')}
</Text>
{reviewers && reviewers?.length !== 0 ? (
reviewers.map((reviewer: { reviewer: { display_name: string }; review_decision: string }): any => {
reviewers.map((reviewer: { reviewer: { display_name: string, id:number }; review_decision: string }): any => {
return (
<Layout.Horizontal>
<Layout.Horizontal key={reviewer.reviewer.id}>
<Icon className={css.reviewerPadding} {...generateReviewDecisionIcon(reviewer.review_decision)} />
<Avatar
className={css.reviewerAvatar}
@ -129,9 +129,9 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
{getString('optional')}
</Text>
{reviewers && reviewers?.length !== 0 ? (
reviewers.map((reviewer: { reviewer: { display_name: string }; review_decision: string }) => {
reviewers.map((reviewer: { reviewer: { display_name: string, id: number }; review_decision: string }) => {
return (
<Layout.Horizontal>
<Layout.Horizontal key={reviewer.reviewer.id}>
<Icon className={css.reviewerPadding} name="dot" />
<Avatar
className={css.reviewerAvatar}

View File

@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
Container,
PageBody,
@ -21,7 +21,7 @@ import { useAppContext } from 'AppContext'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
import { useStrings } from 'framework/strings'
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
import { voidFn, getErrorMessage } from 'utils/Utils'
import { voidFn, getErrorMessage, PR_POLLING_LIMIT } from 'utils/Utils'
import { CodeIcon, GitInfoProps } from 'utils/GitUtils'
import type { TypesPullReq, TypesRepository } from 'services/code'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
@ -41,6 +41,7 @@ export default function PullRequest() {
const history = useHistory()
const { getString } = useStrings()
const { routes } = useAppContext()
const [prHasChanged, setPrHasChanged] = useState(false)
const {
repoMetadata,
error,
@ -49,6 +50,7 @@ export default function PullRequest() {
pullRequestId,
pullRequestSection = PullRequestSection.CONVERSATION
} = useGetRepositoryMetadata()
const {
data: prData,
error: prError,
@ -58,6 +60,40 @@ export default function PullRequest() {
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullRequestId}`,
lazy: !repoMetadata
})
const {
data: pollPrData,
error: pollPrError,
refetch: refetchPollPullRequest
} = useGet<TypesPullReq>({
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullRequestId}`,
lazy: !repoMetadata
})
const [newPrData, setNewPrData] = useState<TypesPullReq>()
const handleRefresh = () => {
refetchPullRequest()
setNewPrData(prData as TypesPullReq)
setPrHasChanged(false)
}
useEffect(() => {
const interval = window.setTimeout(() => {
refetchPollPullRequest()
setNewPrData(pollPrData as TypesPullReq)
}, PR_POLLING_LIMIT)
return () => window.clearTimeout(interval)
}, [pollPrData])
useEffect(() => {
if (prData?.stats && newPrData?.stats) {
const prStatsChanged =
prData.stats.commits !== newPrData.stats.commits || prData.stats.files_changed !== newPrData.stats.files_changed
if (prStatsChanged) {
setPrHasChanged(prStatsChanged)
}
}
}, [newPrData])
const activeTab = useMemo(
() =>
Object.values(PullRequestSection).find(value => value === pullRequestSection)
@ -65,6 +101,7 @@ export default function PullRequest() {
: PullRequestSection.CONVERSATION,
[pullRequestSection]
)
// /repos/${paramsInPath.repo_ref}/pullreq/${paramsInPath.pullreq_number}/metadata
return (
<Container className={css.main}>
@ -81,7 +118,7 @@ export default function PullRequest() {
]
}
/>
<PageBody error={getErrorMessage(error || prError)} retryOnError={voidFn(refetch)}>
<PageBody error={getErrorMessage(error || prError || pollPrError)} retryOnError={voidFn(refetch)}>
<LoadingSpinner visible={loading || prLoading} withBorder={!!prData && prLoading} />
<Render when={repoMetadata && prData}>
@ -116,6 +153,8 @@ export default function PullRequest() {
repoMetadata={repoMetadata as TypesRepository}
pullRequestMetadata={prData as TypesPullReq}
onCommentUpdate={voidFn(refetchPullRequest)}
prHasChanged={prHasChanged}
handleRefresh={voidFn(handleRefresh)}
/>
)
},
@ -132,6 +171,8 @@ export default function PullRequest() {
<PullRequestCommits
repoMetadata={repoMetadata as TypesRepository}
pullRequestMetadata={prData as TypesPullReq}
prHasChanged={prHasChanged}
handleRefresh={voidFn(handleRefresh)}
/>
)
},
@ -154,6 +195,8 @@ export default function PullRequest() {
emptyTitle={getString('noChanges')}
emptyMessage={getString('noChangesPR')}
onCommentUpdate={voidFn(refetchPullRequest)}
prHasChanged={prHasChanged}
handleRefresh={voidFn(handleRefresh)}
/>
</Container>
)

View File

@ -9,9 +9,16 @@ import { ResourceListingPagination } from 'components/ResourceListingPagination/
import { CommitsView } from 'components/CommitsView/CommitsView'
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'>> = ({
interface CommitProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
prHasChanged: boolean
handleRefresh: () => void
}
export const PullRequestCommits: React.FC<CommitProps> = ({
repoMetadata,
pullRequestMetadata
pullRequestMetadata,
prHasChanged,
handleRefresh
}) => {
const limit = LIST_FETCHING_LIMIT
const [page, setPage] = usePageIndex()
@ -40,6 +47,8 @@ export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'p
repoMetadata={repoMetadata}
emptyTitle={getString('noCommits')}
emptyMessage={getString('noCommitsPR')}
prHasChanged={prHasChanged}
handleRefresh={voidFn(handleRefresh)}
/>
<ResourceListingPagination response={response} page={page} setPage={setPage} />

View File

@ -10,6 +10,7 @@ export const DEFAULT_BRANCH_NAME = 'main'
export const REGEX_VALID_REPO_NAME = /^[a-zA-Z_][0-9a-zA-Z-_.$]*$/
export const SUGGESTED_BRANCH_NAMES = [DEFAULT_BRANCH_NAME, 'master']
export const FILE_SEPERATOR = '/'
export const PR_POLLING_LIMIT = 15000
/** This utility shows a toaster without being bound to any component.
* It's useful to show cross-page/component messages */