mirror of
https://github.com/harness/drone.git
synced 2025-05-10 02:39:18 +08:00
Consolidate pagination for the whole codebase + Use SearchInputWithSpinner component (#222)
* Remove center layout for empty repo info - hard to read * Consolidate pagination for the whole codebase * Use SearchInputWithSpinner component * Use SearchInputWithSpinner component
This commit is contained in:
parent
66cc979334
commit
1aa1123bd2
@ -1,40 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import cx from 'classnames'
|
|
||||||
import { Button, ButtonSize, Container, Layout } from '@harness/uicore'
|
|
||||||
import { useStrings } from 'framework/strings'
|
|
||||||
import css from './PrevNextPagination.module.scss'
|
|
||||||
|
|
||||||
interface PrevNextPaginationProps {
|
|
||||||
onPrev?: false | (() => void)
|
|
||||||
onNext?: false | (() => void)
|
|
||||||
skipLayout?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PrevNextPagination({ onPrev, onNext, skipLayout }: PrevNextPaginationProps) {
|
|
||||||
const { getString } = useStrings()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container className={skipLayout ? undefined : css.main}>
|
|
||||||
<Layout.Horizontal>
|
|
||||||
<Button
|
|
||||||
text={getString('prev')}
|
|
||||||
icon="arrow-left"
|
|
||||||
size={ButtonSize.SMALL}
|
|
||||||
className={cx(css.roundedButton, css.buttonLeft)}
|
|
||||||
iconProps={{ size: 12 }}
|
|
||||||
onClick={onPrev ? onPrev : undefined}
|
|
||||||
disabled={!onPrev}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text={getString('next')}
|
|
||||||
rightIcon="arrow-right"
|
|
||||||
size={ButtonSize.SMALL}
|
|
||||||
className={cx(css.roundedButton, css.buttonRight)}
|
|
||||||
iconProps={{ size: 12 }}
|
|
||||||
onClick={onNext ? onNext : undefined}
|
|
||||||
disabled={!onNext}
|
|
||||||
/>
|
|
||||||
</Layout.Horizontal>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,3 +1,7 @@
|
|||||||
|
.pagination {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
|
readonly pagination: string
|
||||||
readonly main: string
|
readonly main: string
|
||||||
readonly roundedButton: string
|
readonly roundedButton: string
|
||||||
readonly selected: string
|
readonly selected: string
|
@ -0,0 +1,107 @@
|
|||||||
|
import React, { useCallback, useMemo } from 'react'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import { Button, ButtonSize, Container, Layout, Pagination } from '@harness/uicore'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import css from './ResourceListingPagination.module.scss'
|
||||||
|
|
||||||
|
interface ResourceListingPaginationProps {
|
||||||
|
response: Response | null
|
||||||
|
page: number
|
||||||
|
setPage: React.Dispatch<React.SetStateAction<number>>
|
||||||
|
scrollTop?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two type of pagination results returned from Code API.
|
||||||
|
// One returns information that works with UICore Pagination component in which we know total pages, total items, etc... The other
|
||||||
|
// has only information to render Prev, Next.
|
||||||
|
//
|
||||||
|
// This component consolidates both cases to remove same pagination logic in pages and components.
|
||||||
|
export const ResourceListingPagination: React.FC<ResourceListingPaginationProps> = ({
|
||||||
|
response,
|
||||||
|
page,
|
||||||
|
setPage,
|
||||||
|
scrollTop = true
|
||||||
|
}) => {
|
||||||
|
const { X_NEXT_PAGE, X_PREV_PAGE, totalItems, totalPages, pageSize } = useParsePaginationInfo(response)
|
||||||
|
const _setPage = useCallback(
|
||||||
|
(_page: number) => {
|
||||||
|
if (scrollTop) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
setPage(_page)
|
||||||
|
},
|
||||||
|
[setPage, scrollTop]
|
||||||
|
)
|
||||||
|
|
||||||
|
return totalItems ? (
|
||||||
|
page === 1 && totalItems < pageSize ? null : (
|
||||||
|
<Container margin={{ left: 'medium', right: 'medium' }}>
|
||||||
|
<Pagination
|
||||||
|
className={css.pagination}
|
||||||
|
hidePageNumbers
|
||||||
|
gotoPage={index => _setPage(index + 1)}
|
||||||
|
itemCount={totalItems}
|
||||||
|
pageCount={totalPages}
|
||||||
|
pageIndex={page - 1}
|
||||||
|
pageSize={pageSize}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
) : page === 1 && !X_PREV_PAGE && !X_NEXT_PAGE ? null : (
|
||||||
|
<PrevNextPagination
|
||||||
|
onPrev={!!X_PREV_PAGE && (() => _setPage(page - 1))}
|
||||||
|
onNext={!!X_NEXT_PAGE && (() => _setPage(page + 1))}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function useParsePaginationInfo(response: Nullable<Response>) {
|
||||||
|
const totalItems = useMemo(() => parseInt(response?.headers?.get('x-total') || '0'), [response])
|
||||||
|
const totalPages = useMemo(() => parseInt(response?.headers?.get('x-total-pages') || '0'), [response])
|
||||||
|
const pageSize = useMemo(() => parseInt(response?.headers?.get('x-per-page') || '0'), [response])
|
||||||
|
const X_NEXT_PAGE = useMemo(() => parseInt(response?.headers?.get('x-next-page') || '0'), [response])
|
||||||
|
const X_PREV_PAGE = useMemo(() => parseInt(response?.headers?.get('x-prev-page') || '0'), [response])
|
||||||
|
|
||||||
|
return { totalItems, totalPages, pageSize, X_NEXT_PAGE, X_PREV_PAGE }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PrevNextPaginationProps {
|
||||||
|
onPrev?: false | (() => void)
|
||||||
|
onNext?: false | (() => void)
|
||||||
|
skipLayout?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrevNextPagination({ onPrev, onNext, skipLayout }: PrevNextPaginationProps) {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className={skipLayout ? undefined : css.main}>
|
||||||
|
<Layout.Horizontal>
|
||||||
|
<Button
|
||||||
|
text={getString('prev')}
|
||||||
|
icon="arrow-left"
|
||||||
|
size={ButtonSize.SMALL}
|
||||||
|
className={cx(css.roundedButton, css.buttonLeft)}
|
||||||
|
iconProps={{ size: 12 }}
|
||||||
|
onClick={onPrev ? onPrev : undefined}
|
||||||
|
disabled={!onPrev}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
text={getString('next')}
|
||||||
|
rightIcon="arrow-right"
|
||||||
|
size={ButtonSize.SMALL}
|
||||||
|
className={cx(css.roundedButton, css.buttonRight)}
|
||||||
|
iconProps={{ size: 12 }}
|
||||||
|
onClick={onNext ? onNext : undefined}
|
||||||
|
disabled={!onNext}
|
||||||
|
/>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
.main {
|
||||||
|
&,
|
||||||
|
.layout {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
padding: 0 0 0 var(--spacing-small) !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
span[data-icon],
|
||||||
|
span[icon] {
|
||||||
|
margin-top: 10px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts
vendored
Normal file
9
web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// this is an auto-generated file
|
||||||
|
declare const styles: {
|
||||||
|
readonly main: string
|
||||||
|
readonly layout: string
|
||||||
|
readonly wrapper: string
|
||||||
|
readonly input: string
|
||||||
|
}
|
||||||
|
export default styles
|
@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Color, Container, Icon, IconName, Layout, TextInput } from '@harness/uicore'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import css from './SearchInputWithSpinner.module.scss'
|
||||||
|
|
||||||
|
interface SearchInputWithSpinnerProps {
|
||||||
|
query?: string
|
||||||
|
setQuery: (value: string) => void
|
||||||
|
loading?: boolean
|
||||||
|
width?: number
|
||||||
|
placeholder?: string
|
||||||
|
icon?: IconName
|
||||||
|
spinnerIcon?: IconName
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchInputWithSpinner: React.FC<SearchInputWithSpinnerProps> = ({
|
||||||
|
query = '',
|
||||||
|
setQuery,
|
||||||
|
loading = false,
|
||||||
|
width = 250,
|
||||||
|
placeholder,
|
||||||
|
icon = 'search',
|
||||||
|
spinnerIcon = 'spinner'
|
||||||
|
}) => {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
return (
|
||||||
|
<Container className={css.main}>
|
||||||
|
<Layout.Horizontal className={css.layout}>
|
||||||
|
{loading && <Icon name={spinnerIcon as IconName} color={Color.PRIMARY_7} />}
|
||||||
|
<TextInput
|
||||||
|
value={query}
|
||||||
|
wrapperClassName={css.wrapper}
|
||||||
|
className={css.input}
|
||||||
|
placeholder={placeholder || getString('search')}
|
||||||
|
leftIcon={icon as IconName}
|
||||||
|
style={{ width }}
|
||||||
|
autoFocus
|
||||||
|
onFocus={event => event.target.select()}
|
||||||
|
onInput={event => setQuery(event.currentTarget.value || '')}
|
||||||
|
/>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
@ -221,6 +221,7 @@ export interface StringsMap {
|
|||||||
webhookCreated: string
|
webhookCreated: string
|
||||||
webhookDeleted: string
|
webhookDeleted: string
|
||||||
webhookDetails: string
|
webhookDetails: string
|
||||||
|
webhookEmpty: string
|
||||||
webhookEventsLabel: string
|
webhookEventsLabel: string
|
||||||
webhookListingContent: string
|
webhookListingContent: string
|
||||||
webhookSelectAllEvents: string
|
webhookSelectAllEvents: string
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
export function useGetPaginationInfo(response: Nullable<Response>) {
|
|
||||||
const totalItems = useMemo(() => parseInt(response?.headers?.get('x-total') || '0'), [response])
|
|
||||||
const totalPages = useMemo(() => parseInt(response?.headers?.get('x-total-pages') || '0'), [response])
|
|
||||||
const pageSize = useMemo(() => parseInt(response?.headers?.get('x-per-page') || '0'), [response])
|
|
||||||
const X_NEXT_PAGE = useMemo(() => parseInt(response?.headers?.get('x-next-page') || '0'), [response])
|
|
||||||
const X_PREV_PAGE = useMemo(() => parseInt(response?.headers?.get('x-prev-page') || '0'), [response])
|
|
||||||
|
|
||||||
return { totalItems, totalPages, pageSize, X_NEXT_PAGE, X_PREV_PAGE }
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
export function usePageIndex(index = 0) {
|
export function usePageIndex(index = 1) {
|
||||||
return useState(index)
|
return useState(index)
|
||||||
}
|
}
|
||||||
|
14
web/src/hooks/useShowRequestError.ts
Normal file
14
web/src/hooks/useShowRequestError.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useToaster } from '@harness/uicore'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import type { GetDataError } from 'restful-react'
|
||||||
|
import { getErrorMessage } from 'utils/Utils'
|
||||||
|
|
||||||
|
export function useShowRequestError(error: GetDataError<Unknown> | null) {
|
||||||
|
const { showError } = useToaster()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) {
|
||||||
|
showError(getErrorMessage(error))
|
||||||
|
}
|
||||||
|
}, [error, showError])
|
||||||
|
}
|
@ -264,3 +264,4 @@ repoEmptyMarkdown: |
|
|||||||
```
|
```
|
||||||
|
|
||||||
You might need [to create an API token](CREATE_API_TOKEN_URL) in order to pull from or push into this repository.
|
You might need [to create an API token](CREATE_API_TOKEN_URL) in order to pull from or push into this repository.
|
||||||
|
webhookEmpty: Here is no WebHooks. Try to
|
||||||
|
@ -12,8 +12,8 @@ import { makeDiffRefs } from 'utils/GitUtils'
|
|||||||
import { CommitsView } from 'components/CommitsView/CommitsView'
|
import { CommitsView } from 'components/CommitsView/CommitsView'
|
||||||
import { Changes } from 'components/Changes/Changes'
|
import { Changes } from 'components/Changes/Changes'
|
||||||
import type { RepoCommit } from 'services/code'
|
import type { RepoCommit } from 'services/code'
|
||||||
import { PrevNextPagination } from 'components/PrevNextPagination/PrevNextPagination'
|
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import { CompareContentHeader } from './CompareContentHeader/CompareContentHeader'
|
import { CompareContentHeader } from './CompareContentHeader/CompareContentHeader'
|
||||||
import css from './Compare.module.scss'
|
import css from './Compare.module.scss'
|
||||||
|
|
||||||
@ -24,17 +24,18 @@ export default function Compare() {
|
|||||||
const { repoMetadata, error, loading, diffRefs } = useGetRepositoryMetadata()
|
const { repoMetadata, error, loading, diffRefs } = useGetRepositoryMetadata()
|
||||||
const [sourceGitRef, setSourceGitRef] = useState(diffRefs.sourceGitRef)
|
const [sourceGitRef, setSourceGitRef] = useState(diffRefs.sourceGitRef)
|
||||||
const [targetGitRef, setTargetGitRef] = useState(diffRefs.targetGitRef)
|
const [targetGitRef, setTargetGitRef] = useState(diffRefs.targetGitRef)
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [page, setPage] = usePageIndex()
|
||||||
const limit = LIST_FETCHING_LIMIT
|
const limit = LIST_FETCHING_LIMIT
|
||||||
const {
|
const {
|
||||||
data: commits,
|
data: commits,
|
||||||
error: commitsError,
|
error: commitsError,
|
||||||
refetch
|
refetch,
|
||||||
|
response
|
||||||
} = useGet<RepoCommit[]>({
|
} = useGet<RepoCommit[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit,
|
limit,
|
||||||
page: pageIndex + 1,
|
page,
|
||||||
git_ref: sourceGitRef,
|
git_ref: sourceGitRef,
|
||||||
after: targetGitRef
|
after: targetGitRef
|
||||||
},
|
},
|
||||||
@ -87,7 +88,7 @@ export default function Compare() {
|
|||||||
id="branchesTags"
|
id="branchesTags"
|
||||||
defaultSelectedTabId="diff"
|
defaultSelectedTabId="diff"
|
||||||
large={false}
|
large={false}
|
||||||
onChange={() => setPageIndex(0)}
|
onChange={() => setPage(1)}
|
||||||
tabList={[
|
tabList={[
|
||||||
{
|
{
|
||||||
id: 'commits',
|
id: 'commits',
|
||||||
@ -95,10 +96,7 @@ export default function Compare() {
|
|||||||
panel: (
|
panel: (
|
||||||
<Container padding="xlarge">
|
<Container padding="xlarge">
|
||||||
{!!commits?.length && <CommitsView commits={commits} repoMetadata={repoMetadata} />}
|
{!!commits?.length && <CommitsView commits={commits} repoMetadata={repoMetadata} />}
|
||||||
<PrevNextPagination
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
onPrev={pageIndex > 0 && (() => setPageIndex(pageIndex - 1))}
|
|
||||||
onNext={commits?.length === limit && (() => setPageIndex(pageIndex + 1))}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -4,8 +4,8 @@ import type { RepoCommit } from 'services/code'
|
|||||||
import type { GitInfoProps } from 'utils/GitUtils'
|
import type { GitInfoProps } from 'utils/GitUtils'
|
||||||
import { voidFn, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
import { voidFn, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import { CommitsView } from 'components/CommitsView/CommitsView'
|
import { CommitsView } from 'components/CommitsView/CommitsView'
|
||||||
import { PrevNextPagination } from 'components/PrevNextPagination/PrevNextPagination'
|
|
||||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||||
|
|
||||||
export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'>> = ({
|
export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'>> = ({
|
||||||
@ -13,17 +13,18 @@ export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'p
|
|||||||
pullRequestMetadata
|
pullRequestMetadata
|
||||||
}) => {
|
}) => {
|
||||||
const limit = LIST_FETCHING_LIMIT
|
const limit = LIST_FETCHING_LIMIT
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [page, setPage] = usePageIndex()
|
||||||
const {
|
const {
|
||||||
data: commits,
|
data: commits,
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
refetch
|
refetch,
|
||||||
|
response
|
||||||
} = useGet<RepoCommit[]>({
|
} = useGet<RepoCommit[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit,
|
limit,
|
||||||
page: pageIndex + 1,
|
page,
|
||||||
git_ref: pullRequestMetadata.source_branch,
|
git_ref: pullRequestMetadata.source_branch,
|
||||||
after: pullRequestMetadata.target_branch
|
after: pullRequestMetadata.target_branch
|
||||||
},
|
},
|
||||||
@ -34,10 +35,7 @@ export const PullRequestCommits: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'p
|
|||||||
<PullRequestTabContentWrapper loading={loading} error={error} onRetry={voidFn(refetch)}>
|
<PullRequestTabContentWrapper loading={loading} error={error} onRetry={voidFn(refetch)}>
|
||||||
{!!commits?.length && <CommitsView commits={commits} repoMetadata={repoMetadata} />}
|
{!!commits?.length && <CommitsView commits={commits} repoMetadata={repoMetadata} />}
|
||||||
|
|
||||||
<PrevNextPagination
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
onPrev={pageIndex > 0 && (() => setPageIndex(pageIndex - 1))}
|
|
||||||
onNext={commits?.length === limit && (() => setPageIndex(pageIndex + 1))}
|
|
||||||
/>
|
|
||||||
</PullRequestTabContentWrapper>
|
</PullRequestTabContentWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
|||||||
import emptyStateImage from 'images/empty-state.svg'
|
import emptyStateImage from 'images/empty-state.svg'
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
import type { TypesPullReq } from 'services/code'
|
import type { TypesPullReq } from 'services/code'
|
||||||
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import { PullRequestsContentHeader } from './PullRequestsContentHeader/PullRequestsContentHeader'
|
import { PullRequestsContentHeader } from './PullRequestsContentHeader/PullRequestsContentHeader'
|
||||||
import prImgOpen from './pull-request-open.svg'
|
import prImgOpen from './pull-request-open.svg'
|
||||||
import prImgMerged from './pull-request-merged.svg'
|
import prImgMerged from './pull-request-merged.svg'
|
||||||
@ -39,17 +40,18 @@ export default function PullRequests() {
|
|||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [filter, setFilter] = useState<string>(PullRequestFilterOption.OPEN)
|
const [filter, setFilter] = useState<string>(PullRequestFilterOption.OPEN)
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [page, setPage] = usePageIndex()
|
||||||
const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
|
const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
error: prError,
|
error: prError,
|
||||||
loading: prLoading
|
loading: prLoading,
|
||||||
|
response
|
||||||
} = useGet<TypesPullReq[]>({
|
} = useGet<TypesPullReq[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit: String(LIST_FETCHING_LIMIT),
|
limit: String(LIST_FETCHING_LIMIT),
|
||||||
page: String(pageIndex + 1),
|
page,
|
||||||
sort: filter == PullRequestFilterOption.MERGED ? 'merged' : 'number',
|
sort: filter == PullRequestFilterOption.MERGED ? 'merged' : 'number',
|
||||||
order: 'desc',
|
order: 'desc',
|
||||||
query: searchTerm,
|
query: searchTerm,
|
||||||
@ -113,15 +115,16 @@ export default function PullRequests() {
|
|||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
onPullRequestFilterChanged={_filter => {
|
onPullRequestFilterChanged={_filter => {
|
||||||
setFilter(_filter)
|
setFilter(_filter)
|
||||||
setPageIndex(0)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
onSearchTermChanged={value => {
|
onSearchTermChanged={value => {
|
||||||
setSearchTerm(value)
|
setSearchTerm(value)
|
||||||
setPageIndex(0)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Container padding="xlarge">
|
<Container padding="xlarge">
|
||||||
{!!data?.length && (
|
{!!data?.length && (
|
||||||
|
<>
|
||||||
<TableV2<TypesPullReq>
|
<TableV2<TypesPullReq>
|
||||||
className={css.table}
|
className={css.table}
|
||||||
hideHeaders
|
hideHeaders
|
||||||
@ -137,6 +140,8 @@ export default function PullRequests() {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{data?.length === 0 && (
|
{data?.length === 0 && (
|
||||||
<Container className={css.noData}>
|
<Container className={css.noData}>
|
||||||
|
@ -9,15 +9,6 @@
|
|||||||
> div {
|
> div {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
|
|
||||||
span[data-icon],
|
|
||||||
span[icon] {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.branchDropdown {
|
.branchDropdown {
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly main: string
|
readonly main: string
|
||||||
readonly input: string
|
|
||||||
readonly branchDropdown: string
|
readonly branchDropdown: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
import React, { useMemo, useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { Container, Layout, FlexExpander, DropDown, ButtonVariation, TextInput, Button } from '@harness/uicore'
|
import { Container, Layout, FlexExpander, DropDown, ButtonVariation, Button } from '@harness/uicore'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { CodeIcon, GitInfoProps, makeDiffRefs, PullRequestFilterOption } from 'utils/GitUtils'
|
import { CodeIcon, GitInfoProps, makeDiffRefs, PullRequestFilterOption } from 'utils/GitUtils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
|
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
||||||
import css from './PullRequestsContentHeader.module.scss'
|
import css from './PullRequestsContentHeader.module.scss'
|
||||||
|
|
||||||
interface PullRequestsContentHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
interface PullRequestsContentHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||||
@ -36,7 +37,6 @@ export function PullRequestsContentHeader({
|
|||||||
],
|
],
|
||||||
[getString]
|
[getString]
|
||||||
)
|
)
|
||||||
const showSpinner = useMemo(() => loading, [loading])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.main} padding="xlarge">
|
<Container className={css.main} padding="xlarge">
|
||||||
@ -51,18 +51,13 @@ export function PullRequestsContentHeader({
|
|||||||
popoverClassName={css.branchDropdown}
|
popoverClassName={css.branchDropdown}
|
||||||
/>
|
/>
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
<TextInput
|
<SearchInputWithSpinner
|
||||||
className={css.input}
|
loading={loading}
|
||||||
placeholder={getString('search')}
|
query={searchTerm}
|
||||||
autoFocus
|
setQuery={value => {
|
||||||
onFocus={event => event.target.select()}
|
|
||||||
value={searchTerm}
|
|
||||||
onInput={event => {
|
|
||||||
const value = event.currentTarget.value
|
|
||||||
setSearchTerm(value)
|
setSearchTerm(value)
|
||||||
onSearchTermChanged(value)
|
onSearchTermChanged(value)
|
||||||
}}
|
}}
|
||||||
leftIcon={showSpinner ? CodeIcon.InputSpinner : CodeIcon.InputSearch}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variation={ButtonVariation.PRIMARY}
|
variation={ButtonVariation.PRIMARY}
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
span[data-icon],
|
span[data-icon],
|
||||||
span[icon] {
|
span[icon] {
|
||||||
@ -98,7 +102,3 @@
|
|||||||
padding-top: var(--spacing-xsmall) !important;
|
padding-top: var(--spacing-xsmall) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly main: string
|
readonly main: string
|
||||||
|
readonly layout: string
|
||||||
readonly input: string
|
readonly input: string
|
||||||
readonly withError: string
|
readonly withError: string
|
||||||
readonly table: string
|
readonly table: string
|
||||||
@ -13,6 +14,5 @@ declare const styles: {
|
|||||||
readonly repoName: string
|
readonly repoName: string
|
||||||
readonly repoScope: string
|
readonly repoScope: string
|
||||||
readonly desc: string
|
readonly desc: string
|
||||||
readonly pagination: string
|
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -9,9 +9,7 @@ import {
|
|||||||
TableV2 as Table,
|
TableV2 as Table,
|
||||||
Text,
|
Text,
|
||||||
Color,
|
Color,
|
||||||
Pagination,
|
Icon
|
||||||
Icon,
|
|
||||||
TextInput
|
|
||||||
} from '@harness/uicore'
|
} from '@harness/uicore'
|
||||||
import type { CellProps, Column } from 'react-table'
|
import type { CellProps, Column } from 'react-table'
|
||||||
import Keywords from 'react-keywords'
|
import Keywords from 'react-keywords'
|
||||||
@ -23,10 +21,10 @@ import { voidFn, formatDate, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/
|
|||||||
import { NewRepoModalButton } from 'components/NewRepoModalButton/NewRepoModalButton'
|
import { NewRepoModalButton } from 'components/NewRepoModalButton/NewRepoModalButton'
|
||||||
import type { TypesRepository } from 'services/code'
|
import type { TypesRepository } from 'services/code'
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
import { useGetPaginationInfo } from 'hooks/useGetPaginationInfo'
|
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
|
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { CodeIcon } from 'utils/GitUtils'
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import emptyStateImage from './empty-state.svg'
|
import emptyStateImage from './empty-state.svg'
|
||||||
import css from './RepositoriesListing.module.scss'
|
import css from './RepositoriesListing.module.scss'
|
||||||
|
|
||||||
@ -38,7 +36,7 @@ export default function RepositoriesListing() {
|
|||||||
const space = useGetSpaceParam()
|
const space = useGetSpaceParam()
|
||||||
const [searchTerm, setSearchTerm] = useState<string | undefined>()
|
const [searchTerm, setSearchTerm] = useState<string | undefined>()
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [page, setPage] = usePageIndex()
|
||||||
const {
|
const {
|
||||||
data: repositories,
|
data: repositories,
|
||||||
error,
|
error,
|
||||||
@ -47,14 +45,13 @@ export default function RepositoriesListing() {
|
|||||||
response
|
response
|
||||||
} = useGet<TypesRepository[]>({
|
} = useGet<TypesRepository[]>({
|
||||||
path: `/api/v1/spaces/${space}/+/repos`,
|
path: `/api/v1/spaces/${space}/+/repos`,
|
||||||
queryParams: { page: pageIndex + 1, limit: LIST_FETCHING_LIMIT, query: searchTerm }
|
queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm }
|
||||||
})
|
})
|
||||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSearchTerm(undefined)
|
setSearchTerm(undefined)
|
||||||
setPageIndex(0)
|
setPage(1)
|
||||||
}, [space, setPageIndex])
|
}, [space, setPage])
|
||||||
|
|
||||||
const columns: Column<TypesRepository>[] = useMemo(
|
const columns: Column<TypesRepository>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@ -138,20 +135,10 @@ export default function RepositoriesListing() {
|
|||||||
button: NewRepoButton
|
button: NewRepoButton
|
||||||
}}>
|
}}>
|
||||||
<Container padding="xlarge">
|
<Container padding="xlarge">
|
||||||
<Layout.Horizontal spacing="large">
|
<Layout.Horizontal spacing="large" className={css.layout}>
|
||||||
{NewRepoButton}
|
{NewRepoButton}
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
<TextInput
|
<SearchInputWithSpinner loading={loading} query={searchTerm} setQuery={setSearchTerm} />
|
||||||
className={css.input}
|
|
||||||
placeholder={getString('search')}
|
|
||||||
leftIcon={loading && searchTerm !== undefined ? CodeIcon.InputSpinner : CodeIcon.InputSearch}
|
|
||||||
style={{ width: 250 }}
|
|
||||||
autoFocus
|
|
||||||
onInput={event => {
|
|
||||||
setSearchTerm(event.currentTarget.value || '')
|
|
||||||
setPageIndex(0)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
<Container margin={{ top: 'medium' }}>
|
<Container margin={{ top: 'medium' }}>
|
||||||
<Table<TypesRepository>
|
<Table<TypesRepository>
|
||||||
@ -164,19 +151,7 @@ export default function RepositoriesListing() {
|
|||||||
getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)}
|
getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
{!!repositories?.length && (
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
<Container margin={{ left: 'medium', right: 'medium' }}>
|
|
||||||
<Pagination
|
|
||||||
className={css.pagination}
|
|
||||||
hidePageNumbers
|
|
||||||
gotoPage={index => setPageIndex(index)}
|
|
||||||
itemCount={totalItems}
|
|
||||||
pageCount={totalPages}
|
|
||||||
pageIndex={pageIndex}
|
|
||||||
pageSize={pageSize}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
</Container>
|
</Container>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Container, Color, Layout, Button, FlexExpander, ButtonVariation, Heading } from '@harness/uicore'
|
import { Container, Color, Layout, Button, FlexExpander, ButtonVariation, Heading, Icon } 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'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import type { OpenapiContentInfo, OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/code'
|
import type { OpenapiContentInfo, OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/code'
|
||||||
|
import { useShowRequestError } from 'hooks/useShowRequestError'
|
||||||
import { CodeIcon } from 'utils/GitUtils'
|
import { CodeIcon } from 'utils/GitUtils'
|
||||||
import css from './Readme.module.scss'
|
import css from './Readme.module.scss'
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ function ReadmeViewer({ metadata, gitRef, readmeInfo, contentOnly, maxWidth }: F
|
|||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
|
|
||||||
const { data /*error, loading, refetch, response */ } = useGet<OpenapiGetContentOutput>({
|
const { data, error, loading } = useGet<OpenapiGetContentOutput>({
|
||||||
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}`,
|
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
include_commit: false,
|
include_commit: false,
|
||||||
@ -29,6 +30,8 @@ function ReadmeViewer({ metadata, gitRef, readmeInfo, contentOnly, maxWidth }: F
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useShowRequestError(error)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
className={cx(css.readmeContainer, contentOnly ? css.contentOnly : '')}
|
className={cx(css.readmeContainer, contentOnly ? css.contentOnly : '')}
|
||||||
@ -38,6 +41,7 @@ function ReadmeViewer({ metadata, gitRef, readmeInfo, contentOnly, maxWidth }: F
|
|||||||
<Layout.Horizontal padding="small" className={css.heading}>
|
<Layout.Horizontal padding="small" className={css.heading}>
|
||||||
<Heading level={5}>{readmeInfo.name}</Heading>
|
<Heading level={5}>{readmeInfo.name}</Heading>
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
|
{loading && <Icon name="spinner" color={Color.PRIMARY_7} />}
|
||||||
<Button
|
<Button
|
||||||
variation={ButtonVariation.ICON}
|
variation={ButtonVariation.ICON}
|
||||||
icon={CodeIcon.Edit}
|
icon={CodeIcon.Edit}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { Container, Layout, FlexExpander, DropDown, ButtonVariation, TextInput } from '@harness/uicore'
|
import { Container, Layout, FlexExpander, DropDown, ButtonVariation } from '@harness/uicore'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { GitBranchType, CodeIcon, GitInfoProps } from 'utils/GitUtils'
|
import { GitBranchType, CodeIcon, GitInfoProps } from 'utils/GitUtils'
|
||||||
|
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
||||||
import { CreateBranchModalButton } from 'components/CreateBranchModal/CreateBranchModal'
|
import { CreateBranchModalButton } from 'components/CreateBranchModal/CreateBranchModal'
|
||||||
import css from './BranchesContentHeader.module.scss'
|
import css from './BranchesContentHeader.module.scss'
|
||||||
|
|
||||||
interface BranchesContentHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
interface BranchesContentHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||||
|
loading?: boolean
|
||||||
activeBranchType?: GitBranchType
|
activeBranchType?: GitBranchType
|
||||||
onBranchTypeSwitched: (branchType: GitBranchType) => void
|
onBranchTypeSwitched: (branchType: GitBranchType) => void
|
||||||
onSearchTermChanged: (searchTerm: string) => void
|
onSearchTermChanged: (searchTerm: string) => void
|
||||||
@ -17,7 +19,8 @@ export function BranchesContentHeader({
|
|||||||
onSearchTermChanged,
|
onSearchTermChanged,
|
||||||
activeBranchType = GitBranchType.ALL,
|
activeBranchType = GitBranchType.ALL,
|
||||||
repoMetadata,
|
repoMetadata,
|
||||||
onNewBranchCreated
|
onNewBranchCreated,
|
||||||
|
loading
|
||||||
}: BranchesContentHeaderProps) {
|
}: BranchesContentHeaderProps) {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const [branchType, setBranchType] = useState(activeBranchType)
|
const [branchType, setBranchType] = useState(activeBranchType)
|
||||||
@ -45,13 +48,10 @@ export function BranchesContentHeader({
|
|||||||
popoverClassName={css.branchDropdown}
|
popoverClassName={css.branchDropdown}
|
||||||
/>
|
/>
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
<TextInput
|
<SearchInputWithSpinner
|
||||||
placeholder={getString('searchBranches')}
|
loading={loading}
|
||||||
autoFocus
|
query={searchTerm}
|
||||||
onFocus={event => event.target.select()}
|
setQuery={value => {
|
||||||
value={searchTerm}
|
|
||||||
onInput={event => {
|
|
||||||
const value = event.currentTarget.value
|
|
||||||
setSearchTerm(value)
|
setSearchTerm(value)
|
||||||
onSearchTermChanged(value)
|
onSearchTermChanged(value)
|
||||||
}}
|
}}
|
||||||
|
@ -4,11 +4,11 @@ import { useGet } from 'restful-react'
|
|||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
import type { RepoBranch } from 'services/code'
|
import type { RepoBranch } from 'services/code'
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
import { useGetPaginationInfo } from 'hooks/useGetPaginationInfo'
|
|
||||||
import { LIST_FETCHING_LIMIT } from 'utils/Utils'
|
import { LIST_FETCHING_LIMIT } from 'utils/Utils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import type { GitInfoProps } from 'utils/GitUtils'
|
import type { GitInfoProps } from 'utils/GitUtils'
|
||||||
import { PrevNextPagination } from 'components/PrevNextPagination/PrevNextPagination'
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
|
import { useShowRequestError } from 'hooks/useShowRequestError'
|
||||||
import { BranchesContentHeader } from './BranchesContentHeader/BranchesContentHeader'
|
import { BranchesContentHeader } from './BranchesContentHeader/BranchesContentHeader'
|
||||||
import { BranchesContent } from './BranchesContent/BranchesContent'
|
import { BranchesContent } from './BranchesContent/BranchesContent'
|
||||||
import css from './RepositoryBranchesContent.module.scss'
|
import css from './RepositoryBranchesContent.module.scss'
|
||||||
@ -17,30 +17,34 @@ export function RepositoryBranchesContent({ repoMetadata }: Pick<GitInfoProps, '
|
|||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [page, setPage] = usePageIndex()
|
||||||
const {
|
const {
|
||||||
data: branches,
|
data: branches,
|
||||||
response /*error, loading,*/,
|
response,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
refetch
|
refetch
|
||||||
} = useGet<RepoBranch[]>({
|
} = useGet<RepoBranch[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit: LIST_FETCHING_LIMIT,
|
limit: LIST_FETCHING_LIMIT,
|
||||||
page: pageIndex + 1,
|
page,
|
||||||
sort: 'date',
|
sort: 'date',
|
||||||
order: 'desc',
|
order: 'desc',
|
||||||
include_commit: true,
|
include_commit: true,
|
||||||
query: searchTerm
|
query: searchTerm
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { X_NEXT_PAGE, X_PREV_PAGE } = useGetPaginationInfo(response)
|
|
||||||
|
useShowRequestError(error)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container padding="xlarge" className={css.resourceContent}>
|
<Container padding="xlarge" className={css.resourceContent}>
|
||||||
<BranchesContentHeader
|
<BranchesContentHeader
|
||||||
|
loading={loading}
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
onBranchTypeSwitched={gitRef => {
|
onBranchTypeSwitched={gitRef => {
|
||||||
setPageIndex(0)
|
setPage(1)
|
||||||
history.push(
|
history.push(
|
||||||
routes.toCODECommits({
|
routes.toCODECommits({
|
||||||
repoPath: repoMetadata.path as string,
|
repoPath: repoMetadata.path as string,
|
||||||
@ -50,7 +54,7 @@ export function RepositoryBranchesContent({ repoMetadata }: Pick<GitInfoProps, '
|
|||||||
}}
|
}}
|
||||||
onSearchTermChanged={value => {
|
onSearchTermChanged={value => {
|
||||||
setSearchTerm(value)
|
setSearchTerm(value)
|
||||||
setPageIndex(0)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
onNewBranchCreated={refetch}
|
onNewBranchCreated={refetch}
|
||||||
/>
|
/>
|
||||||
@ -64,12 +68,7 @@ export function RepositoryBranchesContent({ repoMetadata }: Pick<GitInfoProps, '
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PrevNextPagination
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
onPrev={!!X_PREV_PAGE && (() => setPageIndex(pageIndex - 1))}
|
|
||||||
onNext={!!X_NEXT_PAGE && (() => setPageIndex(pageIndex + 1))}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle loading and error
|
|
||||||
|
@ -7,10 +7,6 @@
|
|||||||
background-color: var(--primary-bg);
|
background-color: var(--primary-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentHeader {
|
.contentHeader {
|
||||||
> div {
|
> div {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly main: string
|
readonly main: string
|
||||||
readonly resourceContent: string
|
readonly resourceContent: string
|
||||||
readonly pagination: string
|
|
||||||
readonly contentHeader: string
|
readonly contentHeader: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Container, FlexExpander, Layout, PageBody, Pagination } from '@harness/uicore'
|
import { Container, FlexExpander, Layout, PageBody } 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'
|
||||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
@ -7,11 +7,11 @@ import { useAppContext } from 'AppContext'
|
|||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
import type { RepoCommit } from 'services/code'
|
import type { RepoCommit } from 'services/code'
|
||||||
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
|
||||||
import { useGetPaginationInfo } from 'hooks/useGetPaginationInfo'
|
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||||
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import { BranchTagSelect } from 'components/BranchTagSelect/BranchTagSelect'
|
import { BranchTagSelect } from 'components/BranchTagSelect/BranchTagSelect'
|
||||||
import { CommitsView } from '../../components/CommitsView/CommitsView'
|
import { CommitsView } from 'components/CommitsView/CommitsView'
|
||||||
import css from './RepositoryCommits.module.scss'
|
import css from './RepositoryCommits.module.scss'
|
||||||
|
|
||||||
export default function RepositoryCommits() {
|
export default function RepositoryCommits() {
|
||||||
@ -19,7 +19,7 @@ export default function RepositoryCommits() {
|
|||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [page, setPage] = usePageIndex()
|
||||||
const {
|
const {
|
||||||
data: commits,
|
data: commits,
|
||||||
response,
|
response,
|
||||||
@ -29,12 +29,11 @@ export default function RepositoryCommits() {
|
|||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit: LIST_FETCHING_LIMIT,
|
limit: LIST_FETCHING_LIMIT,
|
||||||
page: pageIndex + 1,
|
page,
|
||||||
git_ref: commitRef || repoMetadata?.default_branch
|
git_ref: commitRef || repoMetadata?.default_branch
|
||||||
},
|
},
|
||||||
lazy: !repoMetadata
|
lazy: !repoMetadata
|
||||||
})
|
})
|
||||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.main}>
|
<Container className={css.main}>
|
||||||
@ -58,7 +57,7 @@ export default function RepositoryCommits() {
|
|||||||
disableViewAllBranches
|
disableViewAllBranches
|
||||||
gitRef={commitRef || (repoMetadata.default_branch as string)}
|
gitRef={commitRef || (repoMetadata.default_branch as string)}
|
||||||
onSelect={ref => {
|
onSelect={ref => {
|
||||||
setPageIndex(0)
|
setPage(1)
|
||||||
history.push(
|
history.push(
|
||||||
routes.toCODECommits({
|
routes.toCODECommits({
|
||||||
repoPath: repoMetadata.path as string,
|
repoPath: repoMetadata.path as string,
|
||||||
@ -73,17 +72,7 @@ export default function RepositoryCommits() {
|
|||||||
|
|
||||||
<CommitsView commits={commits} repoMetadata={repoMetadata} />
|
<CommitsView commits={commits} repoMetadata={repoMetadata} />
|
||||||
|
|
||||||
<Container margin={{ left: 'large', right: 'large' }}>
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
<Pagination
|
|
||||||
className={css.pagination}
|
|
||||||
hidePageNumbers
|
|
||||||
gotoPage={index => setPageIndex(index)}
|
|
||||||
itemCount={totalItems}
|
|
||||||
pageCount={totalPages}
|
|
||||||
pageIndex={pageIndex}
|
|
||||||
pageSize={pageSize}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
</Container>
|
</Container>
|
||||||
)) ||
|
)) ||
|
||||||
null}
|
null}
|
||||||
|
@ -13,4 +13,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noData > div {
|
||||||
|
height: calc(100vh - var(--page-header-height, 64px) - 120px) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,6 @@ declare const styles: {
|
|||||||
readonly table: string
|
readonly table: string
|
||||||
readonly row: string
|
readonly row: string
|
||||||
readonly title: string
|
readonly title: string
|
||||||
|
readonly noData: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Container,
|
Container,
|
||||||
@ -11,7 +11,8 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
Utils,
|
Utils,
|
||||||
useToaster,
|
useToaster,
|
||||||
IconName
|
IconName,
|
||||||
|
NoDataCard
|
||||||
} from '@harness/uicore'
|
} from '@harness/uicore'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
import { useGet, useMutate } from 'restful-react'
|
import { useGet, useMutate } from 'restful-react'
|
||||||
@ -26,6 +27,7 @@ import emptyStateImage from 'images/empty-state.svg'
|
|||||||
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import type { OpenapiWebhookType } from 'services/code'
|
import type { OpenapiWebhookType } from 'services/code'
|
||||||
import { WebhooksHeader } from './WebhooksHeader/WebhooksHeader'
|
import { WebhooksHeader } from './WebhooksHeader/WebhooksHeader'
|
||||||
import css from './Webhooks.module.scss'
|
import css from './Webhooks.module.scss'
|
||||||
@ -34,20 +36,23 @@ export default function Webhooks() {
|
|||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [page, setPage] = usePageIndex()
|
||||||
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
|
const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
|
||||||
const {
|
const {
|
||||||
data: webhooks,
|
data: webhooks,
|
||||||
loading: webhooksLoading,
|
loading: webhooksLoading,
|
||||||
error: webhooksError,
|
error: webhooksError,
|
||||||
refetch: refetchWebhooks
|
refetch: refetchWebhooks,
|
||||||
|
response
|
||||||
} = useGet<OpenapiWebhookType[]>({
|
} = useGet<OpenapiWebhookType[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/webhooks`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/webhooks`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit: LIST_FETCHING_LIMIT,
|
limit: LIST_FETCHING_LIMIT,
|
||||||
page: pageIndex + 1,
|
page,
|
||||||
sort: 'date',
|
sort: 'date',
|
||||||
order: 'asc'
|
order: 'desc',
|
||||||
|
query: searchTerm
|
||||||
},
|
},
|
||||||
lazy: !repoMetadata
|
lazy: !repoMetadata
|
||||||
})
|
})
|
||||||
@ -115,7 +120,7 @@ export default function Webhooks() {
|
|||||||
deleteWebhook({})
|
deleteWebhook({})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showSuccess(getString('webhookDeleted'), 5000)
|
showSuccess(getString('webhookDeleted'), 5000)
|
||||||
setPageIndex(0)
|
setPage(1)
|
||||||
refetchWebhooks()
|
refetchWebhooks()
|
||||||
})
|
})
|
||||||
.catch(exception => {
|
.catch(exception => {
|
||||||
@ -132,36 +137,26 @@ export default function Webhooks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[history, getString, refetchWebhooks, repoMetadata?.path, routes, setPageIndex]
|
[history, getString, refetchWebhooks, repoMetadata?.path, routes, setPage]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.main}>
|
<Container className={css.main}>
|
||||||
<RepositoryPageHeader repoMetadata={repoMetadata} title={getString('webhooks')} dataTooltipId="webhooks" />
|
<RepositoryPageHeader repoMetadata={repoMetadata} title={getString('webhooks')} dataTooltipId="webhooks" />
|
||||||
<PageBody
|
<PageBody loading={loading} error={getErrorMessage(error || webhooksError)} retryOnError={voidFn(refetch)}>
|
||||||
loading={loading || webhooksLoading}
|
|
||||||
error={getErrorMessage(error || webhooksError)}
|
|
||||||
retryOnError={voidFn(refetch)}
|
|
||||||
noData={{
|
|
||||||
// TODO: Use NoDataCard, this won't render toolbar
|
|
||||||
// when search returns empty response
|
|
||||||
when: () => webhooks?.length === 0,
|
|
||||||
message: getString('noWebHooks'),
|
|
||||||
image: emptyStateImage,
|
|
||||||
button: (
|
|
||||||
<Button
|
|
||||||
variation={ButtonVariation.PRIMARY}
|
|
||||||
text={getString('createWebhook')}
|
|
||||||
icon={CodeIcon.Add}
|
|
||||||
onClick={() => history.push(routes.toCODEWebhookNew({ repoPath: repoMetadata?.path as string }))}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}}>
|
|
||||||
{repoMetadata && (
|
{repoMetadata && (
|
||||||
<Layout.Vertical>
|
<Layout.Vertical>
|
||||||
<WebhooksHeader repoMetadata={repoMetadata} />
|
<WebhooksHeader
|
||||||
{!!webhooks?.length && (
|
repoMetadata={repoMetadata}
|
||||||
|
loading={webhooksLoading}
|
||||||
|
onSearchTermChanged={value => {
|
||||||
|
setSearchTerm(value)
|
||||||
|
setPage(1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Container padding="xlarge">
|
<Container padding="xlarge">
|
||||||
|
{!!webhooks?.length && (
|
||||||
|
<>
|
||||||
<TableV2<OpenapiWebhookType>
|
<TableV2<OpenapiWebhookType>
|
||||||
className={css.table}
|
className={css.table}
|
||||||
hideHeaders
|
hideHeaders
|
||||||
@ -177,8 +172,33 @@ export default function Webhooks() {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{webhooks?.length === 0 && (
|
||||||
|
<Container className={css.noData}>
|
||||||
|
<NoDataCard
|
||||||
|
image={emptyStateImage}
|
||||||
|
message={getString('webhookEmpty')}
|
||||||
|
button={
|
||||||
|
<Button
|
||||||
|
variation={ButtonVariation.PRIMARY}
|
||||||
|
text={getString('createWebhook')}
|
||||||
|
icon={CodeIcon.Add}
|
||||||
|
onClick={() => {
|
||||||
|
history.push(
|
||||||
|
routes.toCODEWebhookNew({
|
||||||
|
repoPath: repoMetadata?.path as string
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
</Container>
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
)}
|
)}
|
||||||
</PageBody>
|
</PageBody>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
.main {
|
.main {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
|
||||||
div[class*='TextInput'] {
|
div[class*='TextInput'] {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
@ -8,7 +10,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding-bottom: 0 !important;
|
.input {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
|
||||||
|
span[data-icon],
|
||||||
|
span[icon] {
|
||||||
|
margin-top: 10px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.branchDropdown {
|
.branchDropdown {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly main: string
|
readonly main: string
|
||||||
|
readonly input: string
|
||||||
readonly branchDropdown: string
|
readonly branchDropdown: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Container, Layout, FlexExpander, ButtonVariation, Button } from '@harness/uicore'
|
import { Container, Layout, FlexExpander, ButtonVariation, Button } from '@harness/uicore'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { CodeIcon, GitInfoProps } from 'utils/GitUtils'
|
import { CodeIcon, GitInfoProps } from 'utils/GitUtils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
|
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
||||||
import css from './WebhooksHeader.module.scss'
|
import css from './WebhooksHeader.module.scss'
|
||||||
|
|
||||||
export function WebhooksHeader({ repoMetadata }: Pick<GitInfoProps, 'repoMetadata'>) {
|
interface WebhooksHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||||
|
loading?: boolean
|
||||||
|
onSearchTermChanged: (searchTerm: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WebhooksHeader({ repoMetadata, loading, onSearchTermChanged }: WebhooksHeaderProps) {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
|
|
||||||
@ -15,6 +22,14 @@ export function WebhooksHeader({ repoMetadata }: Pick<GitInfoProps, 'repoMetadat
|
|||||||
<Container className={css.main} padding="xlarge">
|
<Container className={css.main} padding="xlarge">
|
||||||
<Layout.Horizontal spacing="medium">
|
<Layout.Horizontal spacing="medium">
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
|
<SearchInputWithSpinner
|
||||||
|
loading={loading}
|
||||||
|
query={searchTerm}
|
||||||
|
setQuery={value => {
|
||||||
|
setSearchTerm(value)
|
||||||
|
onSearchTermChanged(value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variation={ButtonVariation.PRIMARY}
|
variation={ButtonVariation.PRIMARY}
|
||||||
text={getString('createWebhook')}
|
text={getString('createWebhook')}
|
||||||
|
@ -86,7 +86,7 @@ export const CodeIcon = {
|
|||||||
Repo: 'code-repo' as IconName,
|
Repo: 'code-repo' as IconName,
|
||||||
Settings: 'code-settings' as IconName,
|
Settings: 'code-settings' as IconName,
|
||||||
Webhook: 'code-webhook' as IconName,
|
Webhook: 'code-webhook' as IconName,
|
||||||
InputSpinner: 'steps-spinner' as IconName,
|
InputSpinner: 'spinner' as IconName,
|
||||||
InputSearch: 'search' as IconName,
|
InputSearch: 'search' as IconName,
|
||||||
Chat: 'code-chat' as IconName
|
Chat: 'code-chat' as IconName
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user