diff --git a/web/src/components/PrevNextPagination/PrevNextPagination.tsx b/web/src/components/PrevNextPagination/PrevNextPagination.tsx
deleted file mode 100644
index af46f140f..000000000
--- a/web/src/components/PrevNextPagination/PrevNextPagination.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
-
- )
-}
diff --git a/web/src/components/PrevNextPagination/PrevNextPagination.module.scss b/web/src/components/ResourceListingPagination/ResourceListingPagination.module.scss
similarity index 95%
rename from web/src/components/PrevNextPagination/PrevNextPagination.module.scss
rename to web/src/components/ResourceListingPagination/ResourceListingPagination.module.scss
index d23200992..40ae35b3e 100644
--- a/web/src/components/PrevNextPagination/PrevNextPagination.module.scss
+++ b/web/src/components/ResourceListingPagination/ResourceListingPagination.module.scss
@@ -1,3 +1,7 @@
+.pagination {
+ padding-top: 0;
+}
+
.main {
display: flex;
align-items: center;
diff --git a/web/src/components/PrevNextPagination/PrevNextPagination.module.scss.d.ts b/web/src/components/ResourceListingPagination/ResourceListingPagination.module.scss.d.ts
similarity index 89%
rename from web/src/components/PrevNextPagination/PrevNextPagination.module.scss.d.ts
rename to web/src/components/ResourceListingPagination/ResourceListingPagination.module.scss.d.ts
index c35601fdc..507297189 100644
--- a/web/src/components/PrevNextPagination/PrevNextPagination.module.scss.d.ts
+++ b/web/src/components/ResourceListingPagination/ResourceListingPagination.module.scss.d.ts
@@ -1,6 +1,7 @@
/* eslint-disable */
// this is an auto-generated file
declare const styles: {
+ readonly pagination: string
readonly main: string
readonly roundedButton: string
readonly selected: string
diff --git a/web/src/components/ResourceListingPagination/ResourceListingPagination.tsx b/web/src/components/ResourceListingPagination/ResourceListingPagination.tsx
new file mode 100644
index 000000000..5c7c0d681
--- /dev/null
+++ b/web/src/components/ResourceListingPagination/ResourceListingPagination.tsx
@@ -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>
+ 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 = ({
+ 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 : (
+
+ _setPage(index + 1)}
+ itemCount={totalItems}
+ pageCount={totalPages}
+ pageIndex={page - 1}
+ pageSize={pageSize}
+ />
+
+ )
+ ) : page === 1 && !X_PREV_PAGE && !X_NEXT_PAGE ? null : (
+ _setPage(page - 1))}
+ onNext={!!X_NEXT_PAGE && (() => _setPage(page + 1))}
+ />
+ )
+}
+
+function useParsePaginationInfo(response: Nullable) {
+ 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 (
+
+
+
+
+
+
+ )
+}
diff --git a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss
new file mode 100644
index 000000000..cd31d6b43
--- /dev/null
+++ b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss
@@ -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;
+ }
+ }
+ }
+}
diff --git a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts
new file mode 100644
index 000000000..c891473a1
--- /dev/null
+++ b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts
@@ -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
diff --git a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.tsx b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.tsx
new file mode 100644
index 000000000..74be4b25a
--- /dev/null
+++ b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.tsx
@@ -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 = ({
+ query = '',
+ setQuery,
+ loading = false,
+ width = 250,
+ placeholder,
+ icon = 'search',
+ spinnerIcon = 'spinner'
+}) => {
+ const { getString } = useStrings()
+ return (
+
+
+ {loading && }
+ event.target.select()}
+ onInput={event => setQuery(event.currentTarget.value || '')}
+ />
+
+
+ )
+}
diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts
index 9c09ed878..ab1360b97 100644
--- a/web/src/framework/strings/stringTypes.ts
+++ b/web/src/framework/strings/stringTypes.ts
@@ -221,6 +221,7 @@ export interface StringsMap {
webhookCreated: string
webhookDeleted: string
webhookDetails: string
+ webhookEmpty: string
webhookEventsLabel: string
webhookListingContent: string
webhookSelectAllEvents: string
diff --git a/web/src/hooks/useGetPaginationInfo.ts b/web/src/hooks/useGetPaginationInfo.ts
deleted file mode 100644
index e8e3087d4..000000000
--- a/web/src/hooks/useGetPaginationInfo.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { useMemo } from 'react'
-
-export function useGetPaginationInfo(response: Nullable) {
- 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 }
-}
diff --git a/web/src/hooks/usePageIndex.ts b/web/src/hooks/usePageIndex.ts
index fa0234f71..f5cabcd16 100644
--- a/web/src/hooks/usePageIndex.ts
+++ b/web/src/hooks/usePageIndex.ts
@@ -1,5 +1,5 @@
import { useState } from 'react'
-export function usePageIndex(index = 0) {
+export function usePageIndex(index = 1) {
return useState(index)
}
diff --git a/web/src/hooks/useShowRequestError.ts b/web/src/hooks/useShowRequestError.ts
new file mode 100644
index 000000000..2ebbabe73
--- /dev/null
+++ b/web/src/hooks/useShowRequestError.ts
@@ -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 | null) {
+ const { showError } = useToaster()
+
+ useEffect(() => {
+ if (error) {
+ showError(getErrorMessage(error))
+ }
+ }, [error, showError])
+}
diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml
index 647b36703..a53add633 100644
--- a/web/src/i18n/strings.en.yaml
+++ b/web/src/i18n/strings.en.yaml
@@ -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.
+webhookEmpty: Here is no WebHooks. Try to
diff --git a/web/src/pages/Compare/Compare.tsx b/web/src/pages/Compare/Compare.tsx
index 50ba8079c..38604864a 100644
--- a/web/src/pages/Compare/Compare.tsx
+++ b/web/src/pages/Compare/Compare.tsx
@@ -12,8 +12,8 @@ import { makeDiffRefs } from 'utils/GitUtils'
import { CommitsView } from 'components/CommitsView/CommitsView'
import { Changes } from 'components/Changes/Changes'
import type { RepoCommit } from 'services/code'
-import { PrevNextPagination } from 'components/PrevNextPagination/PrevNextPagination'
import { usePageIndex } from 'hooks/usePageIndex'
+import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { CompareContentHeader } from './CompareContentHeader/CompareContentHeader'
import css from './Compare.module.scss'
@@ -24,17 +24,18 @@ export default function Compare() {
const { repoMetadata, error, loading, diffRefs } = useGetRepositoryMetadata()
const [sourceGitRef, setSourceGitRef] = useState(diffRefs.sourceGitRef)
const [targetGitRef, setTargetGitRef] = useState(diffRefs.targetGitRef)
- const [pageIndex, setPageIndex] = usePageIndex()
+ const [page, setPage] = usePageIndex()
const limit = LIST_FETCHING_LIMIT
const {
data: commits,
error: commitsError,
- refetch
+ refetch,
+ response
} = useGet({
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
queryParams: {
limit,
- page: pageIndex + 1,
+ page,
git_ref: sourceGitRef,
after: targetGitRef
},
@@ -87,7 +88,7 @@ export default function Compare() {
id="branchesTags"
defaultSelectedTabId="diff"
large={false}
- onChange={() => setPageIndex(0)}
+ onChange={() => setPage(1)}
tabList={[
{
id: 'commits',
@@ -95,10 +96,7 @@ export default function Compare() {
panel: (
{!!commits?.length && }
- 0 && (() => setPageIndex(pageIndex - 1))}
- onNext={commits?.length === limit && (() => setPageIndex(pageIndex + 1))}
- />
+
)
},
diff --git a/web/src/pages/PullRequest/PullRequestCommits/PullRequestCommits.tsx b/web/src/pages/PullRequest/PullRequestCommits/PullRequestCommits.tsx
index 649272ab6..9d1d0d7b6 100644
--- a/web/src/pages/PullRequest/PullRequestCommits/PullRequestCommits.tsx
+++ b/web/src/pages/PullRequest/PullRequestCommits/PullRequestCommits.tsx
@@ -4,8 +4,8 @@ import type { RepoCommit } from 'services/code'
import type { GitInfoProps } from 'utils/GitUtils'
import { voidFn, LIST_FETCHING_LIMIT } from 'utils/Utils'
import { usePageIndex } from 'hooks/usePageIndex'
+import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { CommitsView } from 'components/CommitsView/CommitsView'
-import { PrevNextPagination } from 'components/PrevNextPagination/PrevNextPagination'
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
export const PullRequestCommits: React.FC> = ({
@@ -13,17 +13,18 @@ export const PullRequestCommits: React.FC {
const limit = LIST_FETCHING_LIMIT
- const [pageIndex, setPageIndex] = usePageIndex()
+ const [page, setPage] = usePageIndex()
const {
data: commits,
error,
loading,
- refetch
+ refetch,
+ response
} = useGet({
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
queryParams: {
limit,
- page: pageIndex + 1,
+ page,
git_ref: pullRequestMetadata.source_branch,
after: pullRequestMetadata.target_branch
},
@@ -34,10 +35,7 @@ export const PullRequestCommits: React.FC
{!!commits?.length && }
- 0 && (() => setPageIndex(pageIndex - 1))}
- onNext={commits?.length === limit && (() => setPageIndex(pageIndex + 1))}
- />
+
)
}
diff --git a/web/src/pages/PullRequests/PullRequests.tsx b/web/src/pages/PullRequests/PullRequests.tsx
index f9ea70079..07916a34a 100644
--- a/web/src/pages/PullRequests/PullRequests.tsx
+++ b/web/src/pages/PullRequests/PullRequests.tsx
@@ -25,6 +25,7 @@ import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT } from 'utils/Utils'
import emptyStateImage from 'images/empty-state.svg'
import { usePageIndex } from 'hooks/usePageIndex'
import type { TypesPullReq } from 'services/code'
+import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { PullRequestsContentHeader } from './PullRequestsContentHeader/PullRequestsContentHeader'
import prImgOpen from './pull-request-open.svg'
import prImgMerged from './pull-request-merged.svg'
@@ -39,17 +40,18 @@ export default function PullRequests() {
const { routes } = useAppContext()
const [searchTerm, setSearchTerm] = useState('')
const [filter, setFilter] = useState(PullRequestFilterOption.OPEN)
- const [pageIndex, setPageIndex] = usePageIndex()
+ const [page, setPage] = usePageIndex()
const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
const {
data,
error: prError,
- loading: prLoading
+ loading: prLoading,
+ response
} = useGet({
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq`,
queryParams: {
limit: String(LIST_FETCHING_LIMIT),
- page: String(pageIndex + 1),
+ page,
sort: filter == PullRequestFilterOption.MERGED ? 'merged' : 'number',
order: 'desc',
query: searchTerm,
@@ -113,30 +115,33 @@ export default function PullRequests() {
repoMetadata={repoMetadata}
onPullRequestFilterChanged={_filter => {
setFilter(_filter)
- setPageIndex(0)
+ setPage(1)
}}
onSearchTermChanged={value => {
setSearchTerm(value)
- setPageIndex(0)
+ setPage(1)
}}
/>
{!!data?.length && (
-
- className={css.table}
- hideHeaders
- columns={columns}
- data={data}
- getRowClassName={() => css.row}
- onRowClick={row => {
- history.push(
- routes.toCODEPullRequest({
- repoPath: repoMetadata.path as string,
- pullRequestId: String(row.number)
- })
- )
- }}
- />
+ <>
+
+ className={css.table}
+ hideHeaders
+ columns={columns}
+ data={data}
+ getRowClassName={() => css.row}
+ onRowClick={row => {
+ history.push(
+ routes.toCODEPullRequest({
+ repoPath: repoMetadata.path as string,
+ pullRequestId: String(row.number)
+ })
+ )
+ }}
+ />
+
+ >
)}
{data?.length === 0 && (
diff --git a/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss b/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss
index 1d6a05153..c73209540 100644
--- a/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss
+++ b/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss
@@ -9,15 +9,6 @@
> div {
align-items: center;
}
-
- .input {
- margin-bottom: 0 !important;
-
- span[data-icon],
- span[icon] {
- margin-top: 10px !important;
- }
- }
}
.branchDropdown {
diff --git a/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss.d.ts b/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss.d.ts
index b6413ae9e..4978d5867 100644
--- a/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss.d.ts
+++ b/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.module.scss.d.ts
@@ -2,7 +2,6 @@
// this is an auto-generated file
declare const styles: {
readonly main: string
- readonly input: string
readonly branchDropdown: string
}
export default styles
diff --git a/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.tsx b/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.tsx
index 782c61bc9..830b3b246 100644
--- a/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.tsx
+++ b/web/src/pages/PullRequests/PullRequestsContentHeader/PullRequestsContentHeader.tsx
@@ -1,9 +1,10 @@
import { useHistory } from 'react-router-dom'
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 { CodeIcon, GitInfoProps, makeDiffRefs, PullRequestFilterOption } from 'utils/GitUtils'
import { useAppContext } from 'AppContext'
+import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
import css from './PullRequestsContentHeader.module.scss'
interface PullRequestsContentHeaderProps extends Pick {
@@ -36,7 +37,6 @@ export function PullRequestsContentHeader({
],
[getString]
)
- const showSpinner = useMemo(() => loading, [loading])
return (
@@ -51,18 +51,13 @@ export function PullRequestsContentHeader({
popoverClassName={css.branchDropdown}
/>
- event.target.select()}
- value={searchTerm}
- onInput={event => {
- const value = event.currentTarget.value
+ {
setSearchTerm(value)
onSearchTermChanged(value)
}}
- leftIcon={showSpinner ? CodeIcon.InputSpinner : CodeIcon.InputSearch}
/>