import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Container, PageBody, Text, FontVariation, Tabs, IconName, HarnessIcons, Layout, Button, ButtonVariation, ButtonSize, TextInput, useToaster, Spacing, PaddingProps } 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 { 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 { CodeIcon, GitInfoProps } from 'utils/GitUtils' import type { TypesPullReq, TypesPullReqStats, TypesRepository } from 'services/code' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { PullRequestMetaLine } from './PullRequestMetaLine' import { Conversation } from './Conversation/Conversation' import { Checks } from './Checks/Checks' import { Changes } from '../../components/Changes/Changes' import { PullRequestCommits } from './PullRequestCommits/PullRequestCommits' import css from './PullRequest.module.scss' export default function PullRequest() { const history = useHistory() const { getString } = useStrings() const { routes } = useAppContext() const { repoMetadata, error, loading, refetch, pullRequestId, pullRequestSection = PullRequestSection.CONVERSATION } = useGetRepositoryMetadata() const path = useMemo( () => `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullRequestId}`, [repoMetadata?.path, pullRequestId] ) const { data: pullRequestData, error: prError, loading: prLoading, refetch: refetchPullRequest } = useGet({ path, lazy: !repoMetadata }) const [prData, setPrData] = useState() const showSpinner = useMemo(() => { return loading || (prLoading && !prData) }, [loading, prLoading, prData]) const [stats, setStats] = useState() const prHasChanged = useMemo(() => { if (stats && prData?.stats) { if ( stats.commits !== prData.stats.commits || stats.conversations !== prData.stats.conversations || stats.files_changed !== prData.stats.files_changed ) { window.setTimeout(() => setStats(prData.stats), 50) return true } } return false }, [prData?.stats, stats]) useEffect( function setStatsIfNotSet() { if (!stats && prData?.stats) { setStats(prData.stats) } }, [prData?.stats, stats] ) // prData holds the latest good PR data to make sure page is not broken // when polling fails useEffect( function setPrDataIfNotSet() { if (pullRequestData) { setPrData(pullRequestData) } }, [pullRequestData] ) useEffect(() => { const fn = () => { if (repoMetadata) { refetchPullRequest().then(() => { interval = window.setTimeout(fn, PR_POLLING_INTERVAL) }) } } let interval = window.setTimeout(fn, PR_POLLING_INTERVAL) return () => window.clearTimeout(interval) }, [repoMetadata, refetchPullRequest, path]) const activeTab = useMemo( () => Object.values(PullRequestSection).find(value => value === pullRequestSection) ? pullRequestSection : PullRequestSection.CONVERSATION, [pullRequestSection] ) return ( : ''} dataTooltipId="repositoryPullRequests" extraBreadcrumbLinks={ repoMetadata && [ { label: getString('pullRequests'), url: routes.toCODEPullRequests({ repoPath: repoMetadata.path as string }) } ] } /> <> { history.replace( routes.toCODEPullRequest({ repoPath: repoMetadata?.path as string, pullRequestId, pullRequestSection: tabId !== PullRequestSection.CONVERSATION ? (tabId as string) : undefined }) ) }} tabList={[ { id: PullRequestSection.CONVERSATION, title: ( ), panel: ( ) }, { id: PullRequestSection.COMMITS, title: ( ), panel: ( ) }, { id: PullRequestSection.FILES_CHANGED, title: ( ), panel: ( ) }, { id: PullRequestSection.CHECKS, disabled: window.location.hostname !== 'localhost', // TODO: Remove when API supports checks title: ( ), panel: } ]} /> ) } interface PullRequestTitleProps extends TypesPullReq, Pick { onSaveDone?: (newTitle: string) => Promise } const PullRequestTitle: React.FC = ({ 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 ( 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 } }} />